No One Can Stop Me Now
This commit is contained in:
370
services/setec-manager/web/static/css/style.css
Normal file
370
services/setec-manager/web/static/css/style.css
Normal file
@@ -0,0 +1,370 @@
|
||||
/* Setec App Manager — Dark Theme */
|
||||
:root {
|
||||
--primary: #6366f1;
|
||||
--primary-hover: #818cf8;
|
||||
--surface: #222536;
|
||||
--bg: #1a1b2e;
|
||||
--text: #e2e8f0;
|
||||
--text-muted: #94a3b8;
|
||||
--border: #2e3148;
|
||||
--ok: #22c55e;
|
||||
--err: #ef4444;
|
||||
--warn: #f59e0b;
|
||||
--radius: 6px;
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* ── Sidebar ── */
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
background: var(--surface);
|
||||
border-right: 1px solid var(--border);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
padding: 1.25rem 1rem;
|
||||
border-bottom: 1px solid var(--border);
|
||||
}
|
||||
|
||||
.sidebar-header h1 {
|
||||
font-size: 1.25rem;
|
||||
color: var(--primary);
|
||||
letter-spacing: 0.1em;
|
||||
}
|
||||
|
||||
.sidebar-header .subtitle {
|
||||
font-size: .75rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.nav-menu {
|
||||
list-style: none;
|
||||
flex: 1;
|
||||
padding: .5rem 0;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.sidebar-logo {
|
||||
width: 160px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.nav-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
vertical-align: middle;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: .55rem 1rem;
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
font-size: .9rem;
|
||||
transition: background .15s, color .15s;
|
||||
}
|
||||
|
||||
.nav-link:hover,
|
||||
.nav-link.active {
|
||||
background: rgba(99, 102, 241, .1);
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
.sidebar-footer {
|
||||
padding: .75rem 1rem;
|
||||
border-top: 1px solid var(--border);
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
.sidebar-footer .user-badge {
|
||||
color: var(--text-muted);
|
||||
display: block;
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
|
||||
.sidebar-footer .logout {
|
||||
color: var(--err);
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
/* ── Content ── */
|
||||
.content {
|
||||
margin-left: 220px;
|
||||
flex: 1;
|
||||
padding: 1.5rem 2rem;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.content h2 {
|
||||
margin-bottom: 1rem;
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
|
||||
.content h3 {
|
||||
margin: 1.25rem 0 .75rem;
|
||||
font-size: 1.1rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* ── Stat Cards / Grid ── */
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.stat-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 1rem 1.25rem;
|
||||
}
|
||||
|
||||
.stat-card h3 {
|
||||
margin: 0 0 .5rem;
|
||||
font-size: .95rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.stat-card .big-number {
|
||||
font-size: 2rem;
|
||||
font-weight: 700;
|
||||
color: var(--primary);
|
||||
}
|
||||
|
||||
/* ── Data Table ── */
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.data-table th,
|
||||
.data-table td {
|
||||
padding: .6rem .85rem;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-size: .9rem;
|
||||
}
|
||||
|
||||
.data-table th {
|
||||
background: rgba(99, 102, 241, .08);
|
||||
color: var(--text-muted);
|
||||
font-weight: 600;
|
||||
font-size: .8rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: .04em;
|
||||
}
|
||||
|
||||
.data-table tbody tr:hover {
|
||||
background: rgba(99, 102, 241, .04);
|
||||
}
|
||||
|
||||
.data-table a {
|
||||
color: var(--primary);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.data-table a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* ── Buttons ── */
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: .5rem 1rem;
|
||||
font-size: .875rem;
|
||||
font-weight: 500;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
background: var(--surface);
|
||||
color: var(--text);
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
transition: background .15s, border-color .15s;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: var(--border);
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--primary);
|
||||
border-color: var(--primary);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
background: var(--primary-hover);
|
||||
border-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
.btn-sm {
|
||||
padding: .3rem .6rem;
|
||||
font-size: .8rem;
|
||||
}
|
||||
|
||||
/* ── Badges ── */
|
||||
.badge {
|
||||
display: inline-block;
|
||||
padding: .15rem .5rem;
|
||||
font-size: .75rem;
|
||||
font-weight: 600;
|
||||
border-radius: 999px;
|
||||
background: var(--border);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.badge-ok {
|
||||
background: rgba(34, 197, 94, .15);
|
||||
color: var(--ok);
|
||||
}
|
||||
|
||||
.badge-err {
|
||||
background: rgba(239, 68, 68, .15);
|
||||
color: var(--err);
|
||||
}
|
||||
|
||||
/* ── Forms ── */
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: .3rem;
|
||||
font-size: .85rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.form-group input[type="text"],
|
||||
.form-group input[type="password"],
|
||||
.form-group input[type="email"],
|
||||
.form-group select,
|
||||
.form-group textarea {
|
||||
width: 100%;
|
||||
padding: .5rem .75rem;
|
||||
font-size: .9rem;
|
||||
background: var(--bg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text);
|
||||
outline: none;
|
||||
transition: border-color .15s;
|
||||
}
|
||||
|
||||
.form-group input:focus,
|
||||
.form-group select:focus,
|
||||
.form-group textarea:focus {
|
||||
border-color: var(--primary);
|
||||
}
|
||||
|
||||
/* ── Progress Bar ── */
|
||||
.progress-bar {
|
||||
width: 100%;
|
||||
height: 8px;
|
||||
background: var(--border);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin: .4rem 0;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: var(--primary);
|
||||
border-radius: 4px;
|
||||
transition: width .3s ease;
|
||||
}
|
||||
|
||||
/* ── Login Page ── */
|
||||
.login-page {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.login-card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 2rem;
|
||||
width: 100%;
|
||||
max-width: 380px;
|
||||
}
|
||||
|
||||
.login-card h1 {
|
||||
color: var(--primary);
|
||||
letter-spacing: 0.1em;
|
||||
margin-bottom: .25rem;
|
||||
}
|
||||
|
||||
.login-card .subtitle {
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 1.5rem;
|
||||
font-size: .85rem;
|
||||
}
|
||||
|
||||
.login-card .btn {
|
||||
width: 100%;
|
||||
margin-top: .5rem;
|
||||
}
|
||||
|
||||
/* ── Error Message ── */
|
||||
.error-msg {
|
||||
margin-top: .75rem;
|
||||
padding: .5rem .75rem;
|
||||
background: rgba(239, 68, 68, .1);
|
||||
border: 1px solid rgba(239, 68, 68, .3);
|
||||
border-radius: var(--radius);
|
||||
color: var(--err);
|
||||
font-size: .85rem;
|
||||
}
|
||||
|
||||
/* ── Utility ── */
|
||||
code {
|
||||
background: var(--bg);
|
||||
padding: .1rem .35rem;
|
||||
border-radius: 3px;
|
||||
font-size: .85em;
|
||||
}
|
||||
|
||||
p { margin-bottom: .35rem; }
|
||||
|
||||
/* ── Responsive ── */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar { width: 60px; }
|
||||
.sidebar-header h1 { font-size: .9rem; }
|
||||
.sidebar-header .subtitle,
|
||||
.sidebar-footer .user-badge { display: none; }
|
||||
.nav-link { padding: .5rem; font-size: .75rem; text-align: center; }
|
||||
.content { margin-left: 60px; padding: 1rem; }
|
||||
.stats-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
13
services/setec-manager/web/static/img/favicon.svg
Normal file
13
services/setec-manager/web/static/img/favicon.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
|
||||
<defs>
|
||||
<linearGradient id="fg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#6366f1"/>
|
||||
<stop offset="100%" style="stop-color:#a855f7"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="32" height="32" rx="6" fill="#1e1e2e"/>
|
||||
<path d="M16 3 L28 9 L28 19 Q28 27 16 30 Q4 27 4 19 L4 9 Z"
|
||||
fill="none" stroke="url(#fg)" stroke-width="1.5" opacity="0.6"/>
|
||||
<text x="16" y="22" font-family="monospace" font-size="16" font-weight="bold"
|
||||
fill="url(#fg)" text-anchor="middle">S</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 629 B |
63
services/setec-manager/web/static/img/icons.svg
Normal file
63
services/setec-manager/web/static/img/icons.svg
Normal file
@@ -0,0 +1,63 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display:none">
|
||||
<!-- Dashboard -->
|
||||
<symbol id="icon-dashboard" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="3" width="7" height="7"/><rect x="14" y="3" width="7" height="7"/>
|
||||
<rect x="14" y="14" width="7" height="7"/><rect x="3" y="14" width="7" height="7"/>
|
||||
</symbol>
|
||||
<!-- Sites / Globe -->
|
||||
<symbol id="icon-sites" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/>
|
||||
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/>
|
||||
</symbol>
|
||||
<!-- Shield / Security -->
|
||||
<symbol id="icon-shield" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
</symbol>
|
||||
<!-- Lock / SSL -->
|
||||
<symbol id="icon-lock" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/>
|
||||
</symbol>
|
||||
<!-- Server / Nginx -->
|
||||
<symbol id="icon-server" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<rect x="2" y="2" width="20" height="8" rx="2"/><rect x="2" y="14" width="20" height="8" rx="2"/>
|
||||
<line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/>
|
||||
</symbol>
|
||||
<!-- Firewall / Shield-off -->
|
||||
<symbol id="icon-firewall" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/>
|
||||
<line x1="9" y1="9" x2="15" y2="15"/><line x1="15" y1="9" x2="9" y2="15"/>
|
||||
</symbol>
|
||||
<!-- Users -->
|
||||
<symbol id="icon-users" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/>
|
||||
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>
|
||||
</symbol>
|
||||
<!-- Backup / Archive -->
|
||||
<symbol id="icon-backup" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/>
|
||||
<line x1="10" y1="12" x2="14" y2="12"/>
|
||||
</symbol>
|
||||
<!-- Monitor / Activity -->
|
||||
<symbol id="icon-monitor" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>
|
||||
</symbol>
|
||||
<!-- Logs / Terminal -->
|
||||
<symbol id="icon-logs" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>
|
||||
</symbol>
|
||||
<!-- Float / USB / Link -->
|
||||
<symbol id="icon-float" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
|
||||
<path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
|
||||
</symbol>
|
||||
<!-- Hosting / Cloud-Server -->
|
||||
<symbol id="icon-hosting" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M18 10h-1.26A8 8 0 1 0 9 20h9a5 5 0 0 0 0-10z"/>
|
||||
<line x1="12" y1="13" x2="12" y2="17"/><circle cx="12" cy="13" r="1"/>
|
||||
</symbol>
|
||||
<!-- AUTARCH / Hexagon -->
|
||||
<symbol id="icon-autarch" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/>
|
||||
<polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/>
|
||||
</symbol>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
19
services/setec-manager/web/static/img/logo-full.svg
Normal file
19
services/setec-manager/web/static/img/logo-full.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 60" width="300" height="60">
|
||||
<defs>
|
||||
<linearGradient id="brandGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#6366f1"/>
|
||||
<stop offset="100%" style="stop-color:#a855f7"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Shield icon -->
|
||||
<path d="M30 5 L52 15 L52 33 Q52 48 30 53 Q8 48 8 33 L8 15 Z"
|
||||
fill="url(#brandGrad)" opacity="0.15" stroke="url(#brandGrad)" stroke-width="1.5"/>
|
||||
<text x="30" y="38" font-family="monospace" font-size="24" font-weight="bold"
|
||||
fill="url(#brandGrad)" text-anchor="middle">S</text>
|
||||
<!-- SETEC text -->
|
||||
<text x="70" y="32" font-family="monospace" font-size="26" font-weight="bold"
|
||||
fill="url(#brandGrad)" letter-spacing="4">SETEC</text>
|
||||
<!-- Subtitle -->
|
||||
<text x="70" y="48" font-family="monospace" font-size="11" fill="#6b7280"
|
||||
letter-spacing="2">APP MANAGER</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 939 B |
28
services/setec-manager/web/static/img/logo.svg
Normal file
28
services/setec-manager/web/static/img/logo.svg
Normal file
@@ -0,0 +1,28 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
|
||||
<defs>
|
||||
<linearGradient id="grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#6366f1;stop-opacity:1" />
|
||||
<stop offset="100%" style="stop-color:#a855f7;stop-opacity:1" />
|
||||
</linearGradient>
|
||||
<filter id="glow">
|
||||
<feGaussianBlur stdDeviation="3" result="blur"/>
|
||||
<feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
|
||||
</filter>
|
||||
</defs>
|
||||
<!-- Shield shape -->
|
||||
<path d="M100 10 L180 50 L180 120 Q180 170 100 190 Q20 170 20 120 L20 50 Z"
|
||||
fill="url(#grad)" opacity="0.15" stroke="url(#grad)" stroke-width="2"/>
|
||||
<!-- Inner shield -->
|
||||
<path d="M100 25 L165 58 L165 115 Q165 158 100 175 Q35 158 35 115 L35 58 Z"
|
||||
fill="none" stroke="url(#grad)" stroke-width="1.5" opacity="0.4"/>
|
||||
<!-- S letter -->
|
||||
<text x="100" y="125" font-family="monospace" font-size="90" font-weight="bold"
|
||||
fill="url(#grad)" text-anchor="middle" filter="url(#glow)">S</text>
|
||||
<!-- Circuit lines -->
|
||||
<line x1="55" y1="155" x2="30" y2="155" stroke="#6366f1" stroke-width="1.5" opacity="0.5"/>
|
||||
<circle cx="30" cy="155" r="3" fill="#6366f1" opacity="0.5"/>
|
||||
<line x1="145" y1="155" x2="170" y2="155" stroke="#a855f7" stroke-width="1.5" opacity="0.5"/>
|
||||
<circle cx="170" cy="155" r="3" fill="#a855f7" opacity="0.5"/>
|
||||
<line x1="100" y1="45" x2="100" y2="25" stroke="#6366f1" stroke-width="1.5" opacity="0.5"/>
|
||||
<circle cx="100" cy="22" r="3" fill="#6366f1" opacity="0.5"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
18
services/setec-manager/web/static/img/sidebar-logo.svg
Normal file
18
services/setec-manager/web/static/img/sidebar-logo.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 180 50" width="180" height="50">
|
||||
<defs>
|
||||
<linearGradient id="sideGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
||||
<stop offset="0%" style="stop-color:#6366f1"/>
|
||||
<stop offset="100%" style="stop-color:#a855f7"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<!-- Mini shield -->
|
||||
<path d="M22 4 L38 11 L38 25 Q38 36 22 40 Q6 36 6 25 L6 11 Z"
|
||||
fill="url(#sideGrad)" opacity="0.2" stroke="url(#sideGrad)" stroke-width="1"/>
|
||||
<text x="22" y="28" font-family="monospace" font-size="17" font-weight="bold"
|
||||
fill="url(#sideGrad)" text-anchor="middle">S</text>
|
||||
<!-- Text -->
|
||||
<text x="52" y="24" font-family="monospace" font-size="18" font-weight="bold"
|
||||
fill="#f9fafb" letter-spacing="3">SETEC</text>
|
||||
<text x="52" y="38" font-family="monospace" font-size="8" fill="#6b7280"
|
||||
letter-spacing="1.5">APP MANAGER</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 899 B |
109
services/setec-manager/web/static/js/app.js
Normal file
109
services/setec-manager/web/static/js/app.js
Normal file
@@ -0,0 +1,109 @@
|
||||
/* Setec App Manager — Client JS */
|
||||
|
||||
// ── API helper ──
|
||||
const api = {
|
||||
async get(url) {
|
||||
const r = await fetch(url, { credentials: 'same-origin' });
|
||||
if (r.status === 401) { window.location.href = '/login'; return null; }
|
||||
if (!r.ok) throw new Error(`GET ${url}: ${r.status}`);
|
||||
return r.json();
|
||||
},
|
||||
async post(url, body) {
|
||||
const r = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: body ? JSON.stringify(body) : undefined
|
||||
});
|
||||
if (r.status === 401) { window.location.href = '/login'; return null; }
|
||||
if (!r.ok) throw new Error(`POST ${url}: ${r.status}`);
|
||||
return r.json();
|
||||
},
|
||||
async del(url) {
|
||||
const r = await fetch(url, { method: 'DELETE', credentials: 'same-origin' });
|
||||
if (r.status === 401) { window.location.href = '/login'; return null; }
|
||||
if (!r.ok) throw new Error(`DELETE ${url}: ${r.status}`);
|
||||
return r.json();
|
||||
}
|
||||
};
|
||||
|
||||
// ── Active nav highlight ──
|
||||
(function highlightNav() {
|
||||
const path = window.location.pathname;
|
||||
document.querySelectorAll('.nav-link').forEach(link => {
|
||||
const href = link.getAttribute('href');
|
||||
if (href === '/' && path === '/') {
|
||||
link.classList.add('active');
|
||||
} else if (href !== '/' && path.startsWith(href)) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
})();
|
||||
|
||||
// ── Dashboard auto-refresh ──
|
||||
(function dashboardRefresh() {
|
||||
if (window.location.pathname !== '/') return;
|
||||
|
||||
const INTERVAL = 10000; // 10 seconds
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
const data = await api.get('/api/stats');
|
||||
if (!data) return;
|
||||
|
||||
// Update stat card values if elements exist
|
||||
const updates = {
|
||||
cpuBar: { style: `width: ${data.cpu || 0}%` },
|
||||
memBar: { style: `width: ${data.mem_percent || 0}%` },
|
||||
diskBar: { style: `width: ${data.disk_percent || 0}%` },
|
||||
};
|
||||
|
||||
for (const [id, props] of Object.entries(updates)) {
|
||||
const el = document.getElementById(id);
|
||||
if (el && props.style) el.setAttribute('style', props.style);
|
||||
if (el && props.text) el.textContent = props.text;
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Stats refresh failed:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(refresh, INTERVAL);
|
||||
})();
|
||||
|
||||
// ── Monitor page auto-refresh ──
|
||||
(function monitorRefresh() {
|
||||
if (window.location.pathname !== '/monitor') return;
|
||||
|
||||
const INTERVAL = 5000;
|
||||
|
||||
async function refresh() {
|
||||
try {
|
||||
const data = await api.get('/api/stats');
|
||||
if (!data) return;
|
||||
|
||||
const bar = (id, pct) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.style.width = pct + '%';
|
||||
};
|
||||
const txt = (id, val) => {
|
||||
const el = document.getElementById(id);
|
||||
if (el) el.textContent = val;
|
||||
};
|
||||
|
||||
bar('cpuBar', data.cpu || 0);
|
||||
txt('cpuText', (data.cpu || 0).toFixed(1) + '%');
|
||||
bar('memBar', data.mem_percent || 0);
|
||||
bar('diskBar', data.disk_percent || 0);
|
||||
|
||||
if (data.mem_text) txt('memText', data.mem_text);
|
||||
if (data.disk_text) txt('diskText', data.disk_text);
|
||||
if (data.net_in) txt('netIn', data.net_in);
|
||||
if (data.net_out) txt('netOut', data.net_out);
|
||||
} catch (e) {
|
||||
console.warn('Monitor refresh failed:', e.message);
|
||||
}
|
||||
}
|
||||
|
||||
setInterval(refresh, INTERVAL);
|
||||
})();
|
||||
Reference in New Issue
Block a user