Refine dark theme readability and chart visibility across dashboard pages
This commit is contained in:
1 parent
b9da742b3d
commit
0475bf5aa5
20 files changed
+929
-320
No files matched your search
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"files": {
|
"files": {
|
||||||
"main.css": "/static/css/main.69110889.css",
|
"main.css": "/static/css/main.6034edfe.css",
|
||||||
"main.js": "/static/js/main.820bab7f.js",
|
"main.js": "/static/js/main.243399c3.js",
|
||||||
"static/js/453.d456ce70.chunk.js": "/static/js/453.d456ce70.chunk.js",
|
"static/js/453.d456ce70.chunk.js": "/static/js/453.d456ce70.chunk.js",
|
||||||
"index.html": "/index.html",
|
"index.html": "/index.html",
|
||||||
"main.69110889.css.map": "/static/css/main.69110889.css.map",
|
"main.6034edfe.css.map": "/static/css/main.6034edfe.css.map",
|
||||||
"main.820bab7f.js.map": "/static/js/main.820bab7f.js.map",
|
"main.243399c3.js.map": "/static/js/main.243399c3.js.map",
|
||||||
"453.d456ce70.chunk.js.map": "/static/js/453.d456ce70.chunk.js.map"
|
"453.d456ce70.chunk.js.map": "/static/js/453.d456ce70.chunk.js.map"
|
||||||
},
|
},
|
||||||
"entrypoints": [
|
"entrypoints": [
|
||||||
"static/css/main.69110889.css",
|
"static/css/main.6034edfe.css",
|
||||||
"static/js/main.820bab7f.js"
|
"static/js/main.243399c3.js"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -1 +1 @@
|
|||||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" type="image/png" href="/app-icon.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="SkyWatch - Infrastructure monitoring and alerting system"/><link rel="apple-touch-icon" href="/app-icon.png"/><link rel="manifest" href="/manifest.json"/><title>SkyWatch</title><script defer="defer" src="/static/js/main.820bab7f.js"></script><link href="/static/css/main.69110889.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" type="image/png" href="/app-icon.png"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="SkyWatch - Infrastructure monitoring and alerting system"/><link rel="apple-touch-icon" href="/app-icon.png"/><link rel="manifest" href="/manifest.json"/><title>SkyWatch</title><script defer="defer" src="/static/js/main.243399c3.js"></script><link href="/static/css/main.6034edfe.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#020617;color:#e2e8f0;font-family:Manrope,IBM Plex Sans,Segoe UI,sans-serif;margin:0}*{box-sizing:border-box}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.App{background:radial-gradient(circle at 15% 0,#1e40af33 0,#0000 42%),radial-gradient(circle at 95% 10%,#0f766e24 0,#0000 36%),#020617;min-height:100vh}.navbar{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);background:#0f172ad1;border-bottom:1px solid #334155;box-shadow:0 4px 18px #0f172a0f;color:#e2e8f0;padding:.85rem 2rem;position:-webkit-sticky;position:sticky;top:0;z-index:30}.navbar h1{font-size:1.5rem;letter-spacing:-.02em;margin:0}.navbar-inner{gap:1rem;justify-content:space-between;width:100%}.navbar-inner,.navbar-left{align-items:center;display:flex}.navbar-left{gap:1.5rem;min-width:0}.navbar-links{align-items:center;display:flex;flex-wrap:wrap;gap:.5rem}.nav-link{border-radius:7px;color:#cbd5e1;font-size:.92rem;font-weight:600;padding:.4rem .7rem;text-decoration:none;transition:background .2s,color .2s,box-shadow .2s}.nav-link:hover{background:#111827;box-shadow:inset 0 0 0 1px #334155;color:#f8fafc}.settings-link{align-items:center;display:inline-flex;gap:.4rem}.setup-badge{background:#facc15;border-radius:999px;color:#111827;font-size:.68rem;font-weight:700;padding:.15rem .5rem}.notifications-pill{align-items:center;background:#111827;border:1px solid #334155;border-radius:999px;color:#e2e8f0;display:flex;font-size:.82rem;gap:.45rem;padding:.3rem .7rem}.container{margin:0 auto;max-width:1400px;padding:2rem}.dashboard-toolbar{align-items:center;display:flex;gap:1rem;justify-content:space-between;margin-bottom:2rem}.dashboard-title{color:#f8fafc;font-size:1.85rem;letter-spacing:-.02em;margin:0}.dashboard-subtitle{color:#94a3b8;font-size:.9rem;margin:.2rem 0 0}.dashboard-actions{align-items:center;display:flex;flex-wrap:wrap;gap:.5rem}.quick-filter-row{display:flex;flex-wrap:wrap;gap:.45rem;margin:.2rem 0 .8rem}.quick-chip{background:#0f172a;border:1px solid #334155;border-radius:999px;color:#cbd5e1;cursor:pointer;font-size:.74rem;font-weight:700;letter-spacing:.02em;padding:.28rem .7rem}.quick-chip:hover{background:#111f34}.quick-chip.active{background:#14b8a6;border-color:#14b8a6;color:#052e2b}.tag-filter-input{background:#111827;border:1px solid #334155;border-radius:10px;color:#e2e8f0;min-width:180px;padding:.5rem .75rem}.actions-menu-wrapper{position:relative}.actions-menu{background:#fff;border:1px solid #dbe2ea;border-radius:8px;box-shadow:0 8px 20px #0f172a24;min-width:220px;padding:.35rem 0;position:absolute;right:0;top:110%;z-index:20}.actions-menu-divider{border:0;border-top:1px solid #eef2f6;margin:.25rem 0}.hidden-file-input{display:none}.toast-stack{position:fixed;right:20px;top:20px;z-index:9999}.toast-item{border:1px solid #0000;border-radius:8px;box-shadow:0 8px 20px #0f172a29;margin-bottom:10px;min-width:280px;padding:12px 16px}.toast-error{background:#fee2e2;border-color:#fecaca;color:#7f1d1d}.toast-success{background:#dcfce7;border-color:#bbf7d0;color:#14532d}.toast-info{background:#dbeafe;border-color:#bfdbfe;color:#1e3a8a}.cc-metrics-grid{grid-gap:.65rem;display:grid;gap:.65rem;grid-template-columns:repeat(6,minmax(0,1fr));margin-bottom:1rem}.cc-metric-card{background:linear-gradient(180deg,#0f172af5,#1e293bf5);border:1px solid #334155;border-radius:10px;color:#e2e8f0;padding:.75rem}.cc-metric-label{color:#94a3b8;font-size:.68rem;letter-spacing:.08em;margin:0;text-transform:uppercase}.cc-metric-value{font-size:1.2rem;font-weight:700;margin:.35rem 0 0}.metric-ok{border-color:#14532d}.metric-down{border-color:#7f1d1d}.metric-maint{border-color:#78350f}.cc-layout{grid-gap:.8rem;align-items:start;display:grid;gap:.8rem;grid-template-columns:minmax(0,1fr) 300px}.cc-main{min-width:0}.cc-rail{display:flex;flex-direction:column;gap:.75rem;position:-webkit-sticky;position:sticky;top:82px}.cc-panel{background:#0f172af0;border:1px solid #334155;border-radius:12px;color:#e2e8f0;padding:.75rem}.cc-panel h3{font-size:.9rem;letter-spacing:.03em;margin:0 0 .65rem}.cc-empty{color:#94a3b8;font-size:.85rem;margin:0}.cc-group-list,.cc-incident-list{display:flex;flex-direction:column;gap:.4rem}.cc-group-item,.cc-incident-item{align-items:center;background:#0f172a;border:1px solid #334155;border-radius:8px;color:#e2e8f0;cursor:pointer;display:flex;justify-content:space-between;padding:.45rem .55rem;width:100%}.cc-group-item:hover,.cc-incident-item:hover{background:#111f34;border-color:#475569}.cc-incident-name{font-size:.85rem;font-weight:600;margin-right:.5rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dashboard-grid{grid-gap:1rem;display:grid;gap:1rem;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));margin-bottom:1.5rem}.resource-list{display:flex;flex-direction:column;gap:.45rem}.resource-list-shell{background:#0f172ae6;border:1px solid #334155;border-radius:12px;max-height:calc(100vhLine truncated
|
||||||
|
/*# sourceMappingURL=main.6034edfe.css.map*/
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"static/css/main.6034edfe.css","mappings":"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 truncated
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-color:#f8fafc;color:#0f172a;font-family:Manrope,IBM Plex Sans,Segoe UI,sans-serif;margin:0}*{box-sizing:border-box}code{font-family:source-code-pro,Menlo,Monaco,Consolas,Courier New,monospace}.App{background:radial-gradient(circle at 15% 0,#dff7f2 0,#0000 40%),radial-gradient(circle at 95% 10%,#e8eefc 0,#0000 38%),#f8fafc;min-height:100vh}.navbar{backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);background:#ffffffd9;border-bottom:1px solid #dbe2ea;box-shadow:0 4px 18px #0f172a0f;color:#0f172a;padding:.85rem 2rem;position:-webkit-sticky;position:sticky;top:0;z-index:30}.navbar h1{font-size:1.5rem;letter-spacing:-.02em;margin:0}.navbar-inner{gap:1rem;justify-content:space-between;width:100%}.navbar-inner,.navbar-left{align-items:center;display:flex}.navbar-left{gap:1.5rem;min-width:0}.navbar-links{align-items:center;display:flex;flex-wrap:wrap;gap:.5rem}.nav-link{border-radius:7px;color:#475569;font-size:.92rem;font-weight:600;padding:.4rem .7rem;text-decoration:none;transition:background .2s,color .2s,box-shadow .2s}.nav-link:hover{background:#fff;box-shadow:inset 0 0 0 1px #dbe2ea;color:#0f172a}.settings-link{align-items:center;display:inline-flex;gap:.4rem}.setup-badge{background:#facc15;border-radius:999px;color:#111827;font-size:.68rem;font-weight:700;padding:.15rem .5rem}.notifications-pill{align-items:center;background:#fff;border:1px solid #dbe2ea;border-radius:999px;color:#334155;display:flex;font-size:.82rem;gap:.45rem;padding:.3rem .7rem}.container{margin:0 auto;max-width:1400px;padding:2rem}.dashboard-toolbar{align-items:center;display:flex;gap:1rem;justify-content:space-between;margin-bottom:2rem}.dashboard-title{font-size:1.85rem;letter-spacing:-.02em;margin:0}.dashboard-subtitle{color:#64748b;font-size:.9rem;margin:.2rem 0 0}.dashboard-actions{align-items:center;display:flex;flex-wrap:wrap;gap:.5rem}.tag-filter-input{background:#ffffffe6;border:1px solid #cbd5e1;border-radius:10px;min-width:180px;padding:.5rem .75rem}.actions-menu-wrapper{position:relative}.actions-menu{background:#fff;border:1px solid #dbe2ea;border-radius:8px;box-shadow:0 8px 20px #0f172a24;min-width:220px;padding:.35rem 0;position:absolute;right:0;top:110%;z-index:20}.actions-menu-divider{border:0;border-top:1px solid #eef2f6;margin:.25rem 0}.hidden-file-input{display:none}.toast-stack{position:fixed;right:20px;top:20px;z-index:9999}.toast-item{border:1px solid #0000;border-radius:8px;box-shadow:0 8px 20px #0f172a29;margin-bottom:10px;min-width:280px;padding:12px 16px}.toast-error{background:#fee2e2;border-color:#fecaca;color:#7f1d1d}.toast-success{background:#dcfce7;border-color:#bbf7d0;color:#14532d}.toast-info{background:#dbeafe;border-color:#bfdbfe;color:#1e3a8a}.cc-metrics-grid{grid-gap:.65rem;display:grid;gap:.65rem;grid-template-columns:repeat(6,minmax(0,1fr));margin-bottom:1rem}.cc-metric-card{background:linear-gradient(180deg,#0f172af5,#1e293bf5);border:1px solid #334155;border-radius:10px;color:#e2e8f0;padding:.75rem}.cc-metric-label{color:#94a3b8;font-size:.68rem;letter-spacing:.08em;margin:0;text-transform:uppercase}.cc-metric-value{font-size:1.2rem;font-weight:700;margin:.35rem 0 0}.metric-ok{border-color:#14532d}.metric-down{border-color:#7f1d1d}.metric-maint{border-color:#78350f}.cc-layout{grid-gap:.8rem;align-items:start;display:grid;gap:.8rem;grid-template-columns:minmax(0,1fr) 300px}.cc-main{min-width:0}.cc-rail{display:flex;flex-direction:column;gap:.75rem;position:-webkit-sticky;position:sticky;top:82px}.cc-panel{background:#0f172af0;border:1px solid #334155;border-radius:12px;color:#e2e8f0;padding:.75rem}.cc-panel h3{font-size:.9rem;letter-spacing:.03em;margin:0 0 .65rem}.cc-empty{color:#94a3b8;font-size:.85rem;margin:0}.cc-group-list,.cc-incident-list{display:flex;flex-direction:column;gap:.4rem}.cc-group-item,.cc-incident-item{align-items:center;background:#0f172a;border:1px solid #334155;border-radius:8px;color:#e2e8f0;cursor:pointer;display:flex;justify-content:space-between;padding:.45rem .55rem;width:100%}.cc-group-item:hover,.cc-incident-item:hover{background:#111f34;border-color:#475569}.cc-incident-name{font-size:.85rem;font-weight:600;margin-right:.5rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dashboard-grid{grid-gap:1rem;display:grid;gap:1rem;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));margin-bottom:1.5rem}.resource-list{display:flex;flex-direction:column;gap:.45rem}.resource-list-shell{background:#ffffffd1;border:1px solid #cbd5e1;border-radius:12px;padding:.65rem}.resource-list-header{grid-gap:1rem;border-bottom:1px solid #e2e8f0;color:#64748b;display:grid;font-size:.72rem;gap:1rem;grid-template-columns:minmax(260px,2fr) minmax(220px,1.25fr) minmax(170px,.8fr);letter-spacing:.08em;margin-bottom:.45rem;padding:.35rem .6rem .6rem;text-transform:uppercase}.resource-list-count{color:#64748b;font-size:.75rem;margin:0 .6rem .55rem}.resource-row{grid-gap:1rem;align-items:cLine truncated
|
|
||||||
/*# sourceMappingURL=main.69110889.css.map*/
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"static/css/main.69110889.css","mappings":"AAAA,KAGE,kCAAmC,CACnC,iCAAkC,CAElC,wBAAyB,CADzB,aAAc,CAHd,qDAA+D,CAD/D,QAMF,CAEA,EACE,qBACF,CAEA,KACE,uEAEF,CChBA,KAEE,8HAGS,CAJT,gBAKF,CAEA,QAEE,yBAA0B,CAC1B,iCAAkC,CAFlC,oBAAqC,CAKrC,+BAAgC,CAChC,+BAA6C,CAF7C,aAAc,CADd,mBAAqB,CAIrB,uBAAgB,CAAhB,eAAgB,CAChB,KAAM,CACN,UACF,CAEA,WAEE,gBAAiB,CACjB,qBAAuB,CAFvB,QAGF,CAEA,cAIE,QAAS,CADT,6BAA8B,CAE9B,UACF,CAEA,2BANE,kBAAmB,CADnB,YAYF,CALA,aAGE,UAAW,CACX,WACF,CAEA,cAGE,kBAAmB,CAFnB,YAAa,CAGb,cAAe,CAFf,SAGF,CAEA,UAME,iBAAkB,CALlB,aAAc,CAGd,gBAAkB,CADlB,eAAgB,CAEhB,mBAAsB,CAHtB,oBAAqB,CAKrB,kDACF,CAEA,gBACE,eAAmB,CAEnB,kCAAmC,CADnC,aAEF,CAEA,eAEE,kBAAmB,CADnB,mBAAoB,CAEpB,SACF,CAEA,aACE,kBAAmB,CAGnB,mBAAoB,CAFpB,aAAc,CAGd,gBAAkB,CAClB,eAAgB,CAHhB,oBAIF,CAEA,oBAEE,kBAAmB,CAInB,eAAmB,CACnB,wBAAyB,CACzB,mBAAoB,CAHpB,aAAc,CAJd,YAAa,CAGb,gBAAkB,CADlB,UAAY,CAMZ,mBACF,CAEA,WAEE,aAAc,CADd,gBAAiB,CAEjB,YACF,CAEA,mBAGE,kBAAmB,CAFnB,YAAa,CAGb,QAAS,CAFT,6BAA8B,CAG9B,kBACF,CAEA,iBAEE,iBAAkB,CAClB,qBAAuB,CAFvB,QAGF,CAEA,oBAEE,aAAc,CACd,eAAiB,CAFjB,gBAGF,CAEA,mBAIE,kBAAmB,CAHnB,YAAa,CAEb,cAAe,CADf,SAGF,CAEA,kBAKE,oBAAoC,CAFpC,wBAAyB,CADzB,kBAAmB,CAEnB,eAAgB,CAHhB,oBAKF,CAEA,sBACE,iBACF,CAEA,cAIE,eAAmB,CACnB,wBAAyB,CACzB,iBAAkB,CAClB,+BAA6C,CAC7C,eAAgB,CAChB,gBAAkB,CARlB,iBAAkB,CAClB,OAAQ,CACR,QAAS,CAOT,UACF,CAEA,sBAEE,QAAS,CACT,4BAA6B,CAF7B,eAGF,CAEA,mBACE,YACF,CAEA,aACE,cAAe,CAEf,UAAW,CADX,QAAS,CAET,YACF,CAEA,YAME,sBAA6B,CAH7B,iBAAkB,CAElB,+BAA6C,CAJ7C,kBAAmB,CAGnB,eAAgB,CAFhB,iBAKF,CAEA,aACE,kBAAmB,CAEnB,oBAAqB,CADrB,aAEF,CAEA,eACE,kBAAmB,CAEnB,oBAAqB,CADrB,aAEF,CAEA,YACE,kBAAmB,CAEnB,oBAAqB,CADrB,aAEF,CAEA,iBAGE,eAAY,CAFZ,YAAa,CAEb,UAAY,CADZ,6CAAgD,CAEhD,kBACF,CAEA,gBACE,sDAA2F,CAC3F,wBAAyB,CACzB,kBAAmB,CAEnB,aAAc,CADd,cAEF,CAEA,iBAKE,aAAc,CAHd,gBAAkB,CAElB,oBAAsB,CAHtB,QAAS,CAET,wBAGF,CAEA,iBAEE,gBAAiB,CACjB,eAAgB,CAFhB,iBAGF,CAEA,WACE,oBACF,CAEA,aACE,oBACF,CAEA,cACE,oBACF,CAEA,WAGE,cAAW,CACX,iBAAkB,CAHlB,YAAa,CAEb,SAAW,CADX,yCAGF,CAEA,SACE,WACF,CAEA,SACE,YAAa,CACb,qBAAsB,CACtB,UAAY,CACZ,uBAAgB,CAAhB,eAAgB,CAChB,QACF,CAEA,UACE,oBAAkC,CAElC,wBAAyB,CACzB,kBAAmB,CAFnB,aAAc,CAGd,cACF,CAEA,aAEE,eAAiB,CACjB,oBAAsB,CAFtB,iBAGF,CAEA,UAEE,aAAc,CACd,gBAAkB,CAFlB,QAGF,CAEA,iCAEE,YAAa,CACb,qBAAsB,CACtB,SACF,CAEA,iCAUE,kBAAmB,CAPnB,kBAAmB,CACnB,wBAAyB,CAEzB,iBAAkB,CADlB,aAAc,CAMd,cAAe,CAHf,YAAa,CACb,6BAA8B,CAF9B,qBAAwB,CALxB,UAUF,CAEA,6CAEE,kBAAmB,CACnB,oBACF,CAEA,kBACE,gBAAkB,CAClB,eAAgB,CAIhB,kBAAoB,CAFpB,eAAgB,CAChB,sBAAuB,CAFvB,kBAIF,CAEA,gBAGE,aAAS,CAFT,YAAa,CAEb,QAAS,CADT,yDAA4D,CAE5D,oBACF,CAEA,eACE,YAAa,CACb,qBAAsB,CACtB,UACF,CAEA,qBACE,oBAAqC,CACrC,wBAAyB,CACzB,kBAAmB,CACnB,cACF,CAEA,sBAGE,aAAS,CAMT,+BAAgC,CADhC,aAAc,CAPd,YAAa,CAIb,gBAAkB,CAFlB,QAAS,CADT,+EAAoF,CAKpF,oBAAsB,CAGtB,oBAAsB,CANtB,0BAA8B,CAE9B,wBAKF,CAEA,qBAEE,aAAc,CADd,gBAAkB,CAElB,qBACF,CAEA,cAIE,aAAS,CADT,kBAAmB,CAGnB,oBAAqC,CACrC,wBAAyB,CACzB,kBAAmB,CACnB,+BAA6C,CAC7C,cAAe,CATf,YAAa,CAGb,QAAS,CAFT,+EAAoF,CAGpF,oBAAuB,CAMvB,wDACF,CAEA,oBAEE,oBAAqB,CACrB,+BAA4C,CAF5C,0BAGF,CAEA,UACE,WACF,CAEA,gBAEE,kBAAmB,CADnB,YAAa,CAGb,cAAe,CADf,SAAW,CAEX,oBACF,CAEA,UAEE,gBAAkB,CADlB,QAEF,CAEA,qBAGE,kBAAmB,CADnB,mBAAoB,CAEpB,aAAc,CACd,gBAAkB,CAClB,eAAgB,CALhB,oBAMF,CAEA,iBAGE,kBAAmB,CACnB,wBAAyB,CAEzB,mBAAoB,CAJpB,aAKF,CAEA,qCARE,gBAAkB,CAIlB,qBAWF,CAPA,oBAGE,kBAAmB,CACnB,wBAAyB,CAEzB,mBAAoB,CAJpB,aAKF,CAEA,mBAEE,QACF,CAEA,SAIE,gBAAkB,CAFlB,eAAgB,CAChB,sBAAuB,CAFvB,kBAIF,CAEA,UACE,gBACF,CAEA,aAGE,cAAW,CACX,kBAAmB,CAHnB,YAAa,CAEb,SAAW,CADX,6CAGF,CAEA,yBACE,gBACF,CAEA,eACE,cACF,CAEA,UAGE,oBAAqB,CAFrB,YAAa,CACb,qBAAsB,CAEtB,SACF,CAEA,aACE,YAAa,CACb,UACF,CAEA,eACE,+CAA6D,CAM7D,wBAAyB,CALzB,kBAAmB,CAEnB,+BAA6C,CAE7C,cAAe,CAHf,mBAAoB,CAEpB,uCAGF,CAEA,uBACE,YAAa,CACb,qBAAsB,CACtB,SACF,CAEA,qBAEE,gCAA8C,CAD9C,0BAEF,CAEA,iBAGE,sBAAuB,CAFvB,YAAa,CACb,6BAA8B,CAE9B,mBACF,CAEA,eACE,iBAAkB,CAClB,eAAgB,CAChB,gBACF,CAEA,cACE,aAAc,CACd,eAAiB,CACjB,oBACF,CAEA,eAEE,aAAc,CACd,gBAAkB,CAFlB,iBAGF,CAEA,cAEE,kBAAmB,CACnB,eAAiB,CACjB,eAAgB,CAChB,wBACF,CAEA,WACE,kBAAmB,CACnB,aACF,CAEA,aACE,kBAAmB,CACnB,aACF,CAEA,gBACE,kBAAmB,CACnB,aACF,CAEA,YAGE,aAAS,CAFT,YAAa,CAEb,QAAS,CADT,6BAA8B,CAE9B,eACF,CAEA,MACE,iBACF,CAEA,YAGE,UAAW,CAFX,gBAAiB,CACjB,eAAgB,CAEhB,QACF,CAEA,kBACE,iBACF,CAEA,YAEE,UAAW,CADX,gBAAkB,CAGlB,QAAS,CADT,wBAEF,CAEA,eAIE,eAAY,CADZ,kBAAmB,CAFnB,YAAa,CAGb,UAAY,CAFZ,mCAGF,CAEA,WAEE,WAAY,CADZ,UAEF,CAEA,YAGE,UAAW,CADX,gBAAkB,CADlB,QAGF,CAEA,eAEE,oBAAoC,CACpC,wBAAyB,CACzB,kBAAmB,CAHnB,kBAAmB,CAInB,YACF,CAEA,cAEE,kBAAmB,CAGnB,eAAiB,CAIjB,wBAAyB,CAHzB,kBAAmB,CACnB,cAAe,CANf,YAAa,CAEb,QAAS,CAOT,kBAAmB,CANnB,YAAa,CAIb,yBAGF,CAEA,oBACE,kBACF,CAEA,iBAGE,UAAW,CAFX,gBAAiB,CACjB,QAEF,CAEA,YAEE,QAAO,CADP,QAEF,CAEA,cAEE,aAAc,CADd,eAAiB,CAEjB,cACF,CAEA,aACE,YAAa,CAGb,gBAAkB,CAFlB,UAAW,CACX,gBAEF,CAEA,YACE,kBAAmB,CACnB,wBAAyB,CAEzB,mBAAoB,CACpB,aAAc,CACd,eAAgB,CAHhB,mBAIF,CAEA,YAEE,wBAAyB,CACzB,iBAAkB,CAClB,iBAAkB,CAElB,mBAAqB,CALrB,aAAe,CAIf,UAEF,CAEA,gBAGE,yBAA0B,CAF1B,iBAAmB,CACnB,kBAEF,CAEA,YACE,mBACF,CAEA,kBAGE,UAAW,CAFX,aAAc,CACd,gBAAkB,CAGlB,eAAgB,CADhB,oBAEF,CAEA,kBAGE,qBAAsB,CACtB,iBAAkB,CACLine truncated
|
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
/*! For license information please see main.820bab7f.js.LICENSE.txt */
|
/*! For license information please see main.243399c3.js.LICENSE.txt */
|
||||||
(()=>{var e={356:(e,t,n)=>{e.exports=n(9645).last},418:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.toString=function e(t){if(null==t)return"";if("string"===typeof t)return t;if(Array.isArray(t))return t.map(e).join(",");const n=String(t);return"0"===n&&Object.is(Number(t),-0)?"-0":n}},579:(e,t,n)=>{"use strict";e.exports=n(2799)},870:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isObject=function(e){return null!==e&&("object"===typeof e||"function"===typeof e)}},1203:(e,t,n)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});const r=n(8420);t.isArrayLike=function(e){return null!=e&&"function"!==typeof e&&r.isLength(e.length)}},1293:(e,t,n)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});const r=n(4830);t.debounce=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};"object"!==typeof n&&(n={});const{leading:a=!1,trailing:i=!0,maxWait:o}=n,l=Array(2);let s;a&&(l[0]="leading"),i&&(l[1]="trailing");let c=null;const u=r.debounce(function(){for(var t=arguments.length,n=new Array(t),r=0;r<t;r++)n[r]=arguments[r];s=e.apply(this,n),c=null},t,{edges:l}),d=function(){for(var t=arguments.length,n=new Array(t),r=0;r<t;r++)n[r]=arguments[r];return null!=o&&(null===c&&(c=Date.now()),Date.now()-c>=o)?(s=e.apply(this,n),c=Date.now(),u.cancel(),u.schedule(),s):(u.apply(this,n),s)};return d.cancel=u.cancel,d.flush=()=>(u.flush(),s),d}},1444:(e,t,n)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});const r=n(418),a=n(3272);t.toPath=function(e){if(Array.isArray(e))return e.map(a.toKey);if("symbol"===typeof e)return[e];const t=[],n=(e=r.toString(e)).length;if(0===n)return t;let i=0,o="",l="",s=!1;for(46===e.charCodeAt(0)&&(t.push(""),i++);i<n;){const r=e[i];l?"\\"===r&&i+1<n?(i++,o+=e[i]):r===l?l="":o+=r:s?'"'===r||"'"===r?l=r:"]"===r?(s=!1,t.push(o),o=""):o+=r:"["===r?(s=!0,o&&(t.push(o),o="")):"."===r?o&&(t.push(o),o=""):o+=r,i++}return o&&t.push(o),t}},2086:(e,t,n)=>{"use strict";e.exports=n(5082)},2104:(e,t,n)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});const r=n(1203),a=n(9921);t.isArrayLikeObject=function(e){return a.isObjectLike(e)&&r.isArrayLike(e)}},2132:(e,t,n)=>{"use strict";var r=n(2897).default;Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});const a=n(870),i=n(4803),o=n(9972);function l(e,t,n,u){if(t===e)return!0;switch(typeof t){case"object":return function(e,t,n,r){if(null==t)return!0;if(Array.isArray(t))return s(e,t,n,r);if(t instanceof Map)return function(e,t,n,r){if(0===t.size)return!0;if(!(e instanceof Map))return!1;for(const[a,i]of t.entries()){if(!1===n(e.get(a),i,a,e,t,r))return!1}return!0}(e,t,n,r);if(t instanceof Set)return c(e,t,n,r);const a=Object.keys(t);if(null==e)return 0===a.length;if(0===a.length)return!0;if(null!==r&&void 0!==r&&r.has(t))return r.get(t)===e;null===r||void 0===r||r.set(t,e);try{for(let o=0;o<a.length;o++){const l=a[o];if(!i.isPrimitive(e)&&!(l in e))return!1;if(void 0===t[l]&&void 0!==e[l])return!1;if(null===t[l]&&null!==e[l])return!1;if(!n(e[l],t[l],l,e,t,r))return!1}return!0}finally{null===r||void 0===r||r.delete(t)}}(e,t,n,u);case"function":return Object.keys(t).length>0?l(e,r({},t),n,u):o.eq(e,t);default:return a.isObject(e)?"string"!==typeof t||""===t:o.eq(e,t)}}function s(e,t,n,r){if(0===t.length)return!0;if(!Array.isArray(e))return!1;const a=new Set;for(let i=0;i<t.length;i++){const o=t[i];let l=!1;for(let s=0;s<e.length;s++){if(a.has(s))continue;let c=!1;if(n(e[s],o,i,e,t,r)&&(c=!0),c){a.add(s),l=!0;break}}if(!l)return!1}return!0}function c(e,t,n,r){return 0===t.size||e instanceof Set&&s([...e],[...t],n,r)}t.isMatchWith=function e(t,n,r){return"function"!==typeof r?e(t,n,()=>{}):l(t,n,function e(t,n,a,i,o,s){const c=r(t,n,a,i,o,s);return void 0!==c?Boolean(c):l(t,n,e,s)},new Map)},t.isSetMatch=c},2204:(e,t,n)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});const r=n(5316),a=n(7312),i=n(3799);t.sortBy=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),o=1;o<t;o++)n[o-1]=arguments[o];const l=n.length;return l>1&&i.isIterateeCall(e,n[0],n[1])?n=[]:l>2&&i.isIterateeCall(n[0],n[1],n[2])&&(n=[n[0]]),r.orderBy(e,a.flatten(n),["asc"])}},2330:(e,t,n)=>{"use strict";var r=n(5043);var a="function"===typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e===1/t)||e!==e&&t!==t},i=r.useState,o=r.useEffect,l=r.useLayoutEffect,s=r.useDebugValue;function c(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!a(e,n)}catch(r){return!0}}var u="undefined"===typeof window||"undefined"===typeof window.document||"undefined"===typeof window.document.createElement?function(e,t){return t()}:function(e,t){var n=t(),r=i({inst:{value:n,getSnapshot:t}}),a=r[0].inst,u=r[1];return l(function(){a.value=n,a.getSnapshot=t,c(a)&&u({inst:a})},[e,n,t]),o(function(){return c(a)&&u({iLine truncated
|
(()=>{var e={356:(e,t,n)=>{e.exports=n(9645).last},418:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.toString=function e(t){if(null==t)return"";if("string"===typeof t)return t;if(Array.isArray(t))return t.map(e).join(",");const n=String(t);return"0"===n&&Object.is(Number(t),-0)?"-0":n}},579:(e,t,n)=>{"use strict";e.exports=n(2799)},870:(e,t)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),t.isObject=function(e){return null!==e&&("object"===typeof e||"function"===typeof e)}},1203:(e,t,n)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});const r=n(8420);t.isArrayLike=function(e){return null!=e&&"function"!==typeof e&&r.isLength(e.length)}},1293:(e,t,n)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});const r=n(4830);t.debounce=function(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};"object"!==typeof n&&(n={});const{leading:a=!1,trailing:i=!0,maxWait:o}=n,l=Array(2);let s;a&&(l[0]="leading"),i&&(l[1]="trailing");let c=null;const u=r.debounce(function(){for(var t=arguments.length,n=new Array(t),r=0;r<t;r++)n[r]=arguments[r];s=e.apply(this,n),c=null},t,{edges:l}),d=function(){for(var t=arguments.length,n=new Array(t),r=0;r<t;r++)n[r]=arguments[r];return null!=o&&(null===c&&(c=Date.now()),Date.now()-c>=o)?(s=e.apply(this,n),c=Date.now(),u.cancel(),u.schedule(),s):(u.apply(this,n),s)};return d.cancel=u.cancel,d.flush=()=>(u.flush(),s),d}},1444:(e,t,n)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});const r=n(418),a=n(3272);t.toPath=function(e){if(Array.isArray(e))return e.map(a.toKey);if("symbol"===typeof e)return[e];const t=[],n=(e=r.toString(e)).length;if(0===n)return t;let i=0,o="",l="",s=!1;for(46===e.charCodeAt(0)&&(t.push(""),i++);i<n;){const r=e[i];l?"\\"===r&&i+1<n?(i++,o+=e[i]):r===l?l="":o+=r:s?'"'===r||"'"===r?l=r:"]"===r?(s=!1,t.push(o),o=""):o+=r:"["===r?(s=!0,o&&(t.push(o),o="")):"."===r?o&&(t.push(o),o=""):o+=r,i++}return o&&t.push(o),t}},2086:(e,t,n)=>{"use strict";e.exports=n(5082)},2104:(e,t,n)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});const r=n(1203),a=n(9921);t.isArrayLikeObject=function(e){return a.isObjectLike(e)&&r.isArrayLike(e)}},2132:(e,t,n)=>{"use strict";var r=n(2897).default;Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});const a=n(870),i=n(4803),o=n(9972);function l(e,t,n,u){if(t===e)return!0;switch(typeof t){case"object":return function(e,t,n,r){if(null==t)return!0;if(Array.isArray(t))return s(e,t,n,r);if(t instanceof Map)return function(e,t,n,r){if(0===t.size)return!0;if(!(e instanceof Map))return!1;for(const[a,i]of t.entries()){if(!1===n(e.get(a),i,a,e,t,r))return!1}return!0}(e,t,n,r);if(t instanceof Set)return c(e,t,n,r);const a=Object.keys(t);if(null==e)return 0===a.length;if(0===a.length)return!0;if(null!==r&&void 0!==r&&r.has(t))return r.get(t)===e;null===r||void 0===r||r.set(t,e);try{for(let o=0;o<a.length;o++){const l=a[o];if(!i.isPrimitive(e)&&!(l in e))return!1;if(void 0===t[l]&&void 0!==e[l])return!1;if(null===t[l]&&null!==e[l])return!1;if(!n(e[l],t[l],l,e,t,r))return!1}return!0}finally{null===r||void 0===r||r.delete(t)}}(e,t,n,u);case"function":return Object.keys(t).length>0?l(e,r({},t),n,u):o.eq(e,t);default:return a.isObject(e)?"string"!==typeof t||""===t:o.eq(e,t)}}function s(e,t,n,r){if(0===t.length)return!0;if(!Array.isArray(e))return!1;const a=new Set;for(let i=0;i<t.length;i++){const o=t[i];let l=!1;for(let s=0;s<e.length;s++){if(a.has(s))continue;let c=!1;if(n(e[s],o,i,e,t,r)&&(c=!0),c){a.add(s),l=!0;break}}if(!l)return!1}return!0}function c(e,t,n,r){return 0===t.size||e instanceof Set&&s([...e],[...t],n,r)}t.isMatchWith=function e(t,n,r){return"function"!==typeof r?e(t,n,()=>{}):l(t,n,function e(t,n,a,i,o,s){const c=r(t,n,a,i,o,s);return void 0!==c?Boolean(c):l(t,n,e,s)},new Map)},t.isSetMatch=c},2204:(e,t,n)=>{"use strict";Object.defineProperty(t,Symbol.toStringTag,{value:"Module"});const r=n(5316),a=n(7312),i=n(3799);t.sortBy=function(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),o=1;o<t;o++)n[o-1]=arguments[o];const l=n.length;return l>1&&i.isIterateeCall(e,n[0],n[1])?n=[]:l>2&&i.isIterateeCall(n[0],n[1],n[2])&&(n=[n[0]]),r.orderBy(e,a.flatten(n),["asc"])}},2330:(e,t,n)=>{"use strict";var r=n(5043);var a="function"===typeof Object.is?Object.is:function(e,t){return e===t&&(0!==e||1/e===1/t)||e!==e&&t!==t},i=r.useState,o=r.useEffect,l=r.useLayoutEffect,s=r.useDebugValue;function c(e){var t=e.getSnapshot;e=e.value;try{var n=t();return!a(e,n)}catch(r){return!0}}var u="undefined"===typeof window||"undefined"===typeof window.document||"undefined"===typeof window.document.createElement?function(e,t){return t()}:function(e,t){var n=t(),r=i({inst:{value:n,getSnapshot:t}}),a=r[0].inst,u=r[1];return l(function(){a.value=n,a.getSnapshot=t,c(a)&&u({inst:a})},[e,n,t]),o(function(){return c(a)&&u({iLine truncated
|
||||||
//# sourceMappingURL=main.820bab7f.js.map
|
//# sourceMappingURL=main.243399c3.js.map
|
||||||
File renamed without changes.
@@ -0,0 +1 @@
|
|||||||
|
{"version":3,"file":"static/js/main.243399c3.js","mappings":"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 truncated
|
||||||
@@ -1 +0,0 @@
|
|||||||
{"version":3,"file":"static/js/main.820bab7f.js","mappings":";2BAAAA,EAAOC,QAAU,EAAjBD,MAAAA,I,2BCEAE,OAAOC,eAAeF,EAASG,OAAOC,YAAa,CAAEC,MAAO,WAmB5DL,EAAQM,SAjBR,SAASA,EAASD,GACd,GAAa,MAATA,EACA,MAAO,GAEX,GAAqB,kBAAVA,EACP,OAAOA,EAEX,GAAIE,MAAMC,QAAQH,GACd,OAAOA,EAAMI,IAAIH,GAAUI,KAAK,KAEpC,MAAMC,EAASC,OAAOP,GACtB,MAAe,MAAXM,GAAkBV,OAAOY,GAAGC,OAAOT,IAAS,GACrC,KAEJM,CACX,C,6BChBEZ,EAAOC,QAAU,EAAjBD,K,2BCDFE,OAAOC,eAAeF,EAASG,OAAOC,YAAa,CAAEC,MAAO,WAM5DL,EAAQe,SAJR,SAAkBV,GACd,OAAiB,OAAVA,IAAoC,kBAAVA,GAAuC,oBAAVA,EAClE,C,8BCJAJ,OAAOC,eAAeF,EAASG,OAAOC,YAAa,CAAEC,MAAO,WAE5D,MAAMW,EAAWC,EAAQ,MAMzBjB,EAAQkB,YAJR,SAAqBb,GACjB,OAAgB,MAATA,GAAkC,oBAAVA,GAAwBW,EAASA,SAASX,EAAMc,OACnF,C,8BCNAlB,OAAOC,eAAeF,EAASG,OAAOC,YAAa,CAAEC,MAAO,WAE5D,MAAMe,EAAaH,EAAQ,MA6C3BjB,EAAQqB,SA3CR,SAAkBC,GAAoC,IAA9BC,EAAUC,UAAAL,OAAA,QAAAM,IAAAD,UAAA,GAAAA,UAAA,GAAG,EAAGE,EAAOF,UAAAL,OAAA,QAAAM,IAAAD,UAAA,GAAAA,UAAA,GAAG,CAAC,EACxB,kBAAZE,IACPA,EAAU,CAAC,GAEf,MAAM,QAAEC,GAAU,EAAK,SAAEC,GAAW,EAAI,QAAEC,GAAYH,EAChDI,EAAQvB,MAAM,GAOpB,IAAII,EANAgB,IACAG,EAAM,GAAK,WAEXF,IACAE,EAAM,GAAK,YAGf,IAAIC,EAAY,KAChB,MAAMC,EAAaZ,EAAWC,SAAS,WAAmB,QAAAY,EAAAT,UAAAL,OAANe,EAAI,IAAA3B,MAAA0B,GAAAE,EAAA,EAAAA,EAAAF,EAAAE,IAAJD,EAAIC,GAAAX,UAAAW,GACpDxB,EAASW,EAAKc,MAAMC,KAAMH,GAC1BH,EAAY,IAChB,EAAGR,EAAY,CAAEO,UACXQ,EAAY,WAAmB,QAAAC,EAAAf,UAAAL,OAANe,EAAI,IAAA3B,MAAAgC,GAAAC,EAAA,EAAAA,EAAAD,EAAAC,IAAJN,EAAIM,GAAAhB,UAAAgB,GAC/B,OAAe,MAAXX,IACkB,OAAdE,IACAA,EAAYU,KAAKC,OAEjBD,KAAKC,MAAQX,GAAaF,IAC1BlB,EAASW,EAAKc,MAAMC,KAAMH,GAC1BH,EAAYU,KAAKC,MACjBV,EAAWW,SACXX,EAAWY,WACJjC,IAGfqB,EAAWI,MAAMC,KAAMH,GAChBvB,EACX,EAOA,OAFA2B,EAAUK,OAASX,EAAWW,OAC9BL,EAAUO,MALIA,KACVb,EAAWa,QACJlC,GAIJ2B,CACX,C,8BC7CArC,OAAOC,eAAeF,EAASG,OAAOC,YAAa,CAAEC,MAAO,WAE5D,MAAMC,EAAWW,EAAQ,KACnB6B,EAAQ7B,EAAQ,MA4EtBjB,EAAQ+C,OA1ER,SAAgBC,GACZ,GAAIzC,MAAMC,QAAQwC,GACd,OAAOA,EAAQvC,IAAIqC,EAAMA,OAE7B,GAAuB,kBAAZE,EACP,MAAO,CAACA,GAGZ,MAAMrC,EAAS,GACTQ,GAFN6B,EAAU1C,EAASA,SAAS0C,IAEL7B,OACvB,GAAe,IAAXA,EACA,OAAOR,EAEX,IAAIsC,EAAQ,EACRC,EAAM,GACNC,EAAY,GACZC,GAAU,EAKd,IAJ8B,KAA1BJ,EAAQK,WAAW,KACnB1C,EAAO2C,KAAK,IACZL,KAEGA,EAAQ9B,GAAQ,CACnB,MAAMoC,EAAOP,EAAQC,GACjBE,EACa,OAATI,GAAiBN,EAAQ,EAAI9B,GAC7B8B,IACAC,GAAOF,EAAQC,IAEVM,IAASJ,EACdA,EAAY,GAGZD,GAAOK,EAGNH,EACQ,MAATG,GAAyB,MAATA,EAChBJ,EAAYI,EAEE,MAATA,GACLH,GAAU,EACVzC,EAAO2C,KAAKJ,GACZA,EAAM,IAGNA,GAAOK,EAIE,MAATA,GACAH,GAAU,EACNF,IACAvC,EAAO2C,KAAKJ,GACZA,EAAM,KAGI,MAATK,EACDL,IACAvC,EAAO2C,KAAKJ,GACZA,EAAM,IAIVA,GAAOK,EAGfN,GACJ,CAIA,OAHIC,GACAvC,EAAO2C,KAAKJ,GAETvC,CACX,C,8BC5EEZ,EAAOC,QAAU,EAAjBD,K,8BCDFE,OAAOC,eAAeF,EAASG,OAAOC,YAAa,CAAEC,MAAO,WAE5D,MAAMa,EAAcD,EAAQ,MACtBuC,EAAevC,EAAQ,MAM7BjB,EAAQyD,kBAJR,SAA2BpD,GACvB,OAAOmD,EAAaA,aAAanD,IAAUa,EAAYA,YAAYb,EACvE,C,8BCTa,IAAAqD,EAAAzC,EAAAA,MAAAA,QAEbhB,OAAOC,eAAeF,EAASG,OAAOC,YAAa,CAAEC,MAAO,WAE5D,MAAMU,EAAWE,EAAQ,KACnB0C,EAAc1C,EAAQ,MACtB2C,EAAK3C,EAAQ,MAcnB,SAAS4C,EAAoBC,EAAQC,EAAQC,EAASC,GAClD,GAAIF,IAAWD,EACX,OAAO,EAEX,cAAeC,GACX,IAAK,SACD,OAoBZ,SAAuBD,EAAQC,EAAQC,EAASC,GAC5C,GAAc,MAAVF,EACA,OAAO,EAEX,GAAIxD,MAAMC,QAAQuD,GACd,OAAOG,EAAaJ,EAAQC,EAAQC,EAASC,GAEjD,GAAIF,aAAkBI,IAClB,OAuCR,SAAoBL,EAAQC,EAAQC,EAASC,GACzC,GAAoB,IAAhBF,EAAOK,KACP,OAAO,EAEX,KAAMN,aAAkBK,KACpB,OAAO,EAEX,IAAK,MAAOjB,EAAKmB,KAAgBN,EAAOO,UAAW,CAG/C,IAAgB,IADAN,EADIF,EAAOS,IAAIrB,GACMmB,EAAanB,EAAKY,EAAQC,EAAQE,GAEnE,OAAO,CAEf,CACA,OAAO,CACX,CAtDeO,CAAWV,EAAQC,EAAQC,EAASC,GAE/C,GAAIF,aAAkBU,IAClB,OAAOC,EAAWZ,EAAQC,EAAQC,EAASC,GAE/C,MAAMU,EAAO1E,OAAO0E,KAAKZ,GACzB,GAAc,MAAVD,EACA,OAAuB,IAAhBa,EAAKxD,OAEhB,GAAoB,IAAhBwD,EAAKxD,OACL,OAAO,EAEX,GAAS,OAAL8C,QAAK,IAALA,GAAAA,EAAOW,IAAIb,GACX,OAAOE,EAAMM,IAAIR,KAAYD,EAE5B,OAALG,QAAK,IAALA,GAAAA,EAAOY,IAAId,EAAQD,GACnB,IACI,IAAK,IAAIgB,EAAI,EAAGA,EAAIH,EAAKxD,OAAQ2D,IAAK,CAClC,MAAM5B,EAAMyB,EAAKG,GACjB,IAAKnB,EAAYA,YAAYG,MAAaZ,KAAOY,GAC7C,OAAO,EAEX,QAAoBrC,IAAhBsC,EAAOb,SAAsCzB,IAAhBqC,EAAOZ,GACpC,OAAO,EAEX,GAAoB,OAAhBa,EAAOb,IAAiC,OAAhBY,EAAOZ,GAC/B,OAAO,EAGX,IADgBc,EAAQF,EAAOZ,GAAMa,EAAOb,GAAMA,EAAKY,EAAQC,EAAQE,GAEnE,OAAO,CAEf,CACA,OAAO,CACX,CAAC,QAEQ,OAALA,QAAK,IAALA,GAAAA,EAAOc,OAAOhB,EAClB,CACJ,CAlEmBiB,CAAclB,EAAQC,EAAQC,EAASC,GAElD,IAAK,WAED,OADmBhE,OAAO0E,KAAKZ,GAChB5C,OAAS,EACb0C,EAAoBC,EAAMJ,EAAA,GAAOK,GAAUC,EAASC,GAExDL,EAAGA,GAAGE,EAAQC,GAEzB,QACI,OAAKhD,EAASA,SAAS+C,GAGD,kBAAXC,GACW,KAAXA,EAHAH,EAAGA,GAAGE,EAAQC,GAQrC,CAgEA,SAASG,EAAaJ,EAAQC,EAAQC,EAASC,GAC3C,GAAsB,IAAlBF,EAAO5C,OACP,OAAO,EAEX,IAAKZ,MAAMC,QAAQsD,GACf,OAAO,EAEX,MAAMmB,EAAe,IAAIR,IACzB,IAAK,IAAIK,EAAI,EAAGA,EAAIf,EAAO5C,OAAQ2D,IAAK,CACpC,MAAMI,EAAanB,EAAOe,GAC1B,IAAIK,GAAQ,EACZ,IAAK,IAAIC,EAAI,EAAGA,EAAItB,EAAO3C,OAAQiE,IAAK,CACpC,GAAIH,EAAaL,IAAIQ,GACjB,SAGJ,IAAIC,GAAU,EAKd,GAJgBrB,EAFGF,EAAOsB,GAEUF,EAAYJ,EAAGhB,EAAQC,EAAQE,KAE/DoB,GAAU,GAEVA,EAAS,CACTJ,EAAaK,IAAIF,GACjBD,GAAQ,EACR,KACJ,CACJ,CACA,IAAKA,EACD,OAAO,CAEf,CACA,OAAO,CACX,CACA,SAAST,EAAWZ,EAAQC,EAAQC,EAASC,GACzC,OAAoB,IAAhBF,EAAOK,MAGLN,aAAkBW,KAGjBP,EAAa,IAAIJ,GAAS,IAAIC,GAASC,EAASC,EAC3D,CAEAjE,EAAQuF,YAhJR,SAASA,EAAYzB,EAAQC,EAAQC,GACjC,MAAuB,oBAAZA,EACAuB,EAAYzB,EAAQC,EAAQ,QAEhCF,EAAoBC,EAAQC,EAAQ,SAASyB,EAAUCLine truncated
|
|
||||||
+191
-56
@@ -1,18 +1,18 @@
|
|||||||
.App {
|
.App {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at 15% 0%, #dff7f2 0%, transparent 40%),
|
radial-gradient(circle at 15% 0%, rgba(30, 64, 175, 0.2) 0%, transparent 42%),
|
||||||
radial-gradient(circle at 95% 10%, #e8eefc 0%, transparent 38%),
|
radial-gradient(circle at 95% 10%, rgba(15, 118, 110, 0.14) 0%, transparent 36%),
|
||||||
#f8fafc;
|
#020617;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
background: rgba(255, 255, 255, 0.85);
|
background: rgba(15, 23, 42, 0.82);
|
||||||
backdrop-filter: blur(8px);
|
backdrop-filter: blur(8px);
|
||||||
-webkit-backdrop-filter: blur(8px);
|
-webkit-backdrop-filter: blur(8px);
|
||||||
padding: 0.85rem 2rem;
|
padding: 0.85rem 2rem;
|
||||||
color: #0f172a;
|
color: #e2e8f0;
|
||||||
border-bottom: 1px solid #dbe2ea;
|
border-bottom: 1px solid #334155;
|
||||||
box-shadow: 0 4px 18px rgba(15, 23, 42, 0.06);
|
box-shadow: 0 4px 18px rgba(15, 23, 42, 0.06);
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-link {
|
.nav-link {
|
||||||
color: #475569;
|
color: #cbd5e1;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.92rem;
|
font-size: 0.92rem;
|
||||||
@@ -58,9 +58,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-link:hover {
|
.nav-link:hover {
|
||||||
background: #ffffff;
|
background: #111827;
|
||||||
color: #0f172a;
|
color: #f8fafc;
|
||||||
box-shadow: inset 0 0 0 1px #dbe2ea;
|
box-shadow: inset 0 0 0 1px #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-link {
|
.settings-link {
|
||||||
@@ -83,9 +83,9 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 0.45rem;
|
gap: 0.45rem;
|
||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
color: #334155;
|
color: #e2e8f0;
|
||||||
background: #ffffff;
|
background: #111827;
|
||||||
border: 1px solid #dbe2ea;
|
border: 1px solid #334155;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
padding: 0.3rem 0.7rem;
|
padding: 0.3rem 0.7rem;
|
||||||
}
|
}
|
||||||
@@ -108,11 +108,12 @@
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 1.85rem;
|
font-size: 1.85rem;
|
||||||
letter-spacing: -0.02em;
|
letter-spacing: -0.02em;
|
||||||
|
color: #f8fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dashboard-subtitle {
|
.dashboard-subtitle {
|
||||||
margin: 0.2rem 0 0;
|
margin: 0.2rem 0 0;
|
||||||
color: #64748b;
|
color: #94a3b8;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,12 +124,42 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quick-filter-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.45rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
margin: 0.2rem 0 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-chip {
|
||||||
|
background: #0f172a;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
color: #cbd5e1;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.28rem 0.7rem;
|
||||||
|
font-size: 0.74rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.02em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-chip:hover {
|
||||||
|
background: #111f34;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quick-chip.active {
|
||||||
|
background: #14b8a6;
|
||||||
|
border-color: #14b8a6;
|
||||||
|
color: #052e2b;
|
||||||
|
}
|
||||||
|
|
||||||
.tag-filter-input {
|
.tag-filter-input {
|
||||||
padding: 0.5rem 0.75rem;
|
padding: 0.5rem 0.75rem;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: 1px solid #cbd5e1;
|
border: 1px solid #334155;
|
||||||
min-width: 180px;
|
min-width: 180px;
|
||||||
background: rgba(255, 255, 255, 0.9);
|
background: #111827;
|
||||||
|
color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.actions-menu-wrapper {
|
.actions-menu-wrapper {
|
||||||
@@ -322,10 +353,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.resource-list-shell {
|
.resource-list-shell {
|
||||||
background: rgba(255, 255, 255, 0.82);
|
background: rgba(15, 23, 42, 0.9);
|
||||||
border: 1px solid #cbd5e1;
|
border: 1px solid #334155;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 0.65rem;
|
padding: 0.65rem;
|
||||||
|
max-height: calc(100vh - 250px);
|
||||||
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resource-list-header {
|
.resource-list-header {
|
||||||
@@ -336,15 +369,24 @@
|
|||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.08em;
|
||||||
color: #64748b;
|
color: #94a3b8;
|
||||||
border-bottom: 1px solid #e2e8f0;
|
border-bottom: 1px solid #334155;
|
||||||
margin-bottom: 0.45rem;
|
margin-bottom: 0.45rem;
|
||||||
|
position: sticky;
|
||||||
|
top: 0;
|
||||||
|
z-index: 5;
|
||||||
|
background: rgba(15, 23, 42, 0.98);
|
||||||
}
|
}
|
||||||
|
|
||||||
.resource-list-count {
|
.resource-list-count {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: #64748b;
|
color: #94a3b8;
|
||||||
margin: 0 0.6rem 0.55rem;
|
margin: 0 0.6rem 0.55rem;
|
||||||
|
position: sticky;
|
||||||
|
top: 31px;
|
||||||
|
z-index: 4;
|
||||||
|
background: rgba(15, 23, 42, 0.95);
|
||||||
|
padding-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resource-row {
|
.resource-row {
|
||||||
@@ -353,18 +395,18 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 0.65rem 0.8rem;
|
padding: 0.65rem 0.8rem;
|
||||||
background: rgba(248, 250, 252, 0.96);
|
background: rgba(15, 23, 42, 0.96);
|
||||||
border: 1px solid #dbe2ea;
|
border: 1px solid #334155;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
box-shadow: 0 3px 10px rgba(15, 23, 42, 0.05);
|
box-shadow: 0 4px 12px rgba(2, 6, 23, 0.55);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: border-color 0.2s, transform 0.2s, box-shadow 0.2s;
|
transition: border-color 0.2s, transform 0.2s, box-shadow 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resource-row:hover {
|
.resource-row:hover {
|
||||||
transform: translateY(-1px);
|
transform: translateY(-1px);
|
||||||
border-color: #94a3b8;
|
border-color: #60a5fa;
|
||||||
box-shadow: 0 9px 18px rgba(15, 23, 42, 0.1);
|
box-shadow: 0 12px 22px rgba(2, 6, 23, 0.65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-main {
|
.row-main {
|
||||||
@@ -382,13 +424,14 @@
|
|||||||
.row-name {
|
.row-name {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.98rem;
|
font-size: 0.98rem;
|
||||||
|
color: #f8fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.resource-group-pill {
|
.resource-group-pill {
|
||||||
padding: 0.15rem 0.5rem;
|
padding: 0.15rem 0.5rem;
|
||||||
border-radius: 999px;
|
border-radius: 999px;
|
||||||
background: #e2e8f0;
|
background: #1e293b;
|
||||||
color: #334155;
|
color: #cbd5e1;
|
||||||
font-size: 0.72rem;
|
font-size: 0.72rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
@@ -414,6 +457,7 @@
|
|||||||
.row-url,
|
.row-url,
|
||||||
.row-type {
|
.row-type {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-url {
|
.row-url {
|
||||||
@@ -436,6 +480,12 @@
|
|||||||
|
|
||||||
.row-metrics .stat-label {
|
.row-metrics .stat-label {
|
||||||
font-size: 0.64rem;
|
font-size: 0.64rem;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
|
.row-metrics .stat-value {
|
||||||
|
color: #e2e8f0;
|
||||||
|
font-weight: 700;
|
||||||
}
|
}
|
||||||
|
|
||||||
.row-sparkline {
|
.row-sparkline {
|
||||||
@@ -488,11 +538,40 @@
|
|||||||
margin: 0 0 0.2rem 0;
|
margin: 0 0 0.2rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.trend-chip {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 0.3rem;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
.trend-better {
|
||||||
|
color: #34d399;
|
||||||
|
}
|
||||||
|
.trend-worse {
|
||||||
|
color: #f87171;
|
||||||
|
}
|
||||||
|
.trend-flat {
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
|
||||||
.resource-url {
|
.resource-url {
|
||||||
color: #64748b;
|
color: #64748b;
|
||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
}
|
}
|
||||||
|
.load-more-btn {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px dashed #94a3b8;
|
||||||
|
background: #f8fafc;
|
||||||
|
color: #334155;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0.65rem;
|
||||||
|
font-weight: 700;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.load-more-btn:hover {
|
||||||
|
background: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
.resource-type {
|
.resource-type {
|
||||||
margin: 0.15rem 0 0;
|
margin: 0.15rem 0 0;
|
||||||
@@ -537,7 +616,7 @@
|
|||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 1.2rem;
|
font-size: 1.2rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
color: #333;
|
color: #e2e8f0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,7 +626,7 @@
|
|||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: #666;
|
color: #94a3b8;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@@ -567,7 +646,7 @@
|
|||||||
.last-check {
|
.last-check {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: #999;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.group-section {
|
.group-section {
|
||||||
@@ -671,17 +750,48 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon {
|
.btn-icon {
|
||||||
background: none;
|
background: #0b1325;
|
||||||
border: none;
|
border: 1px solid #334155;
|
||||||
|
color: #e2e8f0;
|
||||||
|
min-width: 34px;
|
||||||
|
min-height: 30px;
|
||||||
|
border-radius: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
opacity: 0.6;
|
opacity: 1;
|
||||||
transition: opacity 0.2s;
|
transition: transform 0.15s, background 0.2s, border-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-icon:hover {
|
.btn-icon:hover {
|
||||||
opacity: 1;
|
transform: translateY(-1px);
|
||||||
|
background: #111f34;
|
||||||
|
border-color: #60a5fa;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon:focus-visible {
|
||||||
|
outline: 2px solid #60a5fa;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon.action-edit {
|
||||||
|
color: #93c5fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon.action-maint {
|
||||||
|
color: #fbbf24;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon.action-delete {
|
||||||
|
color: #fca5a5;
|
||||||
|
border-color: #7f1d1d;
|
||||||
|
background: #3f1d1d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-icon.action-delete:hover {
|
||||||
|
background: #7f1d1d;
|
||||||
|
border-color: #ef4444;
|
||||||
|
color: #fee2e2;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-sm {
|
.btn-sm {
|
||||||
@@ -813,17 +923,18 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.detail-section {
|
.detail-section {
|
||||||
background: rgba(255, 255, 255, 0.92);
|
background: rgba(15, 23, 42, 0.94);
|
||||||
border: 1px solid #e2e8f0;
|
border: 1px solid #334155;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06);
|
box-shadow: 0 10px 24px rgba(2, 6, 23, 0.55);
|
||||||
|
color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-section h2 {
|
.detail-section h2 {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
color: #333;
|
color: #f8fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-header-row {
|
.detail-header-row {
|
||||||
@@ -841,7 +952,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.resource-url-detail {
|
.resource-url-detail {
|
||||||
color: #64748b;
|
color: #94a3b8;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,7 +986,15 @@
|
|||||||
|
|
||||||
.filter-control label {
|
.filter-control label {
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #475569;
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-control select {
|
||||||
|
background: #111827;
|
||||||
|
color: #e2e8f0;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 0.3rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls-pagination {
|
.controls-pagination {
|
||||||
@@ -896,9 +1015,9 @@
|
|||||||
.data-table th {
|
.data-table th {
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 2px solid #dbe2ea;
|
border-bottom: 2px solid #334155;
|
||||||
color: #1e293b;
|
color: #e2e8f0;
|
||||||
background: #f1f5f9;
|
background: #111827;
|
||||||
font-size: 0.86rem;
|
font-size: 0.86rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
@@ -906,11 +1025,12 @@
|
|||||||
|
|
||||||
.data-table td {
|
.data-table td {
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid #1f2937;
|
||||||
|
color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-table tbody tr:hover {
|
.data-table tbody tr:hover {
|
||||||
background: #f0f9ff;
|
background: #1e293b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-right {
|
.text-right {
|
||||||
@@ -918,7 +1038,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.muted-cell {
|
.muted-cell {
|
||||||
color: #64748b;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.w-25 {
|
.w-25 {
|
||||||
@@ -942,14 +1062,14 @@
|
|||||||
|
|
||||||
.incident-reason-toggle {
|
.incident-reason-toggle {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #0f766e;
|
color: #93c5fd;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expand-indicator {
|
.expand-indicator {
|
||||||
margin-left: 0.5rem;
|
margin-left: 0.5rem;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: #64748b;
|
color: #94a3b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-compact {
|
.btn-compact {
|
||||||
@@ -958,7 +1078,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.expanded-row {
|
.expanded-row {
|
||||||
background: #f8fafc;
|
background: #0b1325;
|
||||||
}
|
}
|
||||||
|
|
||||||
.expanded-cell {
|
.expanded-cell {
|
||||||
@@ -966,7 +1086,7 @@
|
|||||||
word-break: break-word;
|
word-break: break-word;
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Courier New', monospace;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #334155;
|
color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.section-title-row {
|
.section-title-row {
|
||||||
@@ -978,7 +1098,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.empty-copy {
|
.empty-copy {
|
||||||
color: #64748b;
|
color: #94a3b8;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
@@ -986,6 +1106,10 @@
|
|||||||
.chart-container {
|
.chart-container {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
height: 300px;
|
height: 300px;
|
||||||
|
background: #0b1325;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.incident-badge {
|
.incident-badge {
|
||||||
@@ -1002,15 +1126,16 @@
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
padding: 0.5rem 1rem;
|
padding: 0.5rem 1rem;
|
||||||
background: #6c757d;
|
background: #1e293b;
|
||||||
color: white;
|
color: #f8fafc;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
|
border: 1px solid #334155;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.back-button:hover {
|
.back-button:hover {
|
||||||
background: #5a6268;
|
background: #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
.empty-state {
|
.empty-state {
|
||||||
@@ -1111,6 +1236,16 @@
|
|||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.resource-list-shell {
|
||||||
|
max-height: none;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-list-count,
|
||||||
|
.resource-list-header {
|
||||||
|
position: static;
|
||||||
|
}
|
||||||
|
|
||||||
.resource-row {
|
.resource-row {
|
||||||
grid-template-columns: 1fr;
|
grid-template-columns: 1fr;
|
||||||
gap: 0.7rem;
|
gap: 0.7rem;
|
||||||
|
|||||||
+134
-23
@@ -52,6 +52,9 @@ function Dashboard() {
|
|||||||
});
|
});
|
||||||
const [tagFilter, setTagFilter] = useState('');
|
const [tagFilter, setTagFilter] = useState('');
|
||||||
const [groupFilter, setGroupFilter] = useState('all');
|
const [groupFilter, setGroupFilter] = useState('all');
|
||||||
|
const [quickFilter, setQuickFilter] = useState('all');
|
||||||
|
const [sortKey, setSortKey] = useState('severity');
|
||||||
|
const [renderLimit, setRenderLimit] = useState(120);
|
||||||
const [groupData, setGroupData] = useState({ name: '', description: '' });
|
const [groupData, setGroupData] = useState({ name: '', description: '' });
|
||||||
const wsRef = useRef(null);
|
const wsRef = useRef(null);
|
||||||
const actionsRef = useRef(null);
|
const actionsRef = useRef(null);
|
||||||
@@ -238,6 +241,17 @@ function Dashboard() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleToggleMaintenance = async (resource) => {
|
||||||
|
try {
|
||||||
|
await axios.patch(`/api/resources/${resource.id}/maintenance-mode`, {
|
||||||
|
maintenance_mode: !resource.maintenance_mode,
|
||||||
|
});
|
||||||
|
loadResources();
|
||||||
|
} catch (error) {
|
||||||
|
alert('Error updating maintenance mode');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleExportCSV = async () => {
|
const handleExportCSV = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await axios.get('/api/resources/export', {
|
const response = await axios.get('/api/resources/export', {
|
||||||
@@ -283,7 +297,28 @@ function Dashboard() {
|
|||||||
return acc;
|
return acc;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
// Filter resources by tag/group and sort for stable scanning
|
const getTrend = (recentChecks = []) => {
|
||||||
|
const withTimes = recentChecks.filter((c) => typeof c.response_time === 'number' && c.response_time > 0);
|
||||||
|
if (withTimes.length < 2) return 'flat';
|
||||||
|
const first = withTimes[0].response_time;
|
||||||
|
const last = withTimes[withTimes.length - 1].response_time;
|
||||||
|
if (first <= 0) return 'flat';
|
||||||
|
const deltaPct = ((last - first) / first) * 100;
|
||||||
|
if (deltaPct <= -12) return 'better';
|
||||||
|
if (deltaPct >= 12) return 'worse';
|
||||||
|
return 'flat';
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSeverityScore = (r) => {
|
||||||
|
const statusScore = r.status === 'down' ? 1000 : r.status === 'unknown' ? 400 : 0;
|
||||||
|
const incidentScore = r.hasActiveIncident ? 350 : 0;
|
||||||
|
const maintenanceOffset = r.maintenance_mode ? -300 : 0;
|
||||||
|
const uptimePenalty = Math.max(0, 100 - parseFloat(r.uptime || 0)) * 3;
|
||||||
|
const responsePenalty = Math.min(250, Math.round((parseInt(r.avgResponseTime || 0, 10) || 0) / 20));
|
||||||
|
return statusScore + incidentScore + uptimePenalty + responsePenalty + maintenanceOffset;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Filter resources by tag/group/quick filters
|
||||||
const filteredResources = resources
|
const filteredResources = resources
|
||||||
.filter((r) => {
|
.filter((r) => {
|
||||||
if (tagFilter) {
|
if (tagFilter) {
|
||||||
@@ -292,9 +327,39 @@ function Dashboard() {
|
|||||||
}
|
}
|
||||||
if (groupFilter === 'ungrouped') return !r.group_id;
|
if (groupFilter === 'ungrouped') return !r.group_id;
|
||||||
if (groupFilter !== 'all') return String(r.group_id || '') === groupFilter;
|
if (groupFilter !== 'all') return String(r.group_id || '') === groupFilter;
|
||||||
|
|
||||||
|
if (quickFilter === 'down' && r.status !== 'down') return false;
|
||||||
|
if (quickFilter === 'maintenance' && !r.maintenance_mode) return false;
|
||||||
|
if (quickFilter === 'nodata' && (!r.recentChecks || r.recentChecks.length === 0)) return false;
|
||||||
|
if (quickFilter === 'slow' && (parseInt(r.avgResponseTime || 0, 10) || 0) < 1500) return false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
})
|
});
|
||||||
.sort((a, b) => {
|
|
||||||
|
const sortedResources = [...filteredResources].sort((a, b) => {
|
||||||
|
if (sortKey === 'name') {
|
||||||
|
return String(a.name || '').localeCompare(String(b.name || ''));
|
||||||
|
}
|
||||||
|
if (sortKey === 'status') {
|
||||||
|
const rank = { down: 0, unknown: 1, up: 2 };
|
||||||
|
return (rank[a.status] ?? 3) - (rank[b.status] ?? 3);
|
||||||
|
}
|
||||||
|
if (sortKey === 'uptime') {
|
||||||
|
return parseFloat(a.uptime || 0) - parseFloat(b.uptime || 0);
|
||||||
|
}
|
||||||
|
if (sortKey === 'response') {
|
||||||
|
return (parseInt(b.avgResponseTime || 0, 10) || 0) - (parseInt(a.avgResponseTime || 0, 10) || 0);
|
||||||
|
}
|
||||||
|
if (sortKey === 'lastcheck') {
|
||||||
|
const ta = a.lastCheck ? new Date(a.lastCheck).getTime() : 0;
|
||||||
|
const tb = b.lastCheck ? new Date(b.lastCheck).getTime() : 0;
|
||||||
|
return tb - ta;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: severity first
|
||||||
|
const severityDiff = getSeverityScore(b) - getSeverityScore(a);
|
||||||
|
if (severityDiff !== 0) return severityDiff;
|
||||||
|
|
||||||
const groupA = (groupNameById[a.group_id] || 'Ungrouped').toLowerCase();
|
const groupA = (groupNameById[a.group_id] || 'Ungrouped').toLowerCase();
|
||||||
const groupB = (groupNameById[b.group_id] || 'Ungrouped').toLowerCase();
|
const groupB = (groupNameById[b.group_id] || 'Ungrouped').toLowerCase();
|
||||||
if (groupA < groupB) return -1;
|
if (groupA < groupB) return -1;
|
||||||
@@ -302,18 +367,24 @@ function Dashboard() {
|
|||||||
return String(a.name || '').localeCompare(String(b.name || ''));
|
return String(a.name || '').localeCompare(String(b.name || ''));
|
||||||
});
|
});
|
||||||
|
|
||||||
const totalCount = filteredResources.length;
|
useEffect(() => {
|
||||||
const upCount = filteredResources.filter((r) => r.status === 'up').length;
|
setRenderLimit(120);
|
||||||
const downCount = filteredResources.filter((r) => r.status === 'down').length;
|
}, [tagFilter, groupFilter, quickFilter, sortKey]);
|
||||||
const maintenanceCount = filteredResources.filter((r) => r.maintenance_mode).length;
|
|
||||||
|
const visibleResources = sortedResources.slice(0, renderLimit);
|
||||||
|
|
||||||
|
const totalCount = sortedResources.length;
|
||||||
|
const upCount = sortedResources.filter((r) => r.status === 'up').length;
|
||||||
|
const downCount = sortedResources.filter((r) => r.status === 'down').length;
|
||||||
|
const maintenanceCount = sortedResources.filter((r) => r.maintenance_mode).length;
|
||||||
const avgUptime = totalCount
|
const avgUptime = totalCount
|
||||||
? (filteredResources.reduce((sum, r) => sum + parseFloat(r.uptime || 0), 0) / totalCount).toFixed(2)
|
? (sortedResources.reduce((sum, r) => sum + parseFloat(r.uptime || 0), 0) / totalCount).toFixed(2)
|
||||||
: '0.00';
|
: '0.00';
|
||||||
const avgResponse = totalCount
|
const avgResponse = totalCount
|
||||||
? Math.round(filteredResources.reduce((sum, r) => sum + parseInt(r.avgResponseTime || 0, 10), 0) / totalCount)
|
? Math.round(sortedResources.reduce((sum, r) => sum + parseInt(r.avgResponseTime || 0, 10), 0) / totalCount)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
const activeIncidents = filteredResources
|
const activeIncidents = sortedResources
|
||||||
.filter((r) => r.hasActiveIncident || r.status === 'down')
|
.filter((r) => r.hasActiveIncident || r.status === 'down')
|
||||||
.slice(0, 8);
|
.slice(0, 8);
|
||||||
|
|
||||||
@@ -321,11 +392,11 @@ function Dashboard() {
|
|||||||
.map((g) => ({
|
.map((g) => ({
|
||||||
id: g.id,
|
id: g.id,
|
||||||
name: g.name,
|
name: g.name,
|
||||||
count: filteredResources.filter((r) => String(r.group_id || '') === String(g.id)).length,
|
count: sortedResources.filter((r) => String(r.group_id || '') === String(g.id)).length,
|
||||||
}))
|
}))
|
||||||
.filter((g) => g.count > 0);
|
.filter((g) => g.count > 0);
|
||||||
|
|
||||||
const ungroupedCount = filteredResources.filter((r) => !r.group_id).length;
|
const ungroupedCount = sortedResources.filter((r) => !r.group_id).length;
|
||||||
|
|
||||||
const renderResourceRow = (resource) => {
|
const renderResourceRow = (resource) => {
|
||||||
const sparkData = (resource.recentChecks || []).map((c, idx) => ({
|
const sparkData = (resource.recentChecks || []).map((c, idx) => ({
|
||||||
@@ -334,6 +405,7 @@ function Dashboard() {
|
|||||||
statusValue: c.status === 'up' ? 1 : 0,
|
statusValue: c.status === 'up' ? 1 : 0,
|
||||||
}));
|
}));
|
||||||
const groupName = groupNameById[resource.group_id] || 'Ungrouped';
|
const groupName = groupNameById[resource.group_id] || 'Ungrouped';
|
||||||
|
const trend = getTrend(resource.recentChecks || []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -358,7 +430,10 @@ function Dashboard() {
|
|||||||
<p className="stat-label">Uptime</p>
|
<p className="stat-label">Uptime</p>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p className="stat-value small">{resource.avgResponseTime}ms</p>
|
<p className="stat-value small">
|
||||||
|
{resource.avgResponseTime}ms
|
||||||
|
<span className={`trend-chip trend-${trend}`}>{trend === 'better' ? '↓' : trend === 'worse' ? '↑' : '→'}</span>
|
||||||
|
</p>
|
||||||
<p className="stat-label">Avg Resp</p>
|
<p className="stat-label">Avg Resp</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="row-sparkline">
|
<div className="row-sparkline">
|
||||||
@@ -379,8 +454,15 @@ function Dashboard() {
|
|||||||
{resource.status}
|
{resource.status}
|
||||||
</span>
|
</span>
|
||||||
<div className="row-actions">
|
<div className="row-actions">
|
||||||
<button className="btn-icon" title="Edit" onClick={(e) => { e.stopPropagation(); openEditModal(resource); }}>✎</button>
|
<button className="btn-icon action-edit" title="Edit" onClick={(e) => { e.stopPropagation(); openEditModal(resource); }}>✎</button>
|
||||||
<button className="btn-icon" title="Delete" onClick={(e) => { e.stopPropagation(); handleDeleteResource(resource.id); }}>🗑</button>
|
<button
|
||||||
|
className="btn-icon action-maint"
|
||||||
|
title={resource.maintenance_mode ? 'End maintenance' : 'Start maintenance'}
|
||||||
|
onClick={(e) => { e.stopPropagation(); handleToggleMaintenance(resource); }}
|
||||||
|
>
|
||||||
|
{resource.maintenance_mode ? '✅' : '🛠'}
|
||||||
|
</button>
|
||||||
|
<button className="btn-icon action-delete" title="Delete" onClick={(e) => { e.stopPropagation(); handleDeleteResource(resource.id); }}>🗑</button>
|
||||||
</div>
|
</div>
|
||||||
<p className="last-check">
|
<p className="last-check">
|
||||||
Last: {resource.lastCheck ? formatLocalTime(resource.lastCheck) : 'Never'}
|
Last: {resource.lastCheck ? formatLocalTime(resource.lastCheck) : 'Never'}
|
||||||
@@ -431,6 +513,18 @@ function Dashboard() {
|
|||||||
))}
|
))}
|
||||||
<option value="ungrouped">Ungrouped</option>
|
<option value="ungrouped">Ungrouped</option>
|
||||||
</select>
|
</select>
|
||||||
|
<select
|
||||||
|
value={sortKey}
|
||||||
|
onChange={(e) => setSortKey(e.target.value)}
|
||||||
|
className="tag-filter-input"
|
||||||
|
>
|
||||||
|
<option value="severity">Sort: Severity</option>
|
||||||
|
<option value="status">Sort: Status</option>
|
||||||
|
<option value="uptime">Sort: Uptime</option>
|
||||||
|
<option value="response">Sort: Response</option>
|
||||||
|
<option value="lastcheck">Sort: Last Check</option>
|
||||||
|
<option value="name">Sort: Name</option>
|
||||||
|
</select>
|
||||||
<div ref={actionsRef} className="actions-menu-wrapper">
|
<div ref={actionsRef} className="actions-menu-wrapper">
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary"
|
className="btn btn-secondary"
|
||||||
@@ -487,6 +581,14 @@ function Dashboard() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="quick-filter-row">
|
||||||
|
<button className={`quick-chip ${quickFilter === 'all' ? 'active' : ''}`} onClick={() => setQuickFilter('all')}>All</button>
|
||||||
|
<button className={`quick-chip ${quickFilter === 'down' ? 'active' : ''}`} onClick={() => setQuickFilter('down')}>Down</button>
|
||||||
|
<button className={`quick-chip ${quickFilter === 'maintenance' ? 'active' : ''}`} onClick={() => setQuickFilter('maintenance')}>Maintenance</button>
|
||||||
|
<button className={`quick-chip ${quickFilter === 'slow' ? 'active' : ''}`} onClick={() => setQuickFilter('slow')}>High Latency</button>
|
||||||
|
<button className={`quick-chip ${quickFilter === 'nodata' ? 'active' : ''}`} onClick={() => setQuickFilter('nodata')}>No Data</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="cc-metrics-grid">
|
<div className="cc-metrics-grid">
|
||||||
<div className="cc-metric-card">
|
<div className="cc-metric-card">
|
||||||
<p className="cc-metric-label">Monitors</p>
|
<p className="cc-metric-label">Monitors</p>
|
||||||
@@ -519,7 +621,7 @@ function Dashboard() {
|
|||||||
<h3>No resources yet</h3>
|
<h3>No resources yet</h3>
|
||||||
<p>Add your first resource to start monitoring</p>
|
<p>Add your first resource to start monitoring</p>
|
||||||
</div>
|
</div>
|
||||||
) : filteredResources.length === 0 ? (
|
) : sortedResources.length === 0 ? (
|
||||||
<div className="empty-state">
|
<div className="empty-state">
|
||||||
<h3>No resources match filter</h3>
|
<h3>No resources match filter</h3>
|
||||||
<p>Try adjusting your tag/group filters</p>
|
<p>Try adjusting your tag/group filters</p>
|
||||||
@@ -532,9 +634,14 @@ function Dashboard() {
|
|||||||
<div>Metrics</div>
|
<div>Metrics</div>
|
||||||
<div>Status</div>
|
<div>Status</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="resource-list-count">{filteredResources.length} monitors</div>
|
<div className="resource-list-count">{visibleResources.length} of {sortedResources.length} monitors</div>
|
||||||
<div className="resource-list">
|
<div className="resource-list">
|
||||||
{filteredResources.map(renderResourceRow)}
|
{visibleResources.map(renderResourceRow)}
|
||||||
|
{visibleResources.length < sortedResources.length && (
|
||||||
|
<button className="load-more-btn" onClick={() => setRenderLimit((prev) => prev + 120)}>
|
||||||
|
Load 120 more
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@@ -1387,11 +1494,15 @@ function ResourceDetail() {
|
|||||||
<div className="chart-container">
|
<div className="chart-container">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<LineChart data={chartData}>
|
<LineChart data={chartData}>
|
||||||
<CartesianGrid strokeDasharray="3 3" />
|
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
||||||
<XAxis dataKey="time" />
|
<XAxis dataKey="time" tick={{ fill: '#94a3b8', fontSize: 12 }} />
|
||||||
<YAxis />
|
<YAxis tick={{ fill: '#94a3b8', fontSize: 12 }} />
|
||||||
<Tooltip />
|
<Tooltip
|
||||||
<Line type="monotone" dataKey="responseTime" stroke="#667eea" strokeWidth={2} />
|
contentStyle={{ backgroundColor: '#0f172a', border: '1px solid #334155', borderRadius: '8px' }}
|
||||||
|
labelStyle={{ color: '#cbd5e1' }}
|
||||||
|
itemStyle={{ color: '#e2e8f0' }}
|
||||||
|
/>
|
||||||
|
<Line type="monotone" dataKey="responseTime" stroke="#60a5fa" strokeWidth={2} dot={false} isAnimationActive={false} />
|
||||||
</LineChart>
|
</LineChart>
|
||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,290 @@
|
|||||||
|
.cc-page {
|
||||||
|
padding-top: 1.4rem;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-alert {
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #854d0e;
|
||||||
|
background: #2b1907;
|
||||||
|
color: #fcd34d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 1.2rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-page-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.65rem;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
color: #f8fafc !important;
|
||||||
|
text-shadow: 0 1px 0 rgba(15, 23, 42, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-page h2 {
|
||||||
|
color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
align-items: center;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-pill-toggle {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
padding: 0.5rem 0.75rem;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: #111827;
|
||||||
|
color: #f1f5f9;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-pill-toggle input {
|
||||||
|
accent-color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-pill-toggle:focus-within,
|
||||||
|
.cc-input-sm:focus,
|
||||||
|
.cc-select-sm:focus {
|
||||||
|
outline: 2px solid #60a5fa;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-surface {
|
||||||
|
background: #0f172a;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-kpi-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
|
||||||
|
gap: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-kpi {
|
||||||
|
background: #0b1325;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-kpi-label {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-kpi-value {
|
||||||
|
margin: 0.35rem 0 0;
|
||||||
|
color: #f8fafc;
|
||||||
|
font-size: 1.45rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-table-wrap {
|
||||||
|
overflow: auto;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: #0b1325;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-table thead tr {
|
||||||
|
background: #0f172a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-table th {
|
||||||
|
text-align: left;
|
||||||
|
padding: 0.75rem;
|
||||||
|
color: #e2e8f0;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.07em;
|
||||||
|
border-bottom: 1px solid #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-table td {
|
||||||
|
padding: 0.75rem;
|
||||||
|
border-bottom: 1px solid #1f2937;
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-table tbody tr:hover {
|
||||||
|
background: #1e293b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-pagination {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-top: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
background: #111827;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-input-sm {
|
||||||
|
width: 56px;
|
||||||
|
padding: 0.45rem;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 8px;
|
||||||
|
text-align: center;
|
||||||
|
background: #111827;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-select-sm {
|
||||||
|
padding: 0.45rem;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #111827;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-empty {
|
||||||
|
text-align: center;
|
||||||
|
color: #cbd5e1;
|
||||||
|
padding: 2.2rem 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-resource-card {
|
||||||
|
margin-bottom: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-resource-title {
|
||||||
|
margin: 0 0 0.45rem;
|
||||||
|
color: #dbeafe;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-resource-meta {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
color: #e2e8f0;
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-resource-meta strong {
|
||||||
|
color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-chart-box {
|
||||||
|
height: 400px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background: #111827;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-trend-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 1rem;
|
||||||
|
margin-bottom: 0.8rem;
|
||||||
|
color: #e2e8f0;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notifications-list {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
gap: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1rem;
|
||||||
|
padding: 1rem;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
background: #0b1325;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-row.is-unread {
|
||||||
|
border-color: #1d4ed8;
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(59, 130, 246, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-content {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-title {
|
||||||
|
margin: 0 0 0.4rem;
|
||||||
|
color: #f8fafc;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-message {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.65rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
color: #cbd5e1;
|
||||||
|
font-size: 0.84rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-chip {
|
||||||
|
background: #111827;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.2rem 0.55rem;
|
||||||
|
color: #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.4rem;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-actions .btn:focus-visible {
|
||||||
|
outline: 2px solid #60a5fa;
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.cc-controls {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-controls .btn,
|
||||||
|
.cc-controls .tag-filter-input,
|
||||||
|
.cc-controls .cc-pill-toggle {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cc-pagination {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
}
|
||||||
|
}
|
||||||
+55
-59
@@ -12,6 +12,7 @@ import {
|
|||||||
ResponsiveContainer,
|
ResponsiveContainer,
|
||||||
} from 'recharts';
|
} from 'recharts';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import './CommandCenterPages.css';
|
||||||
import { formatChartTime } from './utils/timeUtils';
|
import { formatChartTime } from './utils/timeUtils';
|
||||||
|
|
||||||
const asNumberOrZero = (value) => {
|
const asNumberOrZero = (value) => {
|
||||||
@@ -107,23 +108,16 @@ function History() {
|
|||||||
if (error) return <div className="container">Error loading history: {error}</div>;
|
if (error) return <div className="container">Error loading history: {error}</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container cc-page">
|
||||||
{limitedByRetention && (
|
{limitedByRetention && (
|
||||||
<div style={{
|
<div className="cc-alert">
|
||||||
padding: '1rem',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
backgroundColor: '#fff3cd',
|
|
||||||
border: '1px solid #ffc107',
|
|
||||||
borderRadius: '4px',
|
|
||||||
color: '#856404'
|
|
||||||
}}>
|
|
||||||
⚠️ Data limited to {effectiveDays} days (server retention setting). Requested {days} days.
|
⚠️ Data limited to {effectiveDays} days (server retention setting). Requested {days} days.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem', flexWrap: 'wrap', gap: '1rem' }}>
|
<div className="cc-page-header">
|
||||||
<h2>Monitoring History</h2>
|
<h2 className="cc-page-title" style={{ color: '#f8fafc', fontWeight: 800 }}>Monitoring History</h2>
|
||||||
<div style={{ display: 'flex', gap: '1rem', alignItems: 'center', flexWrap: 'wrap' }}>
|
<div className="cc-controls">
|
||||||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
<div className="cc-controls">
|
||||||
{[7, 14, 30].map((d) => (
|
{[7, 14, 30].map((d) => (
|
||||||
<button
|
<button
|
||||||
key={d}
|
key={d}
|
||||||
@@ -136,21 +130,19 @@ function History() {
|
|||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', cursor: 'pointer', userSelect: 'none', background: '#f5f5f5', padding: '0.5rem 1rem', borderRadius: '4px', border: '1px solid #ddd' }}>
|
<label className="cc-pill-toggle">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={showAveraged}
|
checked={showAveraged}
|
||||||
onChange={(e) => setShowAveraged(e.target.checked)}
|
onChange={(e) => setShowAveraged(e.target.checked)}
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
/>
|
/>
|
||||||
<span>Show Averages</span>
|
<span>Show Averages</span>
|
||||||
</label>
|
</label>
|
||||||
<label style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', cursor: 'pointer', userSelect: 'none', background: '#f5f5f5', padding: '0.5rem 1rem', borderRadius: '4px', border: '1px solid #ddd' }}>
|
<label className="cc-pill-toggle">
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={showTrends}
|
checked={showTrends}
|
||||||
onChange={(e) => setShowTrends(e.target.checked)}
|
onChange={(e) => setShowTrends(e.target.checked)}
|
||||||
style={{ cursor: 'pointer' }}
|
|
||||||
/>
|
/>
|
||||||
<span>Show Week-over-Week Trends</span>
|
<span>Show Week-over-Week Trends</span>
|
||||||
</label>
|
</label>
|
||||||
@@ -169,41 +161,43 @@ function History() {
|
|||||||
const chartData = prepareChartData(resource.checks || []);
|
const chartData = prepareChartData(resource.checks || []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={resource.id} className="detail-section" style={{ marginBottom: '3rem', background: '#fff', padding: '1.5rem', borderRadius: '8px', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}>
|
<div key={resource.id} className="detail-section cc-surface history-resource-card">
|
||||||
<h3 style={{ marginBottom: '0.5rem', color: '#667eea', fontSize: '1.5rem' }}>{resource.name}</h3>
|
<h3 className="history-resource-title">{resource.name}</h3>
|
||||||
<p style={{ color: '#666', marginBottom: '1.5rem', fontSize: '0.95rem' }}>
|
<p className="history-resource-meta">
|
||||||
<span style={{ marginRight: '1.5rem' }}>Type: <strong style={{ color: '#333' }}>{resource.type.toUpperCase()}</strong></span>
|
<span>Type: <strong>{resource.type.toUpperCase()}</strong></span>
|
||||||
<span style={{ marginRight: '1.5rem' }}>Uptime: <strong style={{ color: resource.uptime >= 95 ? '#4caf50' : resource.uptime >= 80 ? '#ff9800' : '#f44336' }}>{resource.uptime}%</strong></span>
|
<span>Uptime: <strong style={{ color: resource.uptime >= 95 ? '#22c55e' : resource.uptime >= 80 ? '#f59e0b' : '#ef4444' }}>{resource.uptime}%</strong></span>
|
||||||
<span>Avg Response: <strong style={{ color: '#333' }}>{resource.avgResponseTime}ms</strong></span>
|
<span>Avg Response: <strong>{resource.avgResponseTime}ms</strong></span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{chartData.length > 0 ? (
|
{chartData.length > 0 ? (
|
||||||
<div className="chart-container" style={{ height: '400px', marginBottom: '1.5rem', background: '#fafafa', padding: '1rem', borderRadius: '6px' }}>
|
<div className="chart-container history-chart-box">
|
||||||
<ResponsiveContainer width="100%" height="100%">
|
<ResponsiveContainer width="100%" height="100%">
|
||||||
<ComposedChart data={chartData} margin={{ top: 10, right: 30, left: 10, bottom: 60 }}>
|
<ComposedChart data={chartData} margin={{ top: 10, right: 30, left: 10, bottom: 60 }}>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#e0e0e0" />
|
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="label"
|
dataKey="label"
|
||||||
tick={{ fontSize: 12, fill: '#666' }}
|
tick={{ fontSize: 12, fill: '#94a3b8' }}
|
||||||
angle={-45}
|
angle={-45}
|
||||||
textAnchor="end"
|
textAnchor="end"
|
||||||
height={80}
|
height={80}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
yAxisId="left"
|
yAxisId="left"
|
||||||
label={{ value: 'Response Time (ms)', angle: -90, position: 'insideLeft', style: { fill: '#666' } }}
|
label={{ value: 'Response Time (ms)', angle: -90, position: 'insideLeft', style: { fill: '#94a3b8' } }}
|
||||||
tick={{ fontSize: 12, fill: '#666' }}
|
tick={{ fontSize: 12, fill: '#94a3b8' }}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
yAxisId="right"
|
yAxisId="right"
|
||||||
orientation="right"
|
orientation="right"
|
||||||
domain={[0, 1]}
|
domain={[0, 1]}
|
||||||
ticks={[0, 1]}
|
ticks={[0, 1]}
|
||||||
tick={{ fontSize: 12, fill: '#666' }}
|
tick={{ fontSize: 12, fill: '#94a3b8' }}
|
||||||
label={{ value: 'Status', angle: 90, position: 'insideRight', style: { fill: '#666' } }}
|
label={{ value: 'Status', angle: 90, position: 'insideRight', style: { fill: '#94a3b8' } }}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={{ backgroundColor: '#fff', border: '1px solid #ddd', borderRadius: '4px', padding: '10px' }}
|
contentStyle={{ backgroundColor: '#0f172a', border: '1px solid #334155', borderRadius: '8px', padding: '10px' }}
|
||||||
|
labelStyle={{ color: '#cbd5e1' }}
|
||||||
|
itemStyle={{ color: '#e2e8f0' }}
|
||||||
formatter={(value, name) => {
|
formatter={(value, name) => {
|
||||||
if (name === 'Response Time') return `${value}ms`;
|
if (name === 'Response Time') return `${value}ms`;
|
||||||
if (name === 'Status') return value === 1 ? '✓ UP' : '✗ DOWN';
|
if (name === 'Status') return value === 1 ? '✓ UP' : '✗ DOWN';
|
||||||
@@ -249,18 +243,18 @@ function History() {
|
|||||||
</ResponsiveContainer>
|
</ResponsiveContainer>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ color: '#999', padding: '2rem', textAlign: 'center', background: '#fafafa', borderRadius: '6px' }}>No check data available</div>
|
<div className="cc-empty">No check data available</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showTrends && resource.trendsData && (
|
{showTrends && resource.trendsData && (
|
||||||
<div className="chart-container" style={{ height: '400px', marginBottom: '1.5rem', background: '#fafafa', padding: '1rem', borderRadius: '6px' }}>
|
<div className="chart-container history-chart-box">
|
||||||
<h4 style={{ marginBottom: '1rem', color: '#667eea' }}>Week-over-Week Comparison</h4>
|
<h4 className="history-resource-title">Week-over-Week Comparison</h4>
|
||||||
{(() => {
|
{(() => {
|
||||||
const comparison = resource.trendsData?.comparison || {};
|
const comparison = resource.trendsData?.comparison || {};
|
||||||
const responseTimeChange = asNumberOrZero(comparison.response_time_change);
|
const responseTimeChange = asNumberOrZero(comparison.response_time_change);
|
||||||
const uptimeChange = asNumberOrZero(comparison.uptime_change);
|
const uptimeChange = asNumberOrZero(comparison.uptime_change);
|
||||||
return (
|
return (
|
||||||
<div style={{ display: 'flex', gap: '2rem', marginBottom: '1rem', fontSize: '0.9rem' }}>
|
<div className="history-trend-row">
|
||||||
<div>
|
<div>
|
||||||
<strong>Response Time Change:</strong>{' '}
|
<strong>Response Time Change:</strong>{' '}
|
||||||
<span style={{ color: responseTimeChange >= 0 ? '#f44336' : '#4caf50' }}>
|
<span style={{ color: responseTimeChange >= 0 ? '#f44336' : '#4caf50' }}>
|
||||||
@@ -280,20 +274,22 @@ function History() {
|
|||||||
})()}
|
})()}
|
||||||
<ResponsiveContainer width="100%" height="90%">
|
<ResponsiveContainer width="100%" height="90%">
|
||||||
<ComposedChart data={prepareTrendsChartData(resource.trendsData)} margin={{ top: 10, right: 30, left: 10, bottom: 60 }}>
|
<ComposedChart data={prepareTrendsChartData(resource.trendsData)} margin={{ top: 10, right: 30, left: 10, bottom: 60 }}>
|
||||||
<CartesianGrid strokeDasharray="3 3" stroke="#e0e0e0" />
|
<CartesianGrid strokeDasharray="3 3" stroke="#334155" />
|
||||||
<XAxis
|
<XAxis
|
||||||
dataKey="day"
|
dataKey="day"
|
||||||
tick={{ fontSize: 12, fill: '#666' }}
|
tick={{ fontSize: 12, fill: '#94a3b8' }}
|
||||||
angle={-45}
|
angle={-45}
|
||||||
textAnchor="end"
|
textAnchor="end"
|
||||||
height={80}
|
height={80}
|
||||||
/>
|
/>
|
||||||
<YAxis
|
<YAxis
|
||||||
label={{ value: 'Response Time (ms)', angle: -90, position: 'insideLeft', style: { fill: '#666' } }}
|
label={{ value: 'Response Time (ms)', angle: -90, position: 'insideLeft', style: { fill: '#94a3b8' } }}
|
||||||
tick={{ fontSize: 12, fill: '#666' }}
|
tick={{ fontSize: 12, fill: '#94a3b8' }}
|
||||||
/>
|
/>
|
||||||
<Tooltip
|
<Tooltip
|
||||||
contentStyle={{ backgroundColor: '#fff', border: '1px solid #ddd', borderRadius: '4px', padding: '10px' }}
|
contentStyle={{ backgroundColor: '#0f172a', border: '1px solid #334155', borderRadius: '8px', padding: '10px' }}
|
||||||
|
labelStyle={{ color: '#cbd5e1' }}
|
||||||
|
itemStyle={{ color: '#e2e8f0' }}
|
||||||
formatter={(value, name) => {
|
formatter={(value, name) => {
|
||||||
if (name === 'Current Period') return `${value}ms`;
|
if (name === 'Current Period') return `${value}ms`;
|
||||||
if (name === 'Previous Period') return `${value}ms`;
|
if (name === 'Previous Period') return `${value}ms`;
|
||||||
@@ -327,22 +323,22 @@ function History() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="stats-grid" style={{ gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: '1rem' }}>
|
<div className="cc-kpi-grid">
|
||||||
<div className="stat" style={{ background: '#f5f5f5', padding: '1rem', borderRadius: '6px' }}>
|
<div className="cc-kpi">
|
||||||
<p className="stat-value" style={{ color: '#667eea', fontSize: '2rem', fontWeight: 'bold' }}>{resource.checks?.length || 0}</p>
|
<p className="cc-kpi-label">Total Checks</p>
|
||||||
<p className="stat-label" style={{ color: '#666', fontSize: '0.9rem' }}>Total Checks</p>
|
<p className="cc-kpi-value" style={{ color: '#60a5fa' }}>{resource.checks?.length || 0}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat" style={{ background: '#f5f5f5', padding: '1rem', borderRadius: '6px' }}>
|
<div className="cc-kpi">
|
||||||
<p className="stat-value" style={{ color: resource.uptime >= 95 ? '#4caf50' : resource.uptime >= 80 ? '#ff9800' : '#f44336', fontSize: '2rem', fontWeight: 'bold' }}>{resource.uptime}%</p>
|
<p className="cc-kpi-label">Uptime</p>
|
||||||
<p className="stat-label" style={{ color: '#666', fontSize: '0.9rem' }}>Uptime</p>
|
<p className="cc-kpi-value" style={{ color: resource.uptime >= 95 ? '#22c55e' : resource.uptime >= 80 ? '#f59e0b' : '#ef4444' }}>{resource.uptime}%</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat" style={{ background: '#f5f5f5', padding: '1rem', borderRadius: '6px' }}>
|
<div className="cc-kpi">
|
||||||
<p className="stat-value" style={{ color: '#667eea', fontSize: '2rem', fontWeight: 'bold' }}>{resource.avgResponseTime}ms</p>
|
<p className="cc-kpi-label">Avg Response</p>
|
||||||
<p className="stat-label" style={{ color: '#666', fontSize: '0.9rem' }}>Avg Response</p>
|
<p className="cc-kpi-value" style={{ color: '#a78bfa' }}>{resource.avgResponseTime}ms</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat" style={{ background: '#f5f5f5', padding: '1rem', borderRadius: '6px' }}>
|
<div className="cc-kpi">
|
||||||
<p className="stat-value" style={{ color: '#4caf50', fontSize: '2rem', fontWeight: 'bold' }}>{(resource.checks || []).filter((c) => c.status === 'up').length}/{resource.checks?.length || 0}</p>
|
<p className="cc-kpi-label">Successful Checks</p>
|
||||||
<p className="stat-label" style={{ color: '#666', fontSize: '0.9rem' }}>Successful Checks</p>
|
<p className="cc-kpi-value" style={{ color: '#34d399' }}>{(resource.checks || []).filter((c) => c.status === 'up').length}/{resource.checks?.length || 0}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -350,7 +346,7 @@ function History() {
|
|||||||
})}
|
})}
|
||||||
|
|
||||||
{totalItems > pageSize && (
|
{totalItems > pageSize && (
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '1rem', marginTop: '2rem', padding: '1.5rem', background: '#f5f5f5', borderRadius: '8px' }}>
|
<div className="cc-pagination">
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary"
|
className="btn btn-secondary"
|
||||||
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
|
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
|
||||||
@@ -358,8 +354,8 @@ function History() {
|
|||||||
>
|
>
|
||||||
← Previous
|
← Previous
|
||||||
</button>
|
</button>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
<div className="cc-controls">
|
||||||
<span style={{ color: '#666', fontSize: '0.9rem' }}>Page</span>
|
<span>Page</span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={currentPage}
|
value={currentPage}
|
||||||
@@ -367,14 +363,14 @@ function History() {
|
|||||||
const page = Math.max(1, Math.min(Math.ceil(totalItems / pageSize), parseInt(e.target.value) || 1));
|
const page = Math.max(1, Math.min(Math.ceil(totalItems / pageSize), parseInt(e.target.value) || 1));
|
||||||
setCurrentPage(page);
|
setCurrentPage(page);
|
||||||
}}
|
}}
|
||||||
style={{ width: '50px', padding: '0.5rem', border: '1px solid #ddd', borderRadius: '4px', textAlign: 'center' }}
|
className="cc-input-sm"
|
||||||
/>
|
/>
|
||||||
<span style={{ color: '#666', fontSize: '0.9rem' }}>of {Math.ceil(totalItems / pageSize)}</span>
|
<span>of {Math.ceil(totalItems / pageSize)}</span>
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
value={pageSize}
|
value={pageSize}
|
||||||
onChange={(e) => { setPageSize(parseInt(e.target.value)); setCurrentPage(1); }}
|
onChange={(e) => { setPageSize(parseInt(e.target.value)); setCurrentPage(1); }}
|
||||||
style={{ padding: '0.5rem', border: '1px solid #ddd', borderRadius: '4px', background: '#fff' }}
|
className="cc-select-sm"
|
||||||
>
|
>
|
||||||
<option value={5}>5 per page</option>
|
<option value={5}>5 per page</option>
|
||||||
<option value={10}>10 per page</option>
|
<option value={10}>10 per page</option>
|
||||||
|
|||||||
+21
-28
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { formatLocalTime } from './utils/timeUtils';
|
import { formatLocalTime } from './utils/timeUtils';
|
||||||
|
import './CommandCenterPages.css';
|
||||||
|
|
||||||
function Notifications() {
|
function Notifications() {
|
||||||
const [notifications, setNotifications] = useState([]);
|
const [notifications, setNotifications] = useState([]);
|
||||||
@@ -106,11 +107,13 @@ function Notifications() {
|
|||||||
const totalPages = Math.ceil(total / itemsPerPage);
|
const totalPages = Math.ceil(total / itemsPerPage);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container" style={{ paddingTop: '2rem' }}>
|
<div className="container cc-page">
|
||||||
<h2>📬 Notifications</h2>
|
<div className="cc-page-header">
|
||||||
|
<h2 className="cc-page-title">Notifications</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div style={{ marginBottom: '2rem', display: 'flex', gap: '1rem', flexWrap: 'wrap', alignItems: 'center' }}>
|
<div className="cc-controls" style={{ marginBottom: '1rem' }}>
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
|
<div className="cc-controls">
|
||||||
<button
|
<button
|
||||||
className={`btn ${filter === 'all' ? 'btn-primary' : 'btn-secondary'}`}
|
className={`btn ${filter === 'all' ? 'btn-primary' : 'btn-secondary'}`}
|
||||||
onClick={() => { setFilter('all'); setCurrentPage(1); }}
|
onClick={() => { setFilter('all'); setCurrentPage(1); }}
|
||||||
@@ -143,50 +146,40 @@ function Notifications() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p style={{ textAlign: 'center', color: '#999' }}>Loading...</p>
|
<p className="cc-empty">Loading...</p>
|
||||||
) : notifications.length === 0 ? (
|
) : notifications.length === 0 ? (
|
||||||
<div style={{ textAlign: 'center', padding: '3rem 1rem', color: '#999' }}>
|
<div className="cc-empty">
|
||||||
<p>No {filter === 'unread' ? 'unread ' : filter === 'read' ? 'read ' : ''}notifications</p>
|
<p>No {filter === 'unread' ? 'unread ' : filter === 'read' ? 'read ' : ''}notifications</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: '1fr', gap: '1rem' }}>
|
<div className="notifications-list">
|
||||||
{notifications.map((notification) => (
|
{notifications.map((notification) => (
|
||||||
<div
|
<div
|
||||||
key={notification.id}
|
key={notification.id}
|
||||||
style={{
|
className={`notification-row ${notification.read ? 'is-read' : 'is-unread'}`}
|
||||||
padding: '1rem',
|
|
||||||
border: '1px solid #ddd',
|
|
||||||
borderRadius: '6px',
|
|
||||||
backgroundColor: notification.read ? 'white' : '#f0f8ff',
|
|
||||||
display: 'flex',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'flex-start',
|
|
||||||
gap: '1rem'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<div style={{ flex: 1 }}>
|
<div className="notification-content">
|
||||||
<h4 style={{ margin: '0 0 0.5rem 0', fontSize: '1rem' }}>{notification.title}</h4>
|
<h4 className="notification-title">{notification.title}</h4>
|
||||||
<p style={{ margin: '0 0 0.5rem 0', color: '#666', fontSize: '0.95rem' }}>
|
<p className="notification-message">
|
||||||
{notification.message}
|
{notification.message}
|
||||||
</p>
|
</p>
|
||||||
<div style={{ display: 'flex', gap: '1rem', fontSize: '0.85rem', color: '#999', flexWrap: 'wrap' }}>
|
<div className="notification-meta">
|
||||||
{notification.resource_name && (
|
{notification.resource_name && (
|
||||||
<span style={{ backgroundColor: '#f0f0f0', padding: '0.25rem 0.5rem', borderRadius: '3px' }}>
|
<span className="notification-chip">
|
||||||
📌 {notification.resource_name}
|
{notification.resource_name}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
<span>⏰ {formatLocalTime(notification.created_at)}</span>
|
<span>{formatLocalTime(notification.created_at)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style={{ display: 'flex', gap: '0.5rem', flexShrink: 0 }}>
|
<div className="notification-actions">
|
||||||
{!notification.read && (
|
{!notification.read && (
|
||||||
<button
|
<button
|
||||||
className="btn btn-sm"
|
className="btn btn-sm"
|
||||||
onClick={() => handleMarkAsRead(notification.id)}
|
onClick={() => handleMarkAsRead(notification.id)}
|
||||||
title="Mark as read"
|
title="Mark as read"
|
||||||
style={{ padding: '0.5rem 0.75rem', fontSize: '0.85rem' }}
|
|
||||||
>
|
>
|
||||||
✓ Read
|
✓ Read
|
||||||
</button>
|
</button>
|
||||||
@@ -195,7 +188,7 @@ function Notifications() {
|
|||||||
className="btn btn-sm"
|
className="btn btn-sm"
|
||||||
onClick={() => handleDelete(notification.id)}
|
onClick={() => handleDelete(notification.id)}
|
||||||
title="Delete"
|
title="Delete"
|
||||||
style={{ padding: '0.5rem 0.75rem', fontSize: '0.85rem', background: '#dc3545' }}
|
style={{ background: '#b91c1c' }}
|
||||||
>
|
>
|
||||||
✕ Delete
|
✕ Delete
|
||||||
</button>
|
</button>
|
||||||
@@ -206,7 +199,7 @@ function Notifications() {
|
|||||||
|
|
||||||
{/* Pagination */}
|
{/* Pagination */}
|
||||||
{totalPages > 1 && (
|
{totalPages > 1 && (
|
||||||
<div style={{ marginTop: '2rem', display: 'flex', justifyContent: 'center', gap: '0.5rem', flexWrap: 'wrap' }}>
|
<div className="cc-pagination">
|
||||||
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
|
{Array.from({ length: totalPages }, (_, i) => i + 1).map((page) => (
|
||||||
<button
|
<button
|
||||||
key={page}
|
key={page}
|
||||||
|
|||||||
@@ -2,9 +2,14 @@
|
|||||||
padding-top: 2rem;
|
padding-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.observability-page h2 {
|
||||||
|
color: #f8fafc;
|
||||||
|
text-shadow: 0 1px 0 rgba(15, 23, 42, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
.obs-tabs {
|
.obs-tabs {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
border-bottom: 1px solid #dbe2ea;
|
border-bottom: 1px solid #334155;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -19,15 +24,20 @@
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #64748b;
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.obs-tab:focus-visible {
|
||||||
|
outline: 2px solid #60a5fa;
|
||||||
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-tab.is-active {
|
.obs-tab.is-active {
|
||||||
border-bottom-color: #0ea5a2;
|
border-bottom-color: #60a5fa;
|
||||||
color: #0f172a;
|
color: #f8fafc;
|
||||||
background: #ffffff;
|
background: #0f172a;
|
||||||
border-color: #dbe2ea;
|
border-color: #334155;
|
||||||
border-bottom-color: #0ea5a2;
|
border-bottom-color: #60a5fa;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-summary-grid {
|
.obs-summary-grid {
|
||||||
@@ -39,30 +49,30 @@
|
|||||||
|
|
||||||
.obs-summary-card {
|
.obs-summary-card {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border: 1px solid #dbe2ea;
|
border: 1px solid #334155;
|
||||||
border-radius: 14px;
|
border-radius: 14px;
|
||||||
background: linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);
|
background: linear-gradient(180deg, #0f172a 0%, #0b1325 100%);
|
||||||
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.06);
|
box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-card-label {
|
.obs-card-label {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: #64748b;
|
color: #cbd5e1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-card-value {
|
.obs-card-value {
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
margin-top: 0.45rem;
|
margin-top: 0.45rem;
|
||||||
color: #0f172a;
|
color: #f8fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-card-value.is-danger {
|
.obs-card-value.is-danger {
|
||||||
color: #b91c1c;
|
color: #f87171;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-card-value.is-ok {
|
.obs-card-value.is-ok {
|
||||||
color: #0f766e;
|
color: #34d399;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-table-wrap {
|
.obs-table-wrap {
|
||||||
@@ -81,9 +91,9 @@
|
|||||||
.obs-table th {
|
.obs-table th {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
background: #f1f5f9;
|
background: #0f172a;
|
||||||
border-bottom: 2px solid #dbe2ea;
|
border-bottom: 2px solid #334155;
|
||||||
color: #1e293b;
|
color: #e2e8f0;
|
||||||
font-size: 0.84rem;
|
font-size: 0.84rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.02em;
|
letter-spacing: 0.02em;
|
||||||
@@ -91,11 +101,12 @@
|
|||||||
|
|
||||||
.obs-table td {
|
.obs-table td {
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border-bottom: 1px solid #e5e7eb;
|
border-bottom: 1px solid #1f2937;
|
||||||
|
color: #f1f5f9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-table tbody tr:hover {
|
.obs-table tbody tr:hover {
|
||||||
background: #f0f9ff;
|
background: #1e293b;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-right {
|
.obs-right {
|
||||||
@@ -105,38 +116,38 @@
|
|||||||
.obs-rate-pill {
|
.obs-rate-pill {
|
||||||
padding: 0.25rem 0.5rem;
|
padding: 0.25rem 0.5rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
font-size: 0.85rem;
|
font-size: 0.88rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-rate-pill.is-good {
|
.obs-rate-pill.is-good {
|
||||||
background: #dcfce7;
|
background: #052e16;
|
||||||
color: #166534;
|
color: #4ade80;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-rate-pill.is-warn {
|
.obs-rate-pill.is-warn {
|
||||||
background: #fef3c7;
|
background: #3f2a0f;
|
||||||
color: #92400e;
|
color: #fbbf24;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-empty-copy {
|
.obs-empty-copy {
|
||||||
color: #94a3b8;
|
color: #cbd5e1;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 2rem;
|
padding: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-zebra {
|
.obs-zebra {
|
||||||
background: #f8fafc;
|
background: #0b1325;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-error-code {
|
.obs-error-code {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: #b91c1c;
|
color: #f87171;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-muted-sm {
|
.obs-muted-sm {
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
color: #64748b;
|
color: #cbd5e1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-small {
|
.obs-small {
|
||||||
@@ -146,15 +157,15 @@
|
|||||||
.obs-audit-summary {
|
.obs-audit-summary {
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
background: #f8fafc;
|
background: #0f172a;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
border: 1px solid #dbe2ea;
|
border: 1px solid #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-audit-summary-line {
|
.obs-audit-summary-line {
|
||||||
margin: 0.25rem 0;
|
margin: 0.25rem 0;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
color: #64748b;
|
color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-filters-grid {
|
.obs-filters-grid {
|
||||||
@@ -166,15 +177,21 @@
|
|||||||
|
|
||||||
.obs-filter-input {
|
.obs-filter-input {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
border: 1px solid #dbe2ea;
|
border: 1px solid #334155;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
background: #fff;
|
background: #111827;
|
||||||
|
color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-filter-input:focus {
|
.obs-filter-input:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #0ea5a2;
|
border-color: #2563eb;
|
||||||
box-shadow: 0 0 0 3px rgba(14, 165, 162, 0.18);
|
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.obs-toggle-code:focus-visible {
|
||||||
|
outline: 2px solid #60a5fa;
|
||||||
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-click-row {
|
.obs-click-row {
|
||||||
@@ -183,11 +200,11 @@
|
|||||||
|
|
||||||
.obs-toggle-code {
|
.obs-toggle-code {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #0f766e;
|
color: #bfdbfe;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-expanded-row {
|
.obs-expanded-row {
|
||||||
background: #f0f9ff;
|
background: #111b2e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.obs-expanded-cell {
|
.obs-expanded-cell {
|
||||||
@@ -196,11 +213,12 @@
|
|||||||
|
|
||||||
.obs-pre {
|
.obs-pre {
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
background: #fff;
|
background: #0b1325;
|
||||||
|
color: #e2e8f0;
|
||||||
padding: 0.75rem;
|
padding: 0.75rem;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
border: 1px solid #dbe2ea;
|
border: 1px solid #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
|
|||||||
+54
-60
@@ -1,6 +1,7 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import './CommandCenterPages.css';
|
||||||
|
|
||||||
function SLA() {
|
function SLA() {
|
||||||
const [slaData, setSlaData] = useState([]);
|
const [slaData, setSlaData] = useState([]);
|
||||||
@@ -66,22 +67,15 @@ function SLA() {
|
|||||||
} : null;
|
} : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container">
|
<div className="container cc-page">
|
||||||
{limitedByRetention && (
|
{limitedByRetention && (
|
||||||
<div style={{
|
<div className="cc-alert">
|
||||||
padding: '1rem',
|
|
||||||
marginBottom: '1rem',
|
|
||||||
backgroundColor: '#fff3cd',
|
|
||||||
border: '1px solid #ffc107',
|
|
||||||
borderRadius: '4px',
|
|
||||||
color: '#856404'
|
|
||||||
}}>
|
|
||||||
⚠️ SLA data limited to {effectiveDays} days (server retention setting). Requested {days} days.
|
⚠️ SLA data limited to {effectiveDays} days (server retention setting). Requested {days} days.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem', flexWrap: 'wrap', gap: '1rem' }}>
|
<div className="cc-page-header">
|
||||||
<h2>SLA Dashboard</h2>
|
<h2 className="cc-page-title">SLA Dashboard</h2>
|
||||||
<div style={{ display: 'flex', gap: '0.5rem' }}>
|
<div className="cc-controls">
|
||||||
{[7, 14, 30, 90].map((d) => (
|
{[7, 14, 30, 90].map((d) => (
|
||||||
<button
|
<button
|
||||||
key={d}
|
key={d}
|
||||||
@@ -95,33 +89,33 @@ function SLA() {
|
|||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
{limitedByRetention && (
|
{limitedByRetention && (
|
||||||
<div style={{ background: '#FFF8E1', color: '#8D6E63', padding: '0.5rem 0.75rem', borderRadius: '6px', border: '1px solid #FFECB3' }}>
|
<div className="cc-alert" style={{ marginBottom: 0 }}>
|
||||||
Limited by data retention ({effectiveDays} days). Increase Retention in Settings to view longer periods.
|
Limited by data retention ({effectiveDays} days). Increase Retention in Settings to view longer periods.
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{overallStats && (
|
{overallStats && (
|
||||||
<div className="stats-grid" style={{ marginBottom: '2rem', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))' }}>
|
<div className="cc-kpi-grid" style={{ marginBottom: '1rem' }}>
|
||||||
<div className="stat" style={{ background: '#f5f5f5', padding: '1.5rem', borderRadius: '8px' }}>
|
<div className="cc-kpi">
|
||||||
<p className="stat-value" style={{ color: '#667eea', fontSize: '2.5rem' }}>{overallStats.totalResources}</p>
|
<p className="cc-kpi-label">Total Resources</p>
|
||||||
<p className="stat-label">Total Resources</p>
|
<p className="cc-kpi-value" style={{ color: '#60a5fa' }}>{overallStats.totalResources}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat" style={{ background: '#f5f5f5', padding: '1.5rem', borderRadius: '8px' }}>
|
<div className="cc-kpi">
|
||||||
<p className="stat-value" style={{ color: '#4caf50', fontSize: '2.5rem' }}>{overallStats.meetingTarget}/{overallStats.totalResources}</p>
|
<p className="cc-kpi-label">Meeting SLA Target</p>
|
||||||
<p className="stat-label">Meeting SLA Target</p>
|
<p className="cc-kpi-value" style={{ color: '#22c55e' }}>{overallStats.meetingTarget}/{overallStats.totalResources}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat" style={{ background: '#f5f5f5', padding: '1.5rem', borderRadius: '8px' }}>
|
<div className="cc-kpi">
|
||||||
<p className="stat-value" style={{ color: '#667eea', fontSize: '2.5rem' }}>{overallStats.avgUptime}%</p>
|
<p className="cc-kpi-label">Average Uptime</p>
|
||||||
<p className="stat-label">Average Uptime</p>
|
<p className="cc-kpi-value" style={{ color: '#a78bfa' }}>{overallStats.avgUptime}%</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat" style={{ background: '#f5f5f5', padding: '1.5rem', borderRadius: '8px' }}>
|
<div className="cc-kpi">
|
||||||
<p className="stat-value" style={{ color: '#ff9800', fontSize: '2.5rem' }}>{overallStats.totalIncidents}</p>
|
<p className="cc-kpi-label">Total Incidents</p>
|
||||||
<p className="stat-label">Total Incidents</p>
|
<p className="cc-kpi-value" style={{ color: '#f59e0b' }}>{overallStats.totalIncidents}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="stat" style={{ background: '#f5f5f5', padding: '1.5rem', borderRadius: '8px' }}>
|
<div className="cc-kpi">
|
||||||
<p className="stat-value" style={{ color: '#f44336', fontSize: '2.5rem' }}>{Math.round(overallStats.totalDowntime)}m</p>
|
<p className="cc-kpi-label">Total Downtime</p>
|
||||||
<p className="stat-label">Total Downtime</p>
|
<p className="cc-kpi-value" style={{ color: '#ef4444' }}>{Math.round(overallStats.totalDowntime)}m</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -132,46 +126,46 @@ function SLA() {
|
|||||||
<p>Add resources to start tracking SLA metrics</p>
|
<p>Add resources to start tracking SLA metrics</p>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div style={{ overflowX: 'auto' }}>
|
<div className="cc-table-wrap">
|
||||||
<table style={{ width: '100%', borderCollapse: 'collapse', background: '#fff', borderRadius: '8px', overflow: 'hidden', boxShadow: '0 2px 8px rgba(0,0,0,0.1)' }}>
|
<table className="cc-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr style={{ background: '#667eea', color: 'white' }}>
|
<tr>
|
||||||
<th style={{ padding: '1rem', textAlign: 'left' }}>Resource</th>
|
<th>Resource</th>
|
||||||
<th style={{ padding: '1rem', textAlign: 'center' }}>Target</th>
|
<th style={{ textAlign: 'center' }}>Target</th>
|
||||||
<th style={{ padding: '1rem', textAlign: 'center' }}>Actual</th>
|
<th style={{ textAlign: 'center' }}>Actual</th>
|
||||||
<th style={{ padding: '1rem', textAlign: 'center' }}>Status</th>
|
<th style={{ textAlign: 'center' }}>Status</th>
|
||||||
<th style={{ padding: '1rem', textAlign: 'center' }}>Checks</th>
|
<th style={{ textAlign: 'center' }}>Checks</th>
|
||||||
<th style={{ padding: '1rem', textAlign: 'center' }}>Incidents</th>
|
<th style={{ textAlign: 'center' }}>Incidents</th>
|
||||||
<th style={{ padding: '1rem', textAlign: 'center' }}>Downtime</th>
|
<th style={{ textAlign: 'center' }}>Downtime</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{slaData.map((item, idx) => (
|
{slaData.map((item, idx) => (
|
||||||
<tr key={item.resource_id} style={{ borderBottom: '1px solid #eee', background: idx % 2 === 0 ? '#fafafa' : '#fff' }}>
|
<tr key={item.resource_id} style={{ background: idx % 2 === 0 ? '#0b1325' : '#0f172a' }}>
|
||||||
<td style={{ padding: '1rem', fontWeight: 'bold' }}>{item.resource_name}</td>
|
<td style={{ fontWeight: 'bold' }}>{item.resource_name}</td>
|
||||||
<td style={{ padding: '1rem', textAlign: 'center' }}>{item.sla_target}%</td>
|
<td style={{ textAlign: 'center' }}>{item.sla_target}%</td>
|
||||||
<td style={{ padding: '1rem', textAlign: 'center', fontWeight: 'bold', color: item.meets_target ? '#4caf50' : '#f44336' }}>
|
<td style={{ textAlign: 'center', fontWeight: 'bold', color: item.meets_target ? '#22c55e' : '#ef4444' }}>
|
||||||
{item.actual_uptime}%
|
{item.actual_uptime}%
|
||||||
</td>
|
</td>
|
||||||
<td style={{ padding: '1rem', textAlign: 'center' }}>
|
<td style={{ textAlign: 'center' }}>
|
||||||
<span style={{
|
<span style={{
|
||||||
padding: '0.25rem 0.75rem',
|
padding: '0.25rem 0.75rem',
|
||||||
borderRadius: '12px',
|
borderRadius: '12px',
|
||||||
background: item.meets_target ? '#e8f5e9' : '#ffebee',
|
background: item.meets_target ? '#052e16' : '#3f1d1d',
|
||||||
color: item.meets_target ? '#2e7d32' : '#c62828',
|
color: item.meets_target ? '#4ade80' : '#fda4af',
|
||||||
fontSize: '0.875rem',
|
fontSize: '0.875rem',
|
||||||
fontWeight: 'bold'
|
fontWeight: 'bold'
|
||||||
}}>
|
}}>
|
||||||
{item.meets_target ? '✓ PASS' : '✗ FAIL'}
|
{item.meets_target ? '✓ PASS' : '✗ FAIL'}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
<td style={{ padding: '1rem', textAlign: 'center' }}>
|
<td style={{ textAlign: 'center' }}>
|
||||||
{item.successful_checks}/{item.total_checks}
|
{item.successful_checks}/{item.total_checks}
|
||||||
</td>
|
</td>
|
||||||
<td style={{ padding: '1rem', textAlign: 'center', color: item.incidents > 0 ? '#ff9800' : '#666' }}>
|
<td style={{ textAlign: 'center', color: item.incidents > 0 ? '#f59e0b' : '#94a3b8' }}>
|
||||||
{item.incidents}
|
{item.incidents}
|
||||||
</td>
|
</td>
|
||||||
<td style={{ padding: '1rem', textAlign: 'center' }}>
|
<td style={{ textAlign: 'center' }}>
|
||||||
{item.downtime_minutes > 0 ? `${item.downtime_minutes}m` : '-'}
|
{item.downtime_minutes > 0 ? `${item.downtime_minutes}m` : '-'}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -180,7 +174,7 @@ function SLA() {
|
|||||||
</table>
|
</table>
|
||||||
|
|
||||||
{totalItems > pageSize && (
|
{totalItems > pageSize && (
|
||||||
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', gap: '1rem', marginTop: '2rem', padding: '1.5rem', background: '#f5f5f5', borderRadius: '8px' }}>
|
<div className="cc-pagination" style={{ marginTop: '1rem' }}>
|
||||||
<button
|
<button
|
||||||
className="btn btn-secondary"
|
className="btn btn-secondary"
|
||||||
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
|
onClick={() => setCurrentPage(Math.max(1, currentPage - 1))}
|
||||||
@@ -188,8 +182,8 @@ function SLA() {
|
|||||||
>
|
>
|
||||||
← Previous
|
← Previous
|
||||||
</button>
|
</button>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
|
<div className="cc-controls">
|
||||||
<span style={{ color: '#666', fontSize: '0.9rem' }}>Page</span>
|
<span>Page</span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={currentPage}
|
value={currentPage}
|
||||||
@@ -197,14 +191,14 @@ function SLA() {
|
|||||||
const page = Math.max(1, Math.min(Math.ceil(totalItems / pageSize), parseInt(e.target.value) || 1));
|
const page = Math.max(1, Math.min(Math.ceil(totalItems / pageSize), parseInt(e.target.value) || 1));
|
||||||
setCurrentPage(page);
|
setCurrentPage(page);
|
||||||
}}
|
}}
|
||||||
style={{ width: '50px', padding: '0.5rem', border: '1px solid #ddd', borderRadius: '4px', textAlign: 'center' }}
|
className="cc-input-sm"
|
||||||
/>
|
/>
|
||||||
<span style={{ color: '#666', fontSize: '0.9rem' }}>of {Math.ceil(totalItems / pageSize)}</span>
|
<span>of {Math.ceil(totalItems / pageSize)}</span>
|
||||||
</div>
|
</div>
|
||||||
<select
|
<select
|
||||||
value={pageSize}
|
value={pageSize}
|
||||||
onChange={(e) => { setPageSize(parseInt(e.target.value)); setCurrentPage(1); }}
|
onChange={(e) => { setPageSize(parseInt(e.target.value)); setCurrentPage(1); }}
|
||||||
style={{ padding: '0.5rem', border: '1px solid #ddd', borderRadius: '4px', background: '#fff' }}
|
className="cc-select-sm"
|
||||||
>
|
>
|
||||||
<option value={5}>5 per page</option>
|
<option value={5}>5 per page</option>
|
||||||
<option value={10}>10 per page</option>
|
<option value={10}>10 per page</option>
|
||||||
@@ -223,13 +217,13 @@ function SLA() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div style={{ marginTop: '2rem', padding: '1.5rem', background: '#f5f5f5', borderRadius: '8px' }}>
|
<div className="cc-surface" style={{ marginTop: '1rem' }}>
|
||||||
<h3 style={{ marginTop: 0, color: '#667eea' }}>SLA Report Summary</h3>
|
<h3 className="history-resource-title">SLA Report Summary</h3>
|
||||||
<p style={{ color: '#666', lineHeight: '1.6' }}>
|
<p style={{ color: '#cbd5e1', lineHeight: '1.6' }}>
|
||||||
This report shows the Service Level Agreement (SLA) performance for all monitored resources over the last <strong>{effectiveDays} days</strong>.
|
This report shows the Service Level Agreement (SLA) performance for all monitored resources over the last <strong>{effectiveDays} days</strong>.
|
||||||
Resources meeting their target uptime are marked as <strong style={{ color: '#4caf50' }}>PASS</strong>, while those below target are marked as <strong style={{ color: '#f44336' }}>FAIL</strong>.
|
Resources meeting their target uptime are marked as <strong style={{ color: '#22c55e' }}>PASS</strong>, while those below target are marked as <strong style={{ color: '#ef4444' }}>FAIL</strong>.
|
||||||
</p>
|
</p>
|
||||||
<p style={{ color: '#666', lineHeight: '1.6', marginBottom: 0 }}>
|
<p style={{ color: '#cbd5e1', lineHeight: '1.6', marginBottom: 0 }}>
|
||||||
Overall performance: <strong>{overallStats?.meetingTarget || 0}</strong> out of <strong>{overallStats?.totalResources || 0}</strong> resources ({overallStats ? Math.round((overallStats.meetingTarget / overallStats.totalResources) * 100) : 0}%) are meeting their SLA targets.
|
Overall performance: <strong>{overallStats?.meetingTarget || 0}</strong> out of <strong>{overallStats?.totalResources || 0}</strong> resources ({overallStats ? Math.round((overallStats.meetingTarget / overallStats.totalResources) * 100) : 0}%) are meeting their SLA targets.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,33 +1,39 @@
|
|||||||
.settings-wizard {
|
.settings-wizard {
|
||||||
max-width: 900px;
|
max-width: 980px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-wizard > h2 {
|
||||||
|
color: #f8fafc;
|
||||||
|
text-shadow: 0 1px 0 rgba(15, 23, 42, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-description {
|
.settings-description {
|
||||||
color: #666;
|
color: #cbd5e1;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 1.4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-section {
|
.settings-section {
|
||||||
background: white;
|
background: rgba(15, 23, 42, 0.85);
|
||||||
border-radius: 8px;
|
border-radius: 12px;
|
||||||
padding: 2rem;
|
padding: 1.35rem;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1rem;
|
||||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
border: 1px solid #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-header {
|
.settings-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1rem;
|
||||||
padding-bottom: 1rem;
|
padding-bottom: 0.8rem;
|
||||||
border-bottom: 2px solid #f0f0f0;
|
border-bottom: 1px solid #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-header h3 {
|
.settings-header h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
color: #333;
|
color: #f8fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-form {
|
.settings-form {
|
||||||
@@ -43,23 +49,43 @@
|
|||||||
|
|
||||||
.form-group small {
|
.form-group small {
|
||||||
display: block;
|
display: block;
|
||||||
color: #999;
|
color: #cbd5e1;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin-top: 0.25rem;
|
margin-top: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.settings-wizard .form-group label {
|
||||||
|
color: #e2e8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-wizard .form-group input,
|
||||||
|
.settings-wizard .form-group select,
|
||||||
|
.settings-wizard .form-group textarea {
|
||||||
|
background: #0b1325;
|
||||||
|
color: #f8fafc;
|
||||||
|
border: 1px solid #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-wizard .form-group input:focus,
|
||||||
|
.settings-wizard .form-group select:focus,
|
||||||
|
.settings-wizard .form-group textarea:focus {
|
||||||
|
border-color: #2563eb;
|
||||||
|
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.28);
|
||||||
|
}
|
||||||
|
|
||||||
.info-box {
|
.info-box {
|
||||||
background: #f8f9fa;
|
background: #0b1325;
|
||||||
border-left: 4px solid #667eea;
|
border-left: 4px solid #60a5fa;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
margin: 1.5rem 0;
|
margin: 1.5rem 0;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
|
border: 1px solid #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-box strong {
|
.info-box strong {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 0.5rem;
|
margin-bottom: 0.5rem;
|
||||||
color: #333;
|
color: #f8fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-box ol {
|
.info-box ol {
|
||||||
@@ -69,11 +95,11 @@
|
|||||||
|
|
||||||
.info-box li {
|
.info-box li {
|
||||||
margin: 0.25rem 0;
|
margin: 0.25rem 0;
|
||||||
color: #666;
|
color: #e2e8f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.info-box a {
|
.info-box a {
|
||||||
color: #667eea;
|
color: #bfdbfe;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,12 +108,14 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.info-box pre {
|
.info-box pre {
|
||||||
background: white;
|
background: #111827;
|
||||||
|
color: #f8fafc;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border-radius: 4px;
|
border-radius: 8px;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
margin-top: 0.5rem;
|
margin-top: 0.5rem;
|
||||||
|
border: 1px solid #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-switch {
|
.toggle-switch {
|
||||||
@@ -110,7 +138,7 @@
|
|||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
background-color: #ccc;
|
background-color: #334155;
|
||||||
transition: .4s;
|
transition: .4s;
|
||||||
border-radius: 34px;
|
border-radius: 34px;
|
||||||
}
|
}
|
||||||
@@ -122,13 +150,17 @@
|
|||||||
width: 26px;
|
width: 26px;
|
||||||
left: 4px;
|
left: 4px;
|
||||||
bottom: 4px;
|
bottom: 4px;
|
||||||
background-color: white;
|
background-color: #e2e8f0;
|
||||||
transition: .4s;
|
transition: .4s;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-switch input:checked + .toggle-slider {
|
.toggle-switch input:checked + .toggle-slider {
|
||||||
background-color: #667eea;
|
background-color: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch input:focus + .toggle-slider {
|
||||||
|
box-shadow: 0 0 0 3px rgba(96, 165, 250, 0.28);
|
||||||
}
|
}
|
||||||
|
|
||||||
.toggle-switch input:checked + .toggle-slider:before {
|
.toggle-switch input:checked + .toggle-slider:before {
|
||||||
@@ -136,13 +168,19 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary {
|
.btn-secondary {
|
||||||
background: #6c757d;
|
background: #1e293b;
|
||||||
color: white;
|
color: #f8fafc;
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
|
border: 1px solid #334155;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary:hover:not(:disabled) {
|
.btn-secondary:hover:not(:disabled) {
|
||||||
background: #5a6268;
|
background: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-secondary:focus-visible {
|
||||||
|
outline: 2px solid #60a5fa;
|
||||||
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-secondary:disabled {
|
.btn-secondary:disabled {
|
||||||
@@ -157,15 +195,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.test-result.success {
|
.test-result.success {
|
||||||
background: #d4edda;
|
background: #052e16;
|
||||||
color: #155724;
|
color: #86efac;
|
||||||
border: 1px solid #c3e6cb;
|
border: 1px solid #166534;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-result.error {
|
.test-result.error {
|
||||||
background: #f8d7da;
|
background: #3f1d1d;
|
||||||
color: #721c24;
|
color: #fda4af;
|
||||||
border: 1px solid #f5c6cb;
|
border: 1px solid #7f1d1d;
|
||||||
}
|
}
|
||||||
|
|
||||||
.test-result strong {
|
.test-result strong {
|
||||||
@@ -201,15 +239,15 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.save-status.success {
|
.save-status.success {
|
||||||
background: #4caf50;
|
background: #166534;
|
||||||
color: white;
|
color: #dcfce7;
|
||||||
border-left: 4px solid #388e3c;
|
border-left: 4px solid #22c55e;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-status.error {
|
.save-status.error {
|
||||||
background: #f44336;
|
background: #7f1d1d;
|
||||||
color: white;
|
color: #fee2e2;
|
||||||
border-left: 4px solid #d32f2f;
|
border-left: 4px solid #ef4444;
|
||||||
}
|
}
|
||||||
|
|
||||||
.save-status strong {
|
.save-status strong {
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ body {
|
|||||||
font-family: 'Manrope', 'IBM Plex Sans', 'Segoe UI', sans-serif;
|
font-family: 'Manrope', 'IBM Plex Sans', 'Segoe UI', sans-serif;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
color: #0f172a;
|
color: #e2e8f0;
|
||||||
background-color: #f8fafc;
|
background-color: #020617;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|||||||
@@ -345,6 +345,40 @@ app.patch('/api/resources/:id/group', (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Toggle maintenance mode without overwriting other resource fields
|
||||||
|
app.patch('/api/resources/:id/maintenance-mode', (req, res) => {
|
||||||
|
try {
|
||||||
|
const id = parseInt(req.params.id, 10);
|
||||||
|
const { maintenance_mode } = req.body;
|
||||||
|
|
||||||
|
if (!Number.isInteger(id)) {
|
||||||
|
return res.status(400).json({ error: 'Invalid resource id' });
|
||||||
|
}
|
||||||
|
if (typeof maintenance_mode !== 'boolean') {
|
||||||
|
return res.status(400).json({ error: 'maintenance_mode must be a boolean' });
|
||||||
|
}
|
||||||
|
|
||||||
|
const stmt = db.prepare('UPDATE resources SET maintenance_mode = ? WHERE id = ?');
|
||||||
|
const info = stmt.run(maintenance_mode ? 1 : 0, id);
|
||||||
|
|
||||||
|
if (info.changes === 0) {
|
||||||
|
return res.status(404).json({ error: 'Resource not found' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate related cache entries and broadcast live update
|
||||||
|
cache.invalidatePattern('history:');
|
||||||
|
cache.invalidatePattern('sla:');
|
||||||
|
if (global.broadcastDashboardUpdate) {
|
||||||
|
try { global.broadcastDashboardUpdate(); } catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const updated = db.prepare('SELECT * FROM resources WHERE id = ?').get(id);
|
||||||
|
res.json(updated);
|
||||||
|
} catch (err) {
|
||||||
|
res.status(500).json({ error: 'Failed to update maintenance mode', details: String(err) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Delete resource
|
// Delete resource
|
||||||
app.delete('/api/resources/:id', (req, res) => {
|
app.delete('/api/resources/:id', (req, res) => {
|
||||||
const resource = db.prepare('SELECT name, url FROM resources WHERE id = ?').get(req.params.id);
|
const resource = db.prepare('SELECT name, url FROM resources WHERE id = ?').get(req.params.id);
|
||||||
|
|||||||
Reference in new issue
Block a user