Files
vmis/frontend/admin-portal/css/style.css
VMIS Developer 62baadb06f 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>
2026-03-15 15:31:37 +08:00

162 lines
3.0 KiB
CSS

/* 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; }