Initial commit

parents
Pipeline #2126 canceled with stages
# ==============================================================
# KHANSIA Enterprise Demo Portal - Environment Configuration
# Salin berkas ini ke .env sebelum menjalankan server production:
# cp .env.example .env
# ==============================================================
# --------------------------------------------------------------
# 1. DJANGO CORE CONFIGURATION
# --------------------------------------------------------------
# Generate secret key unik untuk production:
# python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"
SECRET_KEY=django-insecure-m#c3#akq73tn6u%(j30rxrb)h1v*h_2j=mo^$#6%(*sviz(vgd
# Set ke False untuk server production!
DEBUG=False
# Host domain yang diizinkan (pisahkan dengan koma jika lebih dari satu)
ALLOWED_HOSTS=demo.khansia.co.id,localhost,127.0.0.1
# CSRF Trusted Origins untuk HTTPS production
CSRF_TRUSTED_ORIGINS=https://demo.khansia.co.id,http://127.0.0.1:8008
# --------------------------------------------------------------
# 2. DATABASE CONFIGURATION
# --------------------------------------------------------------
# Pilihan DB_ENGINE: 'postgresql' atau 'sqlite'
# Default: sqlite (jika tidak diset)
DB_ENGINE=postgresql
# Parameter PostgreSQL Production
POSTGRES_DB=portal_khansia
POSTGRES_USER=khansia_user
POSTGRES_PASSWORD=PasswordKuatKhansia2026!
POSTGRES_HOST=127.0.0.1
POSTGRES_PORT=5432
# (Opsional) Jika menggunakan SQLite untuk development lokal:
# DB_ENGINE=sqlite
# SQLITE_NAME=db.sqlite3
# --------------------------------------------------------------
# 3. SERVER & REVERSE PROXY CONFIGURATION
# --------------------------------------------------------------
PORT=8008
WSGI_WORKERS=4
WSGI_TIMEOUT=60
.venv/
__pycache__/
*.pyc
*.pyo
*.pyd
.Python
env/
venv/
*.sqlite3
.DS_Store
#!/usr/bin/env bash
# ==============================================================================
# KHANSIA Enterprise Demo Portal - Production Deploy & Update Script
# Skrip 1-perintah untuk update dan deployment di server production.
# Cara pakai: ./deploy.sh
# ==============================================================================
set -e
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR"
echo "=========================================================="
echo " Deploying KHANSIA Enterprise Demo Portal (Production) "
echo "=========================================================="
# 1. Update source code jika menggunakan Git
if [ -d ".git" ]; then
echo "[1/6] Menarik update kode terbaru dari Git repository..."
git pull origin main || git pull origin master || true
else
echo "[1/6] Melewati git pull (bukan git repository)."
fi
# 2. Setup / Update Virtual Environment
echo "[2/6] Memperbarui virtual environment Python..."
if [ ! -d ".venv" ]; then
python3 -m venv .venv
fi
.venv/bin/pip install --upgrade pip
.venv/bin/pip install -r requirements.txt
# 3. Validasi Django Configuration
echo "[3/6] Memeriksa konfigurasi Django..."
.venv/bin/python manage.py check --deploy || .venv/bin/python manage.py check
# 4. Jalankan Migrasi Database
echo "[4/6] Menjalankan migrasi database..."
.venv/bin/python manage.py migrate --no-input
# 5. Kumpulkan Aset Statis
echo "[5/6] Mengumpulkan static files (collectstatic)..."
.venv/bin/python manage.py collectstatic --no-input
# 6. Restart WSGI Application Server (Gunicorn via Systemd jika aktif)
echo "[6/6] Me-restart service server production..."
if systemctl is-active --quiet portal_khansia 2>/dev/null; then
sudo systemctl restart portal_khansia
echo "✓ Service systemd 'portal_khansia' berhasil di-restart."
else
echo "✓ Catatan: Service systemd belum aktif. Jalankan manual atau konfigurasi systemd."
fi
echo "=========================================================="
echo " DEPLOYMENT PRODUCTION SELESAI DENGAN SUKSES! "
echo "=========================================================="
This diff is collapsed.
This diff is collapsed.
# Architecture Decision Records (ADR)
## ADR 001: Architecture & Presentation Strategy for Demo Portal
* **Status:** Accepted
* **Date:** 2026-09-16
* **Deciders:** Engineering Lead, Enterprise Solutions Architect
### Context
Portal `demo.khansia.co.id` berfungsi sebagai etalase produk dan gerbang masuk evaluasi klien korporat. Aplikasi-aplikasi demo yang sesungguhnya (KIERA ERP, Yakes Mobile Web, Digimall, CyberGuard) berjalan di infrastruktur mandiri dengan kredensial, database sandbox, dan sesi pengguna terpisah. Portal ini sendiri harus memuat dengan sangat cepat, minim biaya operasional, berkeamanan tinggi, dan mudah diperbarui katalognya.
### Decision
1. **Decoupled Catalog Architecture:**
* Portal demo dipisahkan sepenuhnya dari aplikasi demo riil.
* Portal demo dibangun sebagai aplikasi statis modern (Jamstack) yang membaca dataset aplikasi terstruktur (JSON/YAML).
* Akses ke aplikasi demo sesungguhnya ditangani melalui secure outbound redirect (`target="_blank"` dengan `rel="noopener noreferrer"`) ke subdomain khusus masing-masing sistem (misal: `https://demo-kiera.khansia.co.id`).
2. **Frontend UI Stack:**
* **Core Engine:** Semantic HTML5, Vanilla ES6+ (atau React/Astro untuk SSG) dengan Tailwind CSS v3.x.
* **Typography:** Plus Jakarta Sans & Inter (Google Fonts) untuk konsistensi visual identitas KHANSIA.
* **Design Pattern:** *Luminous Enterprise Kinetic* — surface `#f8fafc` dengan card white `#ffffff`, aksen oranye `#f97316`, dan kobalt `#1d4ed8`.
3. **Data Schema & State Management:**
* Seluruh katalog produk disimpan dalam modul data JSON tunggal (`catalogData`).
* Filter pencarian (search query + kategori filter) dieksekusi murni di sisi klien (Client-Side Filtering) tanpa perlu memicu network round-trip ke API server.
4. **Hosting & Deployment:**
* Hosting melalui Edge CDN (Cloudflare Pages / Vercel / Nginx reverse proxy) yang diarahkan dari domain utama `demo.khansia.co.id`.
* Enkripsi TLS 1.3 wajib dengan proteksi HTTP Security Headers (HSTS, Content-Security-Policy).
### Consequences
* **Positif:**
* Kecepatan muat instan tanpa dependensi database internal di sisi portal katalog.
* Jika salah satu server sandbox aplikasi pihak ketiga mengalami maintenance, portal demo utama tetap aktif 100%.
* Maintenance penambahan produk baru cukup dilakukan dengan menambahkan objek baru ke file JSON katalog.
* **Negatif:**
* Status keaktifan sandbox (health status) masih bersifat deklaratif; ke depannya memerlukan heartbeat API asynchronous jika ingin memantau uptime tiap sandbox secara real-time.
\ No newline at end of file
This diff is collapsed.
# Product Requirements Document (PRD)
## 1. Document Overview
* **Product Name:** KHANSIA Enterprise Demo Portal
* **Target Domain:** `demo.khansia.co.id`
* **Parent Brand:** PT Khatulistiwa Nusantara Indonesia (`khansia.co.id`)
* **Target Audience:** Calon klien korporat (BUMN, Enterprise, Institusi Pemerintah), C-Level Executive, IT Director/Architect, Procurement Specialist.
* **Goal:** Menyediakan etalase terpusat untuk mendemokan sistem/aplikasi unggulan KHANSIA dengan alur penjelasan modul, profil target persona pengguna, dan tombol langsung menuju sandbox aplikasi.
---
## 2. Problem Statement & Objectives
* **Masalah:** Mengirimkan tautan demo aplikasi langsung kepada calon klien sering kali membingungkan karena kurangnya konteks alur kerja, peran pengguna yang didukung, dan arsitektur dasar.
* **Solusi:** Sebuah portal kurasi portofolio interaktif (*Enterprise Experience Lab*) yang memberikan konteks menyeluruh (ringkasan, sasaran persona, daftar fitur siap uji, spesifikasi teknis) sebelum klien diarahkan ke live sandbox masing-masing aplikasi.
---
## 3. Scope & User Flow
### 3.1 User Journey
1. **Landing & Discovery:** Klien mengunjungi `demo.khansia.co.id`, melihat kredibilitas instansi (metrik SLA, ISO standard, partner list).
2. **Filtering & Searching:** Klien memfilter solusi berdasarkan kategori (`ERP Enterprise`, `Healthcare`, `Smart Facility`, `Cybersecurity`) atau menggunakan search bar (shortcut `⌘K`).
3. **Deep Dive (Technical Insight):** Klien membaca kartu aplikasi atau membuka modal **Spesifikasi Teknis** untuk melihat rincian persona pengguna, kapabilitas modul, dan tech stack.
4. **Hands-on Trial:** Klien menekan tombol **Coba Demo Aplikasi** dan dialihkan (redirect via `target="_blank"`) ke subdomain live demo aplikasi yang dituju (contoh: `demo-kiera.khansia.co.id`).
5. **Conversion:** Klien menghubungi tim konsultan teknis melalui tombol WhatsApp / Email briefing di sticky bar bawah.
---
## 4. Feature Specifications
### 4.1 Header & Hero
* **Branding:** Logo resmi KHANSIA, status badge sandbox aktif (`v3.4 Production Sandbox`), dan navigasi filter instan.
* **Hero Copy:** Menegaskan positioning sistem enterprise KHANSIA.
* **Value Metric Chips:** 30+ Modul Terintegrasi, 99.98% SLA, Hybrid Ready, ISO/IEC 27001 Compliance.
### 4.2 Application Catalog Grid
Setiap item portofolio ditampilkan dalam bentuk **Glass Card** berisi:
* **Tag Kategori & Status:** Indikator ketersediaan sandbox (misal: *Live Sandbox Online*).
* **Judul & Klien Asli:** Nama sistem serta nama instansi/klien pengadopsi (contoh: PT Khatulistiwa Nusantara Indonesia, PT Yakes Telkom, PT Mall TBK).
* **Mockup Browser Frame:** Tampilan antarmuka sistem dengan tag stack teknologi utama.
* **Deskripsi Solusi:** Ringkasan nilai bisnis aplikasi.
* **Persona Pengguna Akhir:** Minimal 3–4 chip peran pemakai (misal: *C-Level*, *Procurement*, *Dokter Spesialis*, *Teknisi ME*).
* **Fitur Unggulan Siap Uji:** Daftar checklist 4 kapabilitas utama yang dapat dicoba di sandbox.
* **Tombol Aksi Ganda:**
1. *Spesifikasi Teknis*: Membuka modal detail.
2. *Coba Demo Aplikasi*: External redirect link ke URL demo riil.
### 4.3 Detail Specification Modal
* Dialog modal responsif yang menyajikan:
* Detail arsitektur backend & database (Golang, ClickHouse, PostgreSQL, Kafka, dll).
* Breakdown peran pengguna beserta manfaat operasionalnya.
* Checklist fitur siap uji secara lengkap.
* Tombol direct trigger menuju live demo.
### 4.4 Social Proof & Workflow Guidance
* **Alur Pengujian (4-Step Flow):** Pilih Solusi $\rightarrow$ Periksa Spesifikasi $\rightarrow$ Jalankan Sandbox $\rightarrow$ PoC & Kustomisasi.
* **Client Trust Band:** Menampilkan daftar mitra strategis (PT Yakes Telkom, Telkom Indonesia, PT Mitratel, Telin, FTMD ITB).
* **Floating Contact Bar:** Jalur cepat penjadwalan *Technical Briefing* via WhatsApp atau Email korporat.
---
## 5. Non-Functional Requirements
* **Performance:** LCP (Largest Contentful Paint) $< 1.2$ detik; zero layout shifts (CLS $< 0.05$).
* **Aesthetics:** Tone formal korporat teknologi tinggi (*Pristine Luminosity*, cool-slate background, aksen oranye kinetik khas KHANSIA).
* **Responsiveness:** Full responsive layout dari resolusi seluler 360px hingga layar desktop 4K.
* **Accessibility:** Mendukung `prefers-reduced-motion` dan keyboard navigation (`Esc` untuk modal, `⌘K / Ctrl+K` untuk pencarian).
\ No newline at end of file
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'portal_project.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()
#!/usr/bin/env bash
# ==============================================================================
# KHANSIA Enterprise Demo Portal - SQLite to PostgreSQL Migration Runner
# Skrip otomatis untuk migrasi skema dan data dari SQLite ke PostgreSQL.
# Cara pakai: ./migrate_to_postgres.sh
# ==============================================================================
set -e
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR"
# Warna Output
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m' # No Color
echo -e "${BLUE}==============================================================${NC}"
echo -e "${BLUE} KHANSIA Portal - SQLite to PostgreSQL Migration Wizard ${NC}"
echo -e "${BLUE}==============================================================${NC}"
# 1. Pastikan Virtualenv aktif
if [ ! -d ".venv" ]; then
echo -e "${YELLOW}Virtual environment belum ada. Membuat .venv...${NC}"
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
fi
# 2. Muat variabel dari .env jika ada
if [ -f ".env" ]; then
echo -e "${GREEN}✓ Menemukan file .env, memuat konfigurasi default...${NC}"
export $(grep -v '^#' .env | xargs)
fi
# 3. Parameter Default / Prompt Interaktif
DB_NAME="${POSTGRES_DB:-portal_khansia}"
DB_USER="${POSTGRES_USER:-postgres}"
DB_PASS="${POSTGRES_PASSWORD:-}"
DB_HOST="${POSTGRES_HOST:-127.0.0.1}"
DB_PORT="${POSTGRES_PORT:-5432}"
echo -e "\nKonfigurasi Target PostgreSQL:"
echo -e " Database : ${GREEN}${DB_NAME}${NC}"
echo -e " User : ${GREEN}${DB_USER}${NC}"
echo -e " Host : ${GREEN}${DB_HOST}:${DB_PORT}${NC}"
# Jika password kosong, minta input
if [ -z "$DB_PASS" ]; then
read -sp "Masukkan Password PostgreSQL untuk user '${DB_USER}': " DB_PASS
echo ""
fi
# 4. Jalankan perintah migrasi Django
echo -e "\n${YELLOW}Memulai proses migrasi data...${NC}"
.venv/bin/python manage.py migrate_to_postgres \
--dbname="$DB_NAME" \
--user="$DB_USER" \
--password="$DB_PASS" \
--host="$DB_HOST" \
--port="$DB_PORT" \
--switch-env
echo -e "\n${GREEN}✓ Migrasi selesai! Anda dapat menjalankan server dengan PostgreSQL sekarang.${NC}"
from django.contrib import admin
# Register your models here.
from django.apps import AppConfig
class PortalConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'portal'
This diff is collapsed.
[
{
"key": "kiera",
"category_id": "erp",
"category_badge": "ERP & MANAJEMEN BISNIS",
"category_color": "orange",
"title": "KIERA — Khansia Enterprise Resource Planning",
"client_label": "Klien Produksi:",
"client_name": "PT Khatulistiwa Nusantara Indonesia / Multi-Enterprise",
"status_badge": "Live Sandbox Online",
"version_badge": "v4.1 LTS Production",
"icon": "corporate_fare",
"subdomain": "kiera-portal.khansia.co.id",
"tech_stack": "Go • PostgreSQL • React",
"mockup_url": "https://lh3.googleusercontent.com/aida-public/AB6AXuDKkywN803BcDYtb20oiO9DsqdaJ84Im5m6o_C_XNY5ikcX4vIFlf7OyfGzi3xGk8MzPWp64mO6Y8Ue-Wops2XohL5SbMUhmAh2rEJ006HKAubZeetG_1Ej0plahVOiu6uHtRKUYSU7GH4JSYCnwFRH3fIQQJj3qQPf_fCWolcPv64I_k8NEcC9q-25onf68tiXjUsl7y6RXKnZmWPPo5eGP8JEjDzfY17-imteSr3eJeeMAu0aus3e",
"description": "Solusi ERP modular yang mengorkestrasi alur lintas departemen perusahaan secara real-time: perencanaan material (MRP), pengadaan suku cadang, inventory multi-gudang, hingga akuntansi keuangan terotomatisasi.",
"personas": [
{
"role": "C-Level Executive",
"benefit": "Visibilitas kinerja finansial & perputaran stok real-time melalui BI Executive Dashboard.",
"icon": "business_center"
},
{
"role": "Procurement",
"benefit": "Otomatisasi Purchase Order, multi-gudang barcode tracking, dan reorder point alerts.",
"icon": "shopping_cart"
},
{
"role": "Logistik & Gudang",
"benefit": "Pelacakan alur rantai pasok dan approval workflow berjenjang multi-tier.",
"icon": "inventory_2"
},
{
"role": "Keuangan & Akuntansi",
"benefit": "General ledger otomatis, sinkronisasi invoice perpajakan, dan laporan laba-rugi instan.",
"icon": "account_balance"
}
],
"features": [
"Multi-Warehouse Inventory Control",
"Automated Financial Ledger & P&L",
"Vendor Lifecycle & Purchase Orders",
"Executive BI Analytics Dashboard"
],
"button_demo_text": "Coba Demo Aplikasi",
"demo_url": "https://demo.kiera.co.id",
"architecture": "Arsitektur Microservices terdistribusi dengan Golang REST/gRPC backend, PostgreSQL database cluster dengan replication, Redis in-memory cache layer, dan Kafka message broker.",
"search_keywords": "kiera erp enterprise resource planning sap oracle procurement inventory gudang keuangan akuntansi ledger po mrp bumn"
},
{
"key": "yakes",
"category_id": "healthcare",
"category_badge": "HEALTHCARE & MOBILE APP",
"category_color": "blue",
"title": "Yakes Mobile (Ekosistem Kesehatan Terpadu)",
"client_label": "Klien Produksi:",
"client_name": "PT Yakes Telkom Indonesia",
"status_badge": "Live Sandbox Online",
"version_badge": "v2.8 Live Production",
"icon": "medical_services",
"subdomain": "yakes-app.khansia.co.id",
"tech_stack": "Flutter • Node.js • Redis",
"mockup_url": "https://lh3.googleusercontent.com/aida-public/AB6AXuCHY4Ih3uHbklykFjtLMAt79B-lvccKNXlzrCo1-Z5ShfgmgcS5IE28SPb0qB58ewmYB1OLwnHC_f_VlUqst9_3k9KlMmzJF16KHHKo3gz6M4UeQRfjj0Tt1MLGEtyu6JNFVxyWFwzD8lhf8xghVrThyS16Ad4FR_gyWRotVrlEuEpag3BHWWQAQJ8wwHLltFF5HYocT1LQm6QLo-i1fpifL0V43MzlRWA6FNgvDrEtLUl1CUJjrHGZ",
"description": "Platform layanan faskes digital komprehensif: antrean digital faskes tanpa calo, telekonsultasi audio/video interaktif bersama dokter spesialis, resep obat elektronik (e-prescription), dan integrasi rekam medis digital.",
"personas": [
{
"role": "Pasien Peserta",
"icon": "person",
"benefit": "Ambil nomor antrean klinik tanpa menunggu fisik dan telekonsultasi dari rumah."
},
{
"role": "Dokter Spesialis",
"icon": "stethoscope",
"benefit": "Melihat rekam medis terpadu dan menulis resep digital secara akurat."
},
{
"role": "Resepsionis",
"icon": "badge",
"benefit": "Verifikasi kepesertaan aktif instan via pemindai QR Code anti-fraud."
},
{
"role": "Apotek & Farmasi",
"icon": "medication",
"benefit": "Menerima antrean resep otomatis dan estimasi durasi penyiapan obat."
}
],
"features": [
"Antrean Cerdas Real-time & Notifikasi",
"Tanya Dokter & Telekonsultasi Aman",
"E-Prescription & Status Ambil Obat",
"Kartu Digital QR Validasi Anti-Fraud"
],
"button_demo_text": "Coba Demo Aplikasi",
"demo_url": "https://demo-yakes.khansia.co.id",
"architecture": "Frontend Flutter Multi-platform, Node.js gateway microservices, WebRTC terenkripsi untuk telemedisin, dan integrasi HL7/FHIR standar SatuSehat Kemenkes.",
"search_keywords": "yakes mobile telemedicine kesehatan rs rumah sakit dokter klinik antrean obat bpjs faskes satu sehat telkom rekam medis"
},
{
"key": "digimall",
"category_id": "facility",
"category_badge": "SMART MALL & FACILITY OPS",
"category_color": "orange",
"title": "Digimall (Tenant, Billing & Facility Ops)",
"client_label": "Klien Produksi:",
"client_name": "PT Mall TBK / Pengelola Commercial Property",
"status_badge": "Live Sandbox Online",
"version_badge": "v3.0 Production Ready",
"icon": "storefront",
"subdomain": "digimall.khansia.co.id",
"tech_stack": "React Native • Express • MySQL",
"mockup_url": "https://lh3.googleusercontent.com/aida-public/AB6AXuBGuhiFNGY2GgdmZbVYURAyNZmgTILa6rP0ZQ6SGFgjgVe_a5U3UDKSX4QCFSpqNa4BHBxwjDnM3-QAq1yND8MqsDjRZNJDnW_clR9zCjoX_si6m_jN4pbdyYwo2OBN0VQIdiDhZ8KJR_Eci0IvkpLoJWamOWsWeyma-4TJF1T6lozwdcPDvbthuS4XcGb4qRms2OVYfHNCcORH-pJfqcaNAVPA-T33BhaLXszR0KC-PeP-P9UkaMRI",
"description": "Ekosistem operasional gedung modern untuk manajemen tagihan terpadu (listrik, air, service charge), pelacakan tiket perbaikan fasilitas dengan hitungan mundur SLA, dan penugasan teknisi gedung otomatis.",
"personas": [
{
"role": "Building Management",
"icon": "domain",
"benefit": "Pengawasan menyeluruh sewa tenant, penagihan utilitas, dan audit SLA komplain."
},
{
"role": "Tenant & Kasir Mall",
"icon": "store",
"benefit": "Cek rincian invoice bulanan (listrik/service charge) dan submit tiket kerusakan."
},
{
"role": "Teknisi ME",
"icon": "engineering",
"benefit": "Menerima dispatch tiket kerja langsung di ponsel lengkap dengan foto lokasi."
},
{
"role": "Tenant Relation",
"icon": "campaign",
"benefit": "Mendistribusikan surat edaran pengelola dan jadwal event promosi gedung."
}
],
"features": [
"Perhitungan Meteran Listrik & Air Otomatis",
"Tiket Komplain & SLA Response Counter",
"Verifikasi Foto Penyelesaian Teknisi",
"Broadcast Edaran Resmi Manajemen"
],
"button_demo_text": "Coba Demo Aplikasi",
"demo_url": "https://demo-digimall.khansia.co.id",
"architecture": "Frontend React Native & PWA, Express.js cluster backend, konektivitas IoT smart meter utilitas, dan gateway billing otomatis Virtual Account & QRIS.",
"search_keywords": "digimall tenant mall building management facility listrik service charge sewa komplain tiket teknisi mall tbk"
},
{
"key": "cyberguard",
"category_id": "cybersecurity",
"category_badge": "CYBERSECURITY & COMPLIANCE",
"category_color": "blue",
"title": "Khansia CyberGuard & Compliance Portal",
"client_label": "Sektor Industri:",
"client_name": "Financial Services, Banking & Enterprise IT",
"status_badge": "Live Sandbox Online",
"version_badge": "v1.9 Enterprise",
"icon": "shield_lock",
"subdomain": "cyberguard.khansia.co.id",
"tech_stack": "Rust Core • ClickHouse • Python",
"mockup_url": "https://lh3.googleusercontent.com/aida-public/AB6AXuB07yFJgGiqKUNnv7e6ic448GirbndfEOrYNk_ZpffXG8mbjcHgaCxD0Tu-QVUYpEy6z2EgiMQ_4VXkF7eOdffkKpL-JeqvXO6MyHGgBLf61979Nf-5Rfb7Bxvgx5NCZu99XXfJv6vOwjpE2AjbuqDNb78n6XJR9NXpQALOgjMOo3hGL1aU_Up6iIQgzuLshl_10YtQGtHudzUdQ3O2r3bPN-q5-93dnTP5u0OlD2Hm1h3EYIU8Z90E",
"description": "Pusat komando keamanan siber SOC: audit kontrol akses hak istimewa pengguna (RBAC), deteksi kerentanan otomatis pada infrastruktur cloud, dan automated generator kepatuhan regulasi ISO/IEC 27001 & PDP.",
"personas": [
{
"role": "CISO & Direktur TI",
"icon": "admin_panel_settings",
"benefit": "Skor risiko siber terkonsolidasi dan pemetaan kepatuhan ISO/IEC 27001 eksekutif."
},
{
"role": "Auditor Kepatuhan",
"icon": "verified_user",
"benefit": "Ekspor otomatis laporan bukti audit (audit trails) bertanda waktu tamper-proof."
},
{
"role": "DevSecOps & SOC",
"icon": "terminal",
"benefit": "Deteksi celah vulnerabilitas pada kode dan miskonfigurasi cloud security."
},
{
"role": "Cloud Architect",
"icon": "cloud_sync",
"benefit": "Pemantauan traffic anomali dan alarm ancaman siber secara real-time."
}
],
"features": [
"Automated Vulnerability Scan API & App",
"Hak Akses Privilege Drift & RBAC Matrix",
"Live SOC Threat Feed & Incident Dispatch",
"One-Click ISO 27001 Audit Evidence"
],
"button_demo_text": "Coba Demo Dashboard",
"demo_url": "https://demo-cyberguard.khansia.co.id",
"architecture": "Rust telemetry engine, ClickHouse columnar database berkecepatan tinggi, scanner worker Python terdistribusi, dan webhook integrasi SIEM.",
"search_keywords": "cybersecurity cyberguard soc audit kepatuhan iso 27001 vulnerability devops bank fintech perbankan siem log governance"
}
]
\ No newline at end of file
This diff is collapsed.
"""
Management command to create default superuser/admin credentials.
"""
from django.core.management.base import BaseCommand
from django.contrib.auth.models import User
class Command(BaseCommand):
help = 'Ensures default admin user exists'
def handle(self, *args, **options):
username = 'admin'
password = 'admin123'
email = 'admin@khansia.co.id'
user, created = User.objects.get_or_create(username=username, defaults={'email': email})
user.set_password(password)
user.is_staff = True
user.is_superuser = True
user.save()
if created:
self.stdout.write(self.style.SUCCESS(f"Successfully created admin user: '{username}' (password: '{password}')"))
else:
self.stdout.write(self.style.SUCCESS(f"Updated password for existing admin user: '{username}'"))
from django.db import models
# Create your models here.
"""
JSON Repository for Portfolio Applications.
Implements decoupled persistent storage without Django ORM ModelAdmin.
Stores applications in JSON format, supporting CRUD and thumbnail management.
"""
import os
import json
import uuid
import re
from pathlib import Path
from django.conf import settings
from .data import APPLICATIONS as INITIAL_APPLICATIONS
DATA_DIR = Path(settings.BASE_DIR) / 'portal' / 'data'
CATALOG_FILE = DATA_DIR / 'catalog.json'
MEDIA_THUMBNAILS_DIR = Path(settings.BASE_DIR) / 'media' / 'thumbnails'
def _ensure_storage():
"""Ensures data directory and initial catalog.json exist."""
DATA_DIR.mkdir(parents=True, exist_ok=True)
MEDIA_THUMBNAILS_DIR.mkdir(parents=True, exist_ok=True)
if not CATALOG_FILE.exists():
with open(CATALOG_FILE, 'w', encoding='utf-8') as f:
json.dump(INITIAL_APPLICATIONS, f, indent=2, ensure_ascii=False)
def get_all_applications():
"""Returns a list of all applications from JSON storage."""
_ensure_storage()
try:
with open(CATALOG_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except (json.JSONDecodeError, FileNotFoundError):
return INITIAL_APPLICATIONS
def get_application_by_key(key):
"""Finds an application by key (slug)."""
apps = get_all_applications()
for app in apps:
if app.get('key') == key:
return app
return None
def slugify_key(text):
"""Generates a clean URL-friendly key."""
text = text.lower().strip()
text = re.sub(r'[^a-z0-9]+', '-', text)
return text.strip('-') or str(uuid.uuid4())[:8]
def save_uploaded_thumbnail(uploaded_file):
"""
Saves an uploaded image to media/thumbnails/ and returns its public URL path.
"""
_ensure_storage()
ext = os.path.splitext(uploaded_file.name)[1].lower()
if not ext:
ext = '.png'
filename = f"thumb_{uuid.uuid4().hex[:12]}{ext}"
dest_path = MEDIA_THUMBNAILS_DIR / filename
with open(dest_path, 'wb+') as destination:
for chunk in uploaded_file.chunks():
destination.write(chunk)
return f"/{settings.MEDIA_URL.strip('/')}/thumbnails/{filename}"
def save_application(app_data, is_new=False):
"""
Saves an application (creates if is_new, or updates existing).
Returns the saved application object.
"""
_ensure_storage()
apps = get_all_applications()
if is_new:
base_key = slugify_key(app_data.get('key') or app_data.get('title', 'app'))
key = base_key
# Ensure uniqueness
counter = 1
existing_keys = {a.get('key') for a in apps}
while key in existing_keys:
key = f"{base_key}-{counter}"
counter += 1
app_data['key'] = key
apps.append(app_data)
else:
key = app_data.get('key')
found = False
for i, existing in enumerate(apps):
if existing.get('key') == key:
apps[i] = app_data
found = True
break
if not found:
apps.append(app_data)
with open(CATALOG_FILE, 'w', encoding='utf-8') as f:
json.dump(apps, f, indent=2, ensure_ascii=False)
return app_data
def delete_application(key):
"""
Deletes an application by key. Returns True if deleted, False otherwise.
"""
_ensure_storage()
apps = get_all_applications()
new_apps = [a for a in apps if a.get('key') != key]
if len(new_apps) == len(apps):
return False
with open(CATALOG_FILE, 'w', encoding='utf-8') as f:
json.dump(new_apps, f, indent=2, ensure_ascii=False)
return True
{% load static %}
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="{% static 'favicon.ico' %}"/>
<link rel="icon" type="image/png" href="{% static 'images/logo.png' %}"/>
<link rel="apple-touch-icon" href="{% static 'images/logo.png' %}"/>
<!-- Google Fonts & Material Symbols -->
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&amp;family=Plus+Jakarta+Sans:wght@500;600;700;800&amp;family=JetBrains+Mono:wght@400;500;600&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
brand: {
orange: "#f97316",
amber: "#fb923c",
cyan: "#0284c7",
blue: "#2563eb",
cobalt: "#1d4ed8",
},
surface: {
page: "#f8fafc",
card: "#ffffff",
alt: "#f1f5f9",
border: "#e2e8f0"
}
},
fontFamily: {
sans: ["Inter", "sans-serif"],
display: ["Plus Jakarta Sans", "sans-serif"],
mono: ["JetBrains Mono", "monospace"],
}
}
}
};
</script>
<link rel="stylesheet" href="{% static 'css/portal.css' %}"/>
{% block extra_head %}{% endblock %}
</head>
<body class="bg-slate-50 text-slate-800 min-h-screen flex flex-col antialiased">
<!-- Top Navigation Bar -->
<header class="sticky top-0 z-30 bg-white/90 backdrop-blur-md border-b border-slate-200">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-16 flex items-center justify-between gap-4">
<!-- Brand & Title -->
<div class="flex items-center gap-3.5">
<a href="{% url 'portal:dashboard' %}" class="flex items-center gap-2.5 group">
<div class="w-9 h-9 overflow-hidden flex items-center justify-center">
<img src="{{ brand.logo_url }}" alt="Logo" class="w-full h-full object-contain"/>
</div>
<span class="font-display font-extrabold text-lg text-slate-900 group-hover:text-brand-orange transition-colors">
{{ brand.name }} <span class="text-xs font-mono text-brand-orange font-semibold px-2 py-0.5 rounded-full bg-orange-50 border border-orange-200 ml-1">CMS</span>
</span>
</a>
</div>
<!-- Quick Actions -->
<div class="flex items-center gap-3">
<a href="{% url 'portal:index' %}" target="_blank" class="inline-flex items-center gap-1.5 px-3.5 py-1.5 rounded-xl text-xs font-semibold text-slate-600 hover:text-blue-700 hover:bg-blue-50 border border-slate-200 transition-all">
<span class="material-symbols-outlined text-[16px]">visibility</span>
<span>Lihat Web Live</span>
</a>
<div class="h-4 w-[1px] bg-slate-200"></div>
<div class="flex items-center gap-2">
<div class="w-8 h-8 rounded-full bg-slate-100 border border-slate-200 flex items-center justify-center text-slate-600 font-bold text-xs">
{{ request.user.username|slice:":1"|upper }}
</div>
<span class="text-xs font-medium text-slate-700 hidden sm:inline">{{ request.user.username }}</span>
</div>
<a href="{% url 'portal:logout' %}" class="inline-flex items-center gap-1 px-3 py-1.5 rounded-xl text-xs font-semibold text-rose-600 hover:bg-rose-50 border border-rose-200 transition-all ml-1" title="Logout">
<span class="material-symbols-outlined text-[16px]">logout</span>
<span class="hidden sm:inline">Keluar</span>
</a>
</div>
</div>
</header>
<!-- Flash Messages -->
{% if messages %}
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-4 w-full">
{% for message in messages %}
<div class="p-4 rounded-2xl flex items-center justify-between gap-3 text-sm font-medium shadow-sm transition-all mb-3 {% if message.tags == 'success' %}bg-emerald-50 text-emerald-800 border border-emerald-200{% elif message.tags == 'error' %}bg-rose-50 text-rose-800 border border-rose-200{% else %}bg-blue-50 text-blue-800 border border-blue-200{% endif %}">
<div class="flex items-center gap-2.5">
<span class="material-symbols-outlined text-[20px]">
{% if message.tags == 'success' %}check_circle{% elif message.tags == 'error' %}error{% else %}info{% endif %}
</span>
<span>{{ message }}</span>
</div>
<button onclick="this.parentElement.remove()" class="text-slate-400 hover:text-slate-600">
<span class="material-symbols-outlined text-[18px]">close</span>
</button>
</div>
{% endfor %}
</div>
{% endif %}
<!-- Main Content -->
<main class="flex-1 max-w-7xl w-full mx-auto px-4 sm:px-6 lg:px-8 py-8">
{% block content %}{% endblock %}
</main>
<!-- Footer -->
<footer class="py-6 border-t border-slate-200 bg-white text-center text-xs text-slate-500">
<p>© {% now "Y" %} {{ brand.company_full }} — Portfolio Content Management System</p>
</footer>
{% block extra_js %}{% endblock %}
</body>
</html>
{% extends 'portal/admin/base_admin.html' %}
{% block title %}Hapus Portofolio — {{ brand.name }} CMS{% endblock %}
{% block content %}
<div class="max-w-xl mx-auto py-10">
<div class="bg-white border border-slate-200 rounded-3xl p-8 sm:p-10 shadow-lg text-center space-y-6">
<!-- Warning Icon -->
<div class="w-16 h-16 rounded-2xl bg-rose-50 border border-rose-200 text-rose-600 flex items-center justify-center mx-auto">
<span class="material-symbols-outlined text-[34px]">warning</span>
</div>
<div>
<h1 class="font-display font-black text-2xl text-slate-900 tracking-tight">
Hapus Solusi Portofolio?
</h1>
<p class="text-xs sm:text-sm text-slate-500 mt-2 leading-relaxed">
Apakah Anda yakin ingin menghapus solusi <strong class="text-slate-900">"{{ app.title }}"</strong> dari katalog? Solusi ini tidak akan ditampilkan lagi pada halaman portal demo.
</p>
</div>
<!-- Mini Preview of Application -->
<div class="p-4 rounded-2xl bg-slate-50 border border-slate-200 text-left flex items-center gap-3">
{% if app.mockup_url %}
<img src="{{ app.mockup_url }}" alt="{{ app.title }}" class="w-14 h-14 rounded-xl object-cover border border-slate-200 flex-shrink-0"/>
{% else %}
<div class="w-14 h-14 rounded-xl bg-slate-200 flex items-center justify-center text-slate-400 flex-shrink-0">
<span class="material-symbols-outlined text-[24px]">{{ app.icon|default:'apps' }}</span>
</div>
{% endif %}
<div class="min-w-0">
<div class="text-[10px] font-mono text-slate-500 uppercase">{{ app.category_badge }}</div>
<div class="font-display font-bold text-sm text-slate-900 truncate">{{ app.title }}</div>
<div class="text-[11px] text-slate-500 font-mono truncate">{{ app.subdomain }}</div>
</div>
</div>
<!-- Actions -->
<form method="POST" class="pt-2 flex items-center justify-center gap-3">
{% csrf_token %}
<a href="{% url 'portal:dashboard' %}" class="w-1/2 py-2.5 px-4 rounded-xl text-xs sm:text-sm font-semibold text-slate-700 bg-slate-100 hover:bg-slate-200 border border-slate-200 transition-all text-center">
Batalkan
</a>
<button
type="submit"
class="w-1/2 py-2.5 px-4 rounded-xl text-xs sm:text-sm font-display font-bold text-white bg-rose-600 hover:bg-rose-700 shadow-md shadow-rose-600/25 active:scale-95 transition-all flex items-center justify-center gap-1.5"
>
<span class="material-symbols-outlined text-[17px]">delete_forever</span>
<span>Ya, Hapus Solusi</span>
</button>
</form>
</div>
</div>
{% endblock %}
This diff is collapsed.
This diff is collapsed.
{% load static %}
<!DOCTYPE html>
<html lang="id">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="{% static 'favicon.ico' %}"/>
<link rel="icon" type="image/png" href="{% static 'images/logo.png' %}"/>
<link rel="apple-touch-icon" href="{% static 'images/logo.png' %}"/>
<!-- Google Fonts & Material Symbols -->
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&amp;family=Plus+Jakarta+Sans:wght@600;700;800&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
brand: {
orange: "#f97316",
amber: "#fb923c",
blue: "#2563eb",
}
},
fontFamily: {
sans: ["Inter", "sans-serif"],
display: ["Plus Jakarta Sans", "sans-serif"],
}
}
}
};
</script>
<link rel="stylesheet" href="{% static 'css/portal.css' %}"/>
</head>
<body class="min-h-screen bg-slate-50 flex items-center justify-center p-4 relative antialiased selection:bg-orange-500 selection:text-white">
<!-- Ambient Mesh Background -->
<div class="fixed inset-0 pointer-events-none z-0 overflow-hidden">
<div class="absolute inset-0 bg-grid-pattern opacity-40"></div>
<div class="mesh-blob-1 absolute top-[5%] left-1/2 w-[600px] h-[450px] rounded-full bg-gradient-to-br from-orange-200/30 to-transparent blur-3xl pointer-events-none"></div>
<div class="mesh-blob-2 absolute bottom-[10%] -left-[10%] w-[500px] h-[400px] rounded-full bg-gradient-to-tr from-blue-200/25 to-transparent blur-3xl pointer-events-none"></div>
</div>
<!-- Login Card -->
<div class="relative z-10 w-full max-w-md bg-white border border-slate-200/80 rounded-3xl shadow-xl shadow-slate-200/60 p-8 sm:p-10">
<!-- Logo & Header -->
<div class="text-center mb-8">
<div class="w-16 h-16 overflow-hidden mx-auto mb-4 flex items-center justify-center">
<img src="{{ brand.logo_url }}" alt="Logo" class="w-full h-full object-contain"/>
</div>
<h1 class="font-display font-extrabold text-2xl text-slate-900 tracking-tight">
CMS Pengelola Portofolio
</h1>
<p class="text-xs text-slate-500 mt-1.5 leading-relaxed">
Masuk untuk menambah, mengedit thumbnail banner, dan memperbarui portofolio KHANSIA.
</p>
</div>
<!-- Error Alert -->
{% if error %}
<div class="p-3.5 mb-6 rounded-xl bg-rose-50 border border-rose-200 text-rose-800 text-xs font-medium flex items-center gap-2">
<span class="material-symbols-outlined text-[18px] text-rose-600">error</span>
<span>{{ error }}</span>
</div>
{% endif %}
<!-- Login Form -->
<form method="POST" action="{% url 'portal:login' %}{% if request.GET.next %}?next={{ request.GET.next }}{% endif %}" class="space-y-4">
{% csrf_token %}
<div>
<label for="username" class="block text-xs font-semibold text-slate-700 mb-1.5 uppercase tracking-wider font-mono">
Username
</label>
<div class="relative flex items-center">
<span class="absolute left-3 text-slate-400 material-symbols-outlined text-[20px]">person</span>
<input
type="text"
id="username"
name="username"
required
autofocus
placeholder="admin"
class="w-full pl-10 pr-4 py-2.5 rounded-xl text-sm bg-slate-50 border border-slate-200 focus:bg-white focus:border-brand-orange focus:ring-2 focus:ring-orange-500/20 text-slate-800 transition-all placeholder:text-slate-400"
/>
</div>
</div>
<div>
<label for="password" class="block text-xs font-semibold text-slate-700 mb-1.5 uppercase tracking-wider font-mono">
Kata Sandi
</label>
<div class="relative flex items-center">
<span class="absolute left-3 text-slate-400 material-symbols-outlined text-[20px]">lock</span>
<input
type="password"
id="password"
name="password"
required
placeholder="••••••••"
class="w-full pl-10 pr-10 py-2.5 rounded-xl text-sm bg-slate-50 border border-slate-200 focus:bg-white focus:border-brand-orange focus:ring-2 focus:ring-orange-500/20 text-slate-800 transition-all placeholder:text-slate-400"
/>
<button
type="button"
onclick="togglePasswordVisibility()"
class="absolute right-3 text-slate-400 hover:text-slate-600 focus:outline-none"
aria-label="Toggle password"
>
<span id="pw-icon" class="material-symbols-outlined text-[19px]">visibility</span>
</button>
</div>
</div>
<div class="pt-2">
<button
type="submit"
class="w-full py-3 px-4 rounded-xl text-sm font-display font-bold text-white bg-gradient-to-r from-orange-600 via-brand-orange to-amber-500 hover:from-orange-500 hover:to-amber-400 shadow-md shadow-orange-500/25 hover:shadow-lg transition-all active:scale-[0.98] flex items-center justify-center gap-2"
>
<span>Masuk ke Dashboard</span>
<span class="material-symbols-outlined text-[18px]">login</span>
</button>
</div>
</form>
<!-- Default Credentials Card -->
<div class="mt-6 pt-5 border-t border-slate-100 bg-slate-50/80 rounded-xl p-3 text-center">
<div class="flex items-center justify-center gap-1 text-[11px] font-mono text-slate-500 mb-1">
<span class="material-symbols-outlined text-[14px] text-amber-500">key</span>
<span>Akses Default Pengelola:</span>
</div>
<p class="text-xs font-mono font-medium text-slate-700">
User: <span class="font-bold text-brand-orange">admin</span> | Sandi: <span class="font-bold text-brand-orange">admin123</span>
</p>
</div>
<!-- Back to public portal -->
<div class="mt-6 text-center">
<a href="{% url 'portal:index' %}" class="inline-flex items-center gap-1.5 text-xs font-medium text-slate-500 hover:text-slate-800 transition-colors">
<span class="material-symbols-outlined text-[16px]">arrow_back</span>
<span>Kembali ke Halaman Utama</span>
</a>
</div>
</div>
<script>
function togglePasswordVisibility() {
const input = document.getElementById('password');
const icon = document.getElementById('pw-icon');
if (input.type === 'password') {
input.type = 'text';
icon.textContent = 'visibility_off';
} else {
input.type = 'password';
icon.textContent = 'visibility';
}
}
</script>
</body>
</html>
{% load static %}
<!DOCTYPE html>
<html class="scroll-smooth" lang="id">
<head>
<meta charset="utf-8"/>
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
<!-- Favicon -->
<link rel="icon" type="image/x-icon" href="{% static 'favicon.ico' %}"/>
<link rel="icon" type="image/png" href="{% static 'images/logo.png' %}"/>
<link rel="apple-touch-icon" href="{% static 'images/logo.png' %}"/>
<!-- Google Fonts & Material Symbols -->
<link href="https://fonts.googleapis.com" rel="preconnect"/>
<link crossorigin="" href="https://fonts.gstatic.com" rel="preconnect"/>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&amp;family=Plus+Jakarta+Sans:wght@500;600;700;800&amp;family=JetBrains+Mono:wght@400;500;600&amp;display=swap" rel="stylesheet"/>
<link href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:wght,FILL@100..700,0..1&amp;display=swap" rel="stylesheet"/>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"></script>
<script>
tailwind.config = {
darkMode: "class",
theme: {
extend: {
colors: {
brand: {
orange: "#f97316",
amber: "#fb923c",
cyan: "#0284c7",
blue: "#2563eb",
cobalt: "#1d4ed8",
},
surface: {
page: "#f8fafc",
card: "#ffffff",
alt: "#f1f5f9",
border: "#e2e8f0"
}
},
fontFamily: {
sans: ["Inter", "sans-serif"],
display: ["Plus Jakarta Sans", "sans-serif"],
mono: ["JetBrains Mono", "monospace"],
}
}
}
};
</script>
<!-- Custom Stylesheet -->
<link rel="stylesheet" href="{% static 'css/portal.css' %}"/>
{% block extra_head %}{% endblock %}
</head>
<body class="min-h-screen flex flex-col antialiased relative selection:bg-orange-500 selection:text-white">
<!-- Interactive Toast Notification -->
<div id="toast" class="fixed top-5 right-5 z-50 flex items-center gap-3 px-5 py-3.5 rounded-2xl bg-white/95 border border-slate-200 shadow-2xl backdrop-blur-xl transition-all duration-300 transform translate-y-[-100px] opacity-0 pointer-events-none">
<div class="w-8 h-8 rounded-xl bg-orange-50 text-brand-orange flex items-center justify-center flex-shrink-0">
<span id="toast-icon" class="material-symbols-outlined text-[19px]">check_circle</span>
</div>
<div>
<h4 class="font-display font-bold text-xs text-slate-900">Portal Notification</h4>
<p id="toast-message" class="text-xs text-slate-600 font-medium"></p>
</div>
</div>
<!-- Background Decorative Canvas with Ambient Mesh Gradients -->
<div class="fixed inset-0 pointer-events-none z-0 overflow-hidden">
<div class="absolute inset-0 bg-grid-pattern opacity-40"></div>
<div class="mesh-blob-1 absolute top-[-10%] left-1/2 w-[720px] h-[520px] rounded-full bg-gradient-to-br from-orange-200/30 via-amber-100/20 to-transparent blur-3xl pointer-events-none"></div>
<div class="mesh-blob-2 absolute top-[25%] -left-[10%] w-[580px] h-[480px] rounded-full bg-gradient-to-tr from-blue-200/25 via-sky-100/20 to-transparent blur-3xl pointer-events-none"></div>
<div class="mesh-blob-3 absolute top-[55%] -right-[10%] w-[640px] h-[520px] rounded-full bg-gradient-to-bl from-blue-200/20 via-orange-100/15 to-transparent blur-3xl pointer-events-none"></div>
</div>
{% block content %}{% endblock %}
<!-- Technical Specification Modal -->
{% include 'portal/components/spec_modal.html' %}
<!-- Serialized Application Data for JavaScript -->
<script>
window.portalAppData = JSON.parse('{{ app_data_json|escapejs }}');
</script>
<!-- Client Interactivity JS -->
<script src="{% static 'js/portal.js' %}"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
<!-- APPLICATION CARD (GLASS CARD) -->
<div class="app-card glass-card rounded-3xl p-7 sm:p-9 flex flex-col justify-between group"
data-category="{{ app.category_id }}"
data-keywords="{{ app.search_keywords }}"
data-title="{{ app.title }}">
<div class="flex flex-col space-y-6">
<!-- Card Header & Meta -->
<div class="flex items-start justify-between gap-4">
<div>
<div class="flex flex-wrap items-center gap-2 mb-2.5">
<span class="px-2.5 py-1 rounded-md text-[11px] font-mono font-bold uppercase tracking-wider {% if app.category_color == 'blue' %}bg-blue-50 text-blue-700 border border-blue-200{% else %}bg-orange-50 text-orange-700 border border-orange-200{% endif %}">
{{ app.category_badge }}
</span>
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md text-[11px] font-mono font-medium text-emerald-700 bg-emerald-50 border border-emerald-200">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 pulse-indicator"></span>
{{ app.status_badge }}
</span>
</div>
<h3 class="font-display font-bold text-2xl text-slate-900 {% if app.category_color == 'blue' %}group-hover:text-blue-600{% else %}group-hover:text-brand-orange{% endif %} transition-colors">
{{ app.title }}
</h3>
<p class="text-xs font-medium text-slate-500 mt-1">
{{ app.client_label }} <span class="text-slate-800 font-semibold">{{ app.client_name }}</span>
</p>
</div>
<!-- Icon Box -->
<div class="w-12 h-12 rounded-2xl {% if app.category_color == 'blue' %}bg-blue-50 border border-blue-200 text-blue-600{% else %}bg-orange-50 border border-orange-200 text-brand-orange{% endif %} flex items-center justify-center flex-shrink-0 shadow-sm transition-transform duration-300 group-hover:rotate-6 group-hover:scale-105">
<span class="material-symbols-outlined text-[26px]">{{ app.icon }}</span>
</div>
</div>
<!-- Browser Frame with Mockup Zoom Effect -->
<div class="rounded-2xl overflow-hidden border border-slate-200 bg-white shadow-md">
<div class="px-4 py-2.5 bg-slate-100 border-b border-slate-200 flex items-center justify-between text-xs">
<div class="flex items-center gap-2">
<span class="w-2.5 h-2.5 rounded-full bg-rose-400 inline-block"></span>
<span class="w-2.5 h-2.5 rounded-full bg-amber-400 inline-block"></span>
<span class="w-2.5 h-2.5 rounded-full bg-emerald-400 inline-block"></span>
<span class="ml-2 font-mono text-[11px] text-slate-600 font-medium hidden sm:inline">{{ app.subdomain }}</span>
</div>
<span class="font-mono text-[11px] font-semibold text-blue-700 bg-blue-50 px-2.5 py-0.5 rounded border border-blue-200">
{{ app.tech_stack }}
</span>
</div>
<div class="relative h-60 w-full overflow-hidden bg-slate-100">
<img src="{{ app.mockup_url }}" alt="{{ app.title }} Preview" class="w-full h-full object-cover object-top group-hover:scale-[1.02] transition-transform duration-500 ease-out"/>
<div class="absolute inset-0 bg-gradient-to-t from-slate-900/10 via-transparent to-transparent pointer-events-none"></div>
</div>
</div>
<!-- Description -->
<p class="text-sm sm:text-[15px] text-slate-600 leading-relaxed">
{{ app.description }}
</p>
<!-- Personas -->
<div class="space-y-2">
<div class="text-xs font-mono text-slate-500 font-bold uppercase tracking-wider">
Target Pengguna Akhir:
</div>
<div class="flex flex-wrap gap-2">
{% for p in app.personas %}
<span class="inline-flex items-center gap-1.5 px-3 py-1 rounded-lg text-xs font-semibold bg-slate-100 text-slate-700 border border-slate-200 hover:bg-slate-200/80 transition-colors">
<span class="material-symbols-outlined text-[15px] {% if app.category_color == 'blue' %}text-blue-600{% else %}text-brand-orange{% endif %}">{{ p.icon }}</span>
{{ p.role }}
</span>
{% endfor %}
</div>
</div>
<!-- Features -->
<div class="space-y-2.5 pt-3 border-t border-slate-100">
<div class="text-xs font-mono text-slate-500 font-bold uppercase tracking-wider">
Fitur Unggulan Tersedia di Sandbox:
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2.5 text-xs sm:text-[13px] text-slate-700">
{% for feat in app.features %}
<div class="flex items-center gap-2 group/feat">
<span class="material-symbols-outlined {% if app.category_color == 'blue' %}text-blue-600{% else %}text-brand-orange{% endif %} text-[18px] transition-transform duration-200 group-hover/feat:scale-110">check_circle</span>
<span>{{ feat }}</span>
</div>
{% endfor %}
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="mt-8 pt-5 border-t border-slate-100 flex flex-col sm:flex-row items-center justify-between gap-3">
<button onclick="openDetailModal('{{ app.key }}')" class="ripple-surface w-full sm:w-auto px-5 py-2.5 rounded-xl text-xs sm:text-sm font-semibold text-slate-700 bg-slate-100 hover:bg-slate-200 border border-slate-200 active:scale-95 transition-all flex items-center justify-center gap-2">
<span class="material-symbols-outlined text-[18px]">menu_book</span>
<span>Spesifikasi Teknis</span>
</button>
<a href="{{ app.demo_url }}" target="_blank" rel="noopener noreferrer" onclick="showToast('Membuka Sandbox {{ app.title }}...', 'launch')" class="ripple-surface w-full sm:w-auto px-6 py-2.5 rounded-xl text-xs sm:text-sm font-display font-bold text-white {% if app.category_color == 'blue' %}bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-500 hover:to-indigo-500 shadow-md shadow-blue-500/20{% else %}bg-gradient-to-r from-orange-600 to-amber-500 hover:from-orange-500 hover:to-amber-400 shadow-md shadow-orange-500/20{% endif %} hover:shadow-lg hover:-translate-y-0.5 active:translate-y-0 active:scale-95 transition-all duration-200 flex items-center justify-center gap-1.5">
<span>{{ app.button_demo_text }}</span>
<span class="material-symbols-outlined text-[17px] transition-transform group-hover:translate-x-0.5">arrow_outward</span>
</a>
</div>
</div>
<!-- CATALOG SHOWCASE GRID SECTION -->
<section id="katalog-demo" class="max-w-7xl mx-auto px-6 lg:px-8 py-12">
<!-- Section Telemetry Header -->
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-4 mb-8 pb-4 border-b border-slate-200/80">
<div>
<div class="inline-flex items-center gap-2 text-xs font-mono font-bold text-brand-orange uppercase tracking-wider mb-1">
<span class="w-1.5 h-1.5 rounded-full bg-brand-orange"></span>
PORTFOLIO SHOWCASE &amp; LIVE SANDBOX
</div>
<h2 class="font-display font-black text-2xl sm:text-3xl text-slate-900 tracking-tight">
Katalog Sistem Produksi Siap Uji
</h2>
</div>
<div class="flex items-center gap-2">
<span id="counter-display" class="px-3.5 py-1.5 rounded-full text-xs font-mono font-semibold bg-white border border-slate-200 text-slate-700 shadow-sm flex items-center gap-2">
<span class="w-2 h-2 rounded-full bg-emerald-500 pulse-indicator"></span>
Menampilkan {{ total_applications }} Solusi Siap Uji
</span>
</div>
</div>
<!-- Cards Grid (2 columns on lg, 1 column on mobile/tablet) -->
<div id="applications-grid" class="grid grid-cols-1 lg:grid-cols-2 gap-8 lg:gap-10">
{% for app in applications %}
{% include 'portal/components/app_card.html' with app=app %}
{% endfor %}
</div>
<!-- No Results State -->
<div id="no-results-state" class="hidden text-center py-16 rounded-3xl bg-white border border-slate-200 shadow-sm p-8 mt-8 transition-all duration-300">
<div class="w-16 h-16 rounded-2xl bg-slate-100 flex items-center justify-center text-slate-400 mx-auto mb-4 animate-bounce">
<span class="material-symbols-outlined text-[32px]">search_off</span>
</div>
<h3 class="font-display font-bold text-xl text-slate-900">Sistem Belum Ditemukan</h3>
<p class="text-sm text-slate-500 mt-2 max-w-md mx-auto">
Kata kunci pencarian tidak cocok dengan aplikasi demo sandbox saat ini. Tim arsitektur KHANSIA dapat menyiapkan prototype kustom sesuai kebutuhan Anda.
</p>
<button onclick="clearSearch()" class="mt-6 px-5 py-2.5 rounded-xl text-xs font-semibold bg-brand-orange text-white hover:bg-orange-500 hover:shadow-md active:scale-95 transition-all">
Reset Filter &amp; Tampilkan Semua
</button>
</div>
</section>
<!-- ENTERPRISE FOOTER (LIGHT CRISP PALETTE) -->
<footer class="relative z-10 w-full bg-slate-100/80 border-t border-slate-200 text-slate-600 pt-16 pb-12">
<div class="max-w-7xl mx-auto px-6 lg:px-8">
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-10 pb-12 border-b border-slate-200">
<!-- Col 1: Identity & Logo -->
<div class="space-y-4">
<div class="flex items-center gap-3">
<div class="w-8 h-8 overflow-hidden flex items-center justify-center">
<img src="{{ brand.logo_url }}" alt="{{ brand.name }} Logo" class="w-full h-full object-contain"/>
</div>
<span class="font-display font-extrabold text-lg text-slate-900 tracking-tight">{{ brand.name }}</span>
</div>
<p class="font-display font-semibold text-xs text-brand-orange tracking-wide">{{ brand.tagline }}</p>
<p class="text-xs text-slate-500 leading-relaxed">
Portal resmi pengujian demonstrasi sistem, arsitektur microservices, dan solusi transformasi digital enterprise dari {{ brand.company_full }}.
</p>
<div class="inline-flex items-center gap-2 px-3 py-1 rounded-full text-[11px] font-mono bg-emerald-100 text-emerald-800 border border-emerald-300">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-600 pulse-indicator"></span>
All Sandbox Nodes Operational
</div>
</div>
<!-- Col 2: Kategori Solusi -->
<div class="space-y-3">
<h4 class="font-display font-bold text-sm text-slate-900">Kategori Solusi Demo</h4>
<ul class="space-y-2 text-xs text-slate-600">
<li><a href="#katalog-demo" onclick="applyCategoryFilter('erp')" class="hover:text-brand-orange hover:translate-x-1 inline-block transition-all">Enterprise Resource Planning (KIERA)</a></li>
<li><a href="#katalog-demo" onclick="applyCategoryFilter('healthcare')" class="hover:text-brand-orange hover:translate-x-1 inline-block transition-all">Healthcare &amp; Telemedicine (Yakes Mobile)</a></li>
<li><a href="#katalog-demo" onclick="applyCategoryFilter('facility')" class="hover:text-brand-orange hover:translate-x-1 inline-block transition-all">Smart Facility &amp; Tenant Ops (Digimall)</a></li>
<li><a href="#katalog-demo" onclick="applyCategoryFilter('cybersecurity')" class="hover:text-brand-orange hover:translate-x-1 inline-block transition-all">Cybersecurity &amp; SOC Telemetry (CyberGuard)</a></li>
<li><a href="#katalog-demo" onclick="applyCategoryFilter('all')" class="hover:text-brand-orange hover:translate-x-1 inline-block transition-all">Custom Microservices Architecture</a></li>
</ul>
</div>
<!-- Col 3: Kantor & Kontak -->
<div class="space-y-3">
<h4 class="font-display font-bold text-sm text-slate-900">Kantor &amp; Kontak Resmi</h4>
<p class="text-xs text-slate-600 font-medium">{{ brand.company_full }}</p>
<p class="text-xs text-slate-500">{{ brand.address }}</p>
<p class="text-xs text-slate-500">Telepon: {{ brand.phone }}</p>
<p class="text-xs text-slate-500">Email: {{ brand.email }}</p>
<p class="text-xs text-slate-500">Jam Operasional: {{ brand.hours }}</p>
</div>
<!-- Col 4: Keamanan & SLA -->
<div class="space-y-3">
<h4 class="font-display font-bold text-sm text-slate-900">Standar Keamanan</h4>
<p class="text-xs text-slate-500 leading-relaxed">
Seluruh lingkungan sandbox terisolasi dengan data dummy terenkripsi TLS 1.3, mematuhi standar kerahasiaan data dan kepatuhan ISO/IEC 27001.
</p>
<div class="pt-2">
<a href="mailto:{{ brand.email }}?subject=Konsultasi%20Sistem%20Enterprise" onclick="showToast('Membuka support tiket enterprise...', 'mail')" class="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-lg text-xs font-semibold text-blue-700 bg-blue-50 border border-blue-200 hover:bg-blue-100 hover:border-blue-300 transition-all active:scale-95">
<span class="material-symbols-outlined text-[16px]">mail</span>
<span>Hubungi Enterprise Support</span>
</a>
</div>
</div>
</div>
<!-- Copyright & Bottom Links -->
<div class="pt-8 flex flex-col sm:flex-row items-center justify-between gap-4 text-xs text-slate-500">
<p>© {% now "Y" %} {{ brand.company_full }}. All rights reserved.</p>
<div class="flex items-center gap-6">
<a href="{% url 'portal:dashboard' %}" class="hover:text-brand-orange transition-colors font-semibold">Kelola Portofolio (CMS)</a>
<a href="{% url 'portal:privacy_policy' %}" class="hover:text-slate-800 transition-colors">Kebijakan Privasi</a>
<a href="#" onclick="showToast('99.98% SLA Uptime Terverifikasi', 'info')" class="hover:text-slate-800 transition-colors">SLA &amp; Audit Log</a>
<a href="#" onclick="showToast('Seluruh Node Sandbox Berjalan Normal', 'verified')" class="hover:text-slate-800 transition-colors">Status Sandbox</a>
</div>
</div>
</div>
</footer>
<!-- ENTERPRISE HEADER -->
<header class="sticky top-0 z-40 w-full bg-white/80 backdrop-blur-xl border-b border-slate-200/80 transition-all">
<div class="max-w-7xl mx-auto px-6 lg:px-8 h-20 flex items-center justify-between gap-4">
<!-- Logo & Brand Badge -->
<a href="{% url 'portal:index' %}" class="flex items-center gap-3.5 group">
<div class="w-10 h-10 overflow-hidden transition-transform duration-300 group-hover:scale-105 flex items-center justify-center">
<img src="{{ brand.logo_url }}" alt="{{ brand.name }} Logo" class="w-full h-full object-contain"/>
</div>
<div class="flex flex-col">
<div class="flex items-center gap-2">
<span class="font-display font-black text-xl tracking-tight text-slate-900 group-hover:text-brand-orange transition-colors">{{ brand.name }}</span>
<span class="hidden sm:inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded-full text-[11px] font-mono font-medium tracking-wide bg-blue-50 border border-blue-200/70 text-blue-700 hover:bg-blue-100 transition-colors">
<span class="w-1.5 h-1.5 rounded-full bg-emerald-500 pulse-indicator"></span>
{{ brand.subdomain }}
</span>
</div>
<span class="text-[11px] font-medium text-slate-500 tracking-wide">{{ brand.tagline }}</span>
</div>
</a>
<!-- Category Nav Filter Buttons (Desktop Nav) -->
<nav class="hidden xl:flex items-center gap-1.5 p-1 rounded-full bg-slate-100/90 border border-slate-200">
{% for cat in categories %}
<button onclick="applyCategoryFilter('{{ cat.id }}', this)" class="nav-filter-btn px-4 py-1.5 rounded-full text-xs font-semibold {% if cat.active %}bg-brand-orange text-white shadow-sm{% else %}text-slate-600 hover:text-slate-900 hover:bg-slate-200/60{% endif %} transition-all duration-200 hover:scale-[1.02] active:scale-95">
{{ cat.label }}
</button>
{% endfor %}
</nav>
<!-- Primary Actions & Schedule Briefing -->
<div class="flex items-center gap-3">
{% if request.user.is_authenticated %}
<a href="{% url 'portal:dashboard' %}" class="inline-flex items-center gap-1.5 px-3.5 py-2 rounded-xl text-xs font-semibold text-brand-orange bg-orange-50 hover:bg-orange-100 border border-orange-200 transition-all active:scale-95">
<span class="material-symbols-outlined text-[17px]">dashboard</span>
<span class="hidden sm:inline">CMS Panel</span>
</a>
{% endif %}
<a href="#katalog-demo" class="hidden md:inline-flex items-center gap-1.5 px-4 py-2 rounded-xl text-xs font-semibold text-slate-700 hover:text-blue-700 hover:bg-blue-50 border border-slate-200 hover:border-blue-200 transition-all active:scale-95">
<span class="material-symbols-outlined text-[17px] text-blue-600">explore</span>
Katalog Live
</a>
<a href="{{ brand.whatsapp_url }}" target="_blank" rel="noopener noreferrer" class="ripple-surface inline-flex items-center gap-2 px-4 sm:px-5 py-2.5 rounded-xl text-xs sm:text-sm font-display font-bold text-white bg-gradient-to-r from-orange-600 via-brand-orange to-amber-500 hover:from-orange-500 hover:to-amber-400 shadow-md shadow-orange-500/25 hover:shadow-lg hover:shadow-orange-500/35 hover:-translate-y-0.5 active:translate-y-0 active:scale-95 transition-all duration-200">
<span>Briefing Teknis</span>
<span class="material-symbols-outlined text-[17px] transition-transform duration-200 group-hover:translate-x-1">arrow_forward</span>
</a>
</div>
</div>
</header>
<!-- HERO SECTION -->
<section class="relative pt-16 pb-16 lg:pt-24 lg:pb-20 max-w-7xl mx-auto px-6 lg:px-8 text-center flex flex-col items-center">
<!-- Live Badge Pill with Pulse Animation -->
<div class="reveal-item delay-100 inline-flex items-center gap-2.5 px-4 py-1.5 rounded-full bg-white border border-slate-200 text-xs font-mono text-slate-700 shadow-sm mb-6 backdrop-blur-md hover:border-slate-300 hover:shadow transition-all">
<span class="relative flex h-2.5 w-2.5">
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500 pulse-indicator"></span>
</span>
<span class="font-bold text-slate-800 tracking-wide">ENTERPRISE EXPERIENCE LAB</span>
<span class="text-slate-300">/</span>
<span class="text-blue-700 font-semibold">{{ brand.sandbox_version }}</span>
</div>
<!-- Main Headline -->
<h1 class="reveal-item delay-200 font-display font-extrabold text-4xl sm:text-5xl lg:text-6xl text-slate-950 tracking-tight leading-[1.15] max-w-4xl">
Jelajahi Ekosistem Solusi Digital <span class="text-gradient-orange">{{ brand.name }} Enterprise</span>
</h1>
<!-- Subtitle -->
<p class="reveal-item delay-300 mt-6 font-sans text-base sm:text-lg lg:text-xl text-slate-600 max-w-3xl font-normal leading-relaxed">
Eksplorasi portofolio sistem SaaS, ERP korporat, dan aplikasi terintegrasi berkinerja tinggi yang telah teruji dan dipercaya di berbagai sektor industri nasional dan BUMN.
</p>
<!-- Metric Value Chips (Grid of 4) -->
<div class="reveal-item delay-400 mt-10 grid grid-cols-2 md:grid-cols-4 gap-3.5 sm:gap-5 w-full max-w-4xl">
{% for metric in metrics %}
<div class="p-4 sm:p-5 rounded-2xl bg-white border border-slate-200/90 shadow-sm hover:shadow-md hover:border-slate-300 transition-all text-left flex items-start gap-3.5 group">
<div class="w-10 h-10 rounded-xl {{ metric.color_class }} border flex items-center justify-center flex-shrink-0 transition-transform group-hover:scale-105">
<span class="material-symbols-outlined text-[20px]">{{ metric.icon }}</span>
</div>
<div>
<div class="font-display font-extrabold text-base sm:text-lg text-slate-900 leading-snug">{{ metric.title }}</div>
<div class="text-[11px] sm:text-xs text-slate-500 font-medium">{{ metric.subtitle }}</div>
</div>
</div>
{% endfor %}
</div>
<!-- Interactive Search Input Bar with Keyboard Shortcut & Quick Filters -->
<div class="reveal-item delay-500 mt-10 w-full max-w-2xl">
<div class="relative flex items-center shadow-lg shadow-slate-200/50 rounded-2xl bg-white border border-slate-200 hover:border-slate-300 focus-within:border-blue-500 focus-within:ring-4 focus-within:ring-blue-500/10 transition-all p-1.5">
<div class="pl-3.5 pr-2 text-slate-400 flex items-center justify-center">
<span class="material-symbols-outlined text-[22px]">search</span>
</div>
<input
id="demo-search-input"
type="text"
placeholder="Cari solusi atau fitur (misal: ERP gudang, telemedicine, komplain tenant, audit SOC)..."
class="w-full py-2.5 text-xs sm:text-sm text-slate-800 placeholder-slate-400 bg-transparent border-0 focus:outline-none focus:ring-0"
aria-label="Pencarian Solusi Enterprise"
/>
<div class="flex items-center gap-1.5 pr-2">
<kbd class="hidden sm:inline-flex items-center gap-0.5 px-2 py-1 text-[10px] font-mono font-semibold text-slate-500 bg-slate-100 border border-slate-200 rounded-md">
<span>⌘K</span>
</kbd>
<button onclick="clearSearch()" class="px-2.5 py-1 text-xs font-semibold text-slate-500 hover:text-slate-800 hover:bg-slate-100 rounded-lg transition-colors" title="Reset filter">
Reset
</button>
</div>
</div>
</div>
</section>
<!-- INTERACTIVE SPECIFICATION MODAL (LIGHT THEME) -->
<div id="detail-modal" class="fixed inset-0 z-50 flex items-center justify-center p-4 sm:p-6 bg-slate-900/60 backdrop-blur-md hidden transition-all duration-300 opacity-0">
<div id="modal-box" class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-white border border-slate-200 rounded-3xl shadow-2xl p-6 sm:p-8 flex flex-col space-y-6 text-slate-700 transform scale-95 transition-all duration-300">
<!-- Modal Header -->
<div class="flex items-start justify-between gap-4 border-b border-slate-100 pb-5">
<div class="flex items-center gap-3.5">
<div id="modal-app-icon" class="w-12 h-12 rounded-2xl bg-orange-50 border border-orange-200 text-brand-orange flex items-center justify-center flex-shrink-0">
<span class="material-symbols-outlined text-[26px]">corporate_fare</span>
</div>
<div>
<div class="flex items-center gap-2 mb-1">
<span id="modal-app-category" class="px-2 py-0.5 rounded text-[10px] font-mono font-bold uppercase tracking-wider bg-orange-100 text-orange-700">
ERP
</span>
<span id="modal-app-badge" class="font-mono text-xs text-slate-500 font-medium">v4.1 LTS</span>
</div>
<h3 id="modal-app-title" class="font-display font-bold text-xl text-slate-900">
KIERA Enterprise Resource Planning
</h3>
</div>
</div>
<button onclick="closeDetailModal()" class="w-9 h-9 rounded-full bg-slate-100 hover:bg-slate-200 text-slate-500 hover:text-slate-800 flex items-center justify-center active:scale-90 transition-all" aria-label="Tutup Dialog">
<span class="material-symbols-outlined text-[20px]">close</span>
</button>
</div>
<!-- Architecture Box -->
<div class="p-4 rounded-xl bg-slate-50 border border-slate-200 space-y-1">
<div class="flex items-center gap-1.5 text-xs font-mono font-bold text-blue-700">
<span class="material-symbols-outlined text-[16px]">memory</span>
<span>ARSITEKTUR &amp; SPESIFIKASI BACKEND</span>
</div>
<p id="modal-app-architecture" class="text-xs sm:text-[13px] text-slate-600 leading-relaxed"></p>
</div>
<!-- User Personas Grid -->
<div class="space-y-2.5">
<h4 class="text-xs font-mono uppercase tracking-wider text-slate-500 font-bold">Profil Persona Pengguna:</h4>
<div id="modal-personas-container" class="grid grid-cols-1 sm:grid-cols-2 gap-2.5"></div>
</div>
<!-- Features List -->
<div class="space-y-2.5">
<h4 class="text-xs font-mono uppercase tracking-wider text-slate-500 font-bold">Cakupan Fitur yang Siap Diuji:</h4>
<ul id="modal-features-list" class="space-y-2 text-xs sm:text-[13px] text-slate-700"></ul>
</div>
<!-- Demo Credentials & Launch Button -->
<div class="pt-4 border-t border-slate-100 flex flex-col sm:flex-row items-center justify-between gap-4">
<div class="flex items-center gap-2 text-xs text-emerald-700 font-mono font-semibold">
<span class="material-symbols-outlined text-[18px]">key</span>
<span>Kredensial Demo Sandbox Terisi Otomatis</span>
</div>
<a id="modal-launch-demo-btn" href="#" target="_blank" rel="noopener noreferrer" onclick="showToast('Membuka live sandbox...', 'launch')" class="ripple-surface w-full sm:w-auto px-6 py-2.5 rounded-xl text-xs sm:text-sm font-display font-bold text-white bg-gradient-to-r from-orange-600 to-amber-500 hover:from-orange-500 hover:to-amber-400 shadow-md shadow-orange-500/25 active:scale-95 flex items-center justify-center gap-1.5 transition-all">
<span>Buka Sandbox Sekarang</span>
<span class="material-symbols-outlined text-[17px]">arrow_outward</span>
</a>
</div>
</div>
</div>
<!-- SECTION 5: FLOATING / STICKY BOTTOM CALL TO ACTION -->
<aside class="sticky bottom-6 z-40 max-w-7xl mx-auto px-6 lg:px-8 w-full mb-6">
<div class="p-6 rounded-2xl bg-white/95 border border-slate-200 backdrop-blur-2xl shadow-xl flex flex-col md:flex-row items-center justify-between gap-6 transition-all duration-300 hover:shadow-2xl">
<div class="flex items-center gap-4 text-left">
<div class="w-12 h-12 rounded-xl bg-gradient-to-br from-brand-orange to-amber-500 text-white flex items-center justify-center flex-shrink-0 shadow-md shadow-orange-500/25">
<span class="material-symbols-outlined text-[26px]">support_agent</span>
</div>
<div>
<h4 class="font-display font-bold text-base sm:text-lg text-slate-900">
Butuh Dedicated Sandbox atau Proof of Concept (PoC)?
</h4>
<p class="text-xs sm:text-sm text-slate-500">
Konsultasikan kebutuhan arsitektur sistem khusus dan integrasi API On-Premise langsung dengan tim engineer kami.
</p>
</div>
</div>
<div class="flex flex-wrap items-center gap-3 w-full md:w-auto">
<a href="{{ brand.whatsapp_url }}" target="_blank" rel="noopener noreferrer" onclick="showToast('Menghubungkan ke WhatsApp Enterprise Support...', 'chat')" class="ripple-surface flex-1 md:flex-none px-4 py-2.5 rounded-xl bg-slate-50 hover:bg-slate-100 text-emerald-700 font-semibold text-xs sm:text-sm border border-emerald-300 flex items-center justify-center gap-2 transition-all shadow-sm hover:shadow active:scale-95">
<span class="material-symbols-outlined text-[18px]">chat</span>
<span>WhatsApp: 0877-6383-8845</span>
</a>
<a href="{{ brand.email_briefing_url }}" onclick="showToast('Membuka template email brief...', 'mail')" class="ripple-surface flex-1 md:flex-none px-5 py-2.5 rounded-xl bg-gradient-to-r from-orange-600 to-amber-500 hover:from-orange-500 hover:to-amber-400 text-white font-display font-bold text-xs sm:text-sm shadow-md shadow-orange-500/25 hover:shadow-lg hover:-translate-y-0.5 active:translate-y-0 active:scale-95 flex items-center justify-center gap-2 transition-all">
<span class="material-symbols-outlined text-[18px]">calendar_today</span>
<span>Jadwalkan Technical Briefing</span>
</a>
</div>
</div>
</aside>
<!-- SECTION 3: PROTOKOL TESTING / ALUR PENGUJIAN -->
<section class="max-w-7xl mx-auto px-6 lg:px-8 py-20 border-t border-slate-200">
<div class="text-center max-w-3xl mx-auto mb-14">
<div class="inline-flex items-center gap-2 text-xs font-mono font-bold text-brand-orange uppercase tracking-wider mb-2">
<span class="w-2 h-2 rounded-full bg-brand-orange"></span>
EVALUASI TRANSPARAN &amp; TANPA HAMBATAN
</div>
<h2 class="font-display font-extrabold text-3xl sm:text-4xl text-slate-900 tracking-tight">
Alur Pengujian Sandbox Demo
</h2>
<p class="mt-4 text-slate-600 text-sm sm:text-base">
Setiap calon mitra dapat mengevaluasi fungsionalitas dan skalabilitas sistem KHANSIA secara independen dalam 4 langkah terstruktur:
</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
{% for step in steps %}
<div class="step-card p-7 rounded-2xl bg-white border border-slate-200 shadow-sm relative flex flex-col justify-between group {% if step.action_color == 'blue' %}hover:border-blue-600{% else %}hover:border-brand-orange{% endif %} hover:-translate-y-1.5 hover:shadow-md transition-all duration-300">
<div>
<div class="step-number font-display font-black text-3xl {% if step.action_color == 'blue' %}text-gradient-blue{% else %}text-gradient-orange{% endif %} mb-3">
{{ step.step }}
</div>
<h3 class="font-display font-bold text-lg text-slate-900 mb-2 {% if step.action_color == 'blue' %}group-hover:text-blue-600{% else %}group-hover:text-brand-orange{% endif %} transition-colors">
{{ step.title }}
</h3>
<p class="text-xs sm:text-sm text-slate-600 leading-relaxed">
{{ step.description }}
</p>
</div>
<div class="mt-6 pt-4 border-t border-slate-100 flex items-center text-xs font-mono font-semibold {% if step.action_color == 'blue' %}text-blue-600{% else %}text-brand-orange{% endif %} gap-1.5">
<span>{{ step.action_label }}</span>
<span class="material-symbols-outlined text-[15px] transition-transform duration-200 group-hover:translate-x-1">arrow_forward</span>
</div>
</div>
{% endfor %}
</div>
</section>
<!-- SECTION 4: CLIENT TRUSTED LOGO BAND -->
<section class="max-w-7xl mx-auto px-6 lg:px-8 py-14 border-t border-slate-200">
<div class="rounded-3xl p-8 sm:p-10 bg-white border border-slate-200 shadow-sm text-center">
<div class="text-xs font-mono uppercase tracking-widest text-slate-500 font-bold mb-7">
DIPERCAYA OLEH EKOSISTEM BUMN, PERUSAHAAN NASIONAL &amp; LEMBAGA TINGGI
</div>
<div class="flex flex-wrap items-center justify-center gap-8 sm:gap-14 text-slate-700 font-display font-bold text-sm sm:text-base">
{% for partner in partners %}
<span class="hover:text-blue-700 transition-colors cursor-pointer hover:scale-105 duration-200" onclick="showToast('{{ partner.toast }}', 'verified')">
{{ partner.name }}
</span>
{% endfor %}
</div>
</div>
</section>
{% extends 'portal/base.html' %}
{% block content %}
<!-- Header Component -->
{% include 'portal/components/header.html' %}
<!-- Main Content Container -->
<main class="relative z-10 flex-1">
<!-- Hero Section with Metrics and Search -->
{% include 'portal/components/hero.html' %}
<!-- Applications Catalog Grid -->
{% include 'portal/components/catalog_grid.html' %}
<!-- Testing Protocol / 4-Step Flow -->
{% include 'portal/components/test_flow.html' %}
<!-- Client Trusted Logo Band -->
{% include 'portal/components/trust_band.html' %}
<!-- Floating / Sticky Contact Bar -->
{% include 'portal/components/sticky_cta.html' %}
</main>
<!-- Enterprise Footer -->
{% include 'portal/components/footer.html' %}
{% endblock %}
This diff is collapsed.
This diff is collapsed.
from django.urls import path
from . import views
app_name = 'portal'
urlpatterns = [
path('', views.index, name='index'),
path('health/', views.health_check, name='health_check'),
path('kebijakan-privasi/', views.privacy_policy_view, name='privacy_policy'),
# Custom Tailwind CMS Auth & Management
path('login/', views.login_view, name='login'),
path('logout/', views.logout_view, name='logout'),
path('manage/', views.dashboard_view, name='dashboard'),
path('manage/create/', views.application_create_view, name='create'),
path('manage/edit/<str:key>/', views.application_edit_view, name='edit'),
path('manage/delete/<str:key>/', views.application_delete_view, name='delete'),
]
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment