feat(vmis): 租戶自動開通完整流程 + Admin Portal SSO + NC 行事曆訂閱
Backend: - schedule_tenant: NC 新容器自動 pgsql 安裝 (_nc_db_check 全新容器處理) - schedule_tenant: NC 初始化加入 Redis + APCu memcache 設定 (修正 OIDC invalid_state) - schedule_tenant: 新租戶 KC realm 自動設定 accessCodeLifespan=600s (修正 authentication_expired) - schedule_account: NC Mail 帳號自動設定 (nc_mail_result/nc_mail_done_at) - schedule_account: NC 台灣國定假日行事曆自動訂閱 (CalDAV MKCALENDAR) - nextcloud_client: 新增 subscribe_calendar() CalDAV 訂閱方法 - settings: 新增系統設定 API (site_title/version/timezone/SSO/Keycloak) - models/result: 新增 nc_mail_result, nc_mail_done_at 欄位 - alembic: 遷移 002(system_settings) 003(keycloak_admin) 004(nc_mail_result) Frontend (Admin Portal): - 新增完整管理後台 (index/tenants/accounts/servers/schedules/logs/settings/system-status) - api.js: Keycloak JS Adapter SSO 整合 (PKCE/S256, fallback KC JS 來源, 自動 token 更新) - index.html: Promise.allSettled 取代 Promise.all,防止單一 API 失敗影響整頁 - 所有頁面加入 try/catch + toast 錯誤處理 - 新增品牌 LOGO 與 favicon Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
161
frontend/admin-portal/css/style.css
Normal file
161
frontend/admin-portal/css/style.css
Normal file
@@ -0,0 +1,161 @@
|
||||
/* VMIS Admin Portal - Custom Styles */
|
||||
|
||||
:root {
|
||||
--sidebar-width: 220px;
|
||||
--sidebar-bg: #1a1d23;
|
||||
--sidebar-active: #0d6efd;
|
||||
--header-height: 56px;
|
||||
}
|
||||
|
||||
body {
|
||||
font-size: 0.875rem;
|
||||
background: #f5f6fa;
|
||||
}
|
||||
|
||||
/* ── Sidebar ── */
|
||||
#sidebar {
|
||||
width: var(--sidebar-width);
|
||||
min-height: 100vh;
|
||||
background: var(--sidebar-bg);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#sidebar .brand {
|
||||
height: var(--header-height);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 1.2rem;
|
||||
border-bottom: 1px solid rgba(255,255,255,0.08);
|
||||
color: #fff;
|
||||
font-weight: 700;
|
||||
font-size: 1rem;
|
||||
letter-spacing: 0.03em;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#sidebar .nav-link {
|
||||
color: rgba(255,255,255,0.65);
|
||||
padding: 0.6rem 1.2rem;
|
||||
border-radius: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
font-size: 0.85rem;
|
||||
transition: background 0.15s, color 0.15s;
|
||||
}
|
||||
|
||||
#sidebar .nav-link:hover {
|
||||
color: #fff;
|
||||
background: rgba(255,255,255,0.07);
|
||||
}
|
||||
|
||||
#sidebar .nav-link.active {
|
||||
color: #fff;
|
||||
background: var(--sidebar-active);
|
||||
}
|
||||
|
||||
#sidebar .nav-section {
|
||||
color: rgba(255,255,255,0.3);
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 1rem 1.2rem 0.3rem;
|
||||
}
|
||||
|
||||
/* ── Main Content ── */
|
||||
#main {
|
||||
margin-left: var(--sidebar-width);
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#topbar {
|
||||
height: var(--header-height);
|
||||
background: #fff;
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 1.5rem;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
#content {
|
||||
padding: 1.5rem;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
/* ── Status Lights ── */
|
||||
.light {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.light-grey { background: #aaaaaa; }
|
||||
.light-green { background: #28a745; }
|
||||
.light-red { background: #dc3545; }
|
||||
|
||||
/* ── DataTable tweaks ── */
|
||||
.dataTables_wrapper .dataTables_filter input {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
.dataTables_wrapper .dataTables_length select {
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 0.375rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
}
|
||||
|
||||
table.dataTable thead th {
|
||||
border-bottom: 2px solid #dee2e6;
|
||||
background: #f8f9fa;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
table.dataTable tbody tr:hover {
|
||||
background: #f0f4ff;
|
||||
}
|
||||
|
||||
/* ── Cards ── */
|
||||
.stat-card {
|
||||
border: none;
|
||||
border-radius: 0.75rem;
|
||||
box-shadow: 0 1px 4px rgba(0,0,0,0.08);
|
||||
}
|
||||
|
||||
/* ── Availability bar ── */
|
||||
.avail-bar {
|
||||
height: 6px;
|
||||
border-radius: 3px;
|
||||
background: #e9ecef;
|
||||
overflow: hidden;
|
||||
}
|
||||
.avail-fill {
|
||||
height: 100%;
|
||||
border-radius: 3px;
|
||||
background: #28a745;
|
||||
transition: width 0.4s;
|
||||
}
|
||||
.avail-fill.warn { background: #ffc107; }
|
||||
.avail-fill.danger { background: #dc3545; }
|
||||
|
||||
/* ── System status matrix ── */
|
||||
.status-matrix .env-col {
|
||||
min-width: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* ── Modal ── */
|
||||
.modal-header { background: #f8f9fa; }
|
||||
Reference in New Issue
Block a user