715 lines
44 KiB
JavaScript
715 lines
44 KiB
JavaScript
|
|
var __esDecorate = (this && this.__esDecorate) || function (ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
||
|
|
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
||
|
|
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
||
|
|
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
||
|
|
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
||
|
|
var _, done = false;
|
||
|
|
for (var i = decorators.length - 1; i >= 0; i--) {
|
||
|
|
var context = {};
|
||
|
|
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
||
|
|
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
||
|
|
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
||
|
|
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
||
|
|
if (kind === "accessor") {
|
||
|
|
if (result === void 0) continue;
|
||
|
|
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
||
|
|
if (_ = accept(result.get)) descriptor.get = _;
|
||
|
|
if (_ = accept(result.set)) descriptor.set = _;
|
||
|
|
if (_ = accept(result.init)) initializers.unshift(_);
|
||
|
|
}
|
||
|
|
else if (_ = accept(result)) {
|
||
|
|
if (kind === "field") initializers.unshift(_);
|
||
|
|
else descriptor[key] = _;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
||
|
|
done = true;
|
||
|
|
};
|
||
|
|
var __runInitializers = (this && this.__runInitializers) || function (thisArg, initializers, value) {
|
||
|
|
var useValue = arguments.length > 2;
|
||
|
|
for (var i = 0; i < initializers.length; i++) {
|
||
|
|
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
||
|
|
}
|
||
|
|
return useValue ? value : void 0;
|
||
|
|
};
|
||
|
|
import * as plugins from '../../plugins.js';
|
||
|
|
import { DeesElement, property, html, customElement, css, cssManager, unsafeCSS, state, } from '@design.estate/dees-element';
|
||
|
|
import * as sharedStyles from '../../styles/shared.styles.js';
|
||
|
|
import { demoFunc } from './upladmin-dashboard.demo.js';
|
||
|
|
let UpladminDashboard = (() => {
|
||
|
|
let _classDecorators = [customElement('upladmin-dashboard')];
|
||
|
|
let _classDescriptor;
|
||
|
|
let _classExtraInitializers = [];
|
||
|
|
let _classThis;
|
||
|
|
let _classSuper = DeesElement;
|
||
|
|
let _monitors_decorators;
|
||
|
|
let _monitors_initializers = [];
|
||
|
|
let _monitors_extraInitializers = [];
|
||
|
|
let _incidents_decorators;
|
||
|
|
let _incidents_initializers = [];
|
||
|
|
let _incidents_extraInitializers = [];
|
||
|
|
let _overallStatus_decorators;
|
||
|
|
let _overallStatus_initializers = [];
|
||
|
|
let _overallStatus_extraInitializers = [];
|
||
|
|
let _loading_decorators;
|
||
|
|
let _loading_initializers = [];
|
||
|
|
let _loading_extraInitializers = [];
|
||
|
|
var UpladminDashboard = class extends _classSuper {
|
||
|
|
static { _classThis = this; }
|
||
|
|
static {
|
||
|
|
const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
|
||
|
|
_monitors_decorators = [property({ type: Array })];
|
||
|
|
_incidents_decorators = [property({ type: Array })];
|
||
|
|
_overallStatus_decorators = [property({ type: Object })];
|
||
|
|
_loading_decorators = [property({ type: Boolean })];
|
||
|
|
__esDecorate(this, null, _monitors_decorators, { kind: "accessor", name: "monitors", static: false, private: false, access: { has: obj => "monitors" in obj, get: obj => obj.monitors, set: (obj, value) => { obj.monitors = value; } }, metadata: _metadata }, _monitors_initializers, _monitors_extraInitializers);
|
||
|
|
__esDecorate(this, null, _incidents_decorators, { kind: "accessor", name: "incidents", static: false, private: false, access: { has: obj => "incidents" in obj, get: obj => obj.incidents, set: (obj, value) => { obj.incidents = value; } }, metadata: _metadata }, _incidents_initializers, _incidents_extraInitializers);
|
||
|
|
__esDecorate(this, null, _overallStatus_decorators, { kind: "accessor", name: "overallStatus", static: false, private: false, access: { has: obj => "overallStatus" in obj, get: obj => obj.overallStatus, set: (obj, value) => { obj.overallStatus = value; } }, metadata: _metadata }, _overallStatus_initializers, _overallStatus_extraInitializers);
|
||
|
|
__esDecorate(this, null, _loading_decorators, { kind: "accessor", name: "loading", static: false, private: false, access: { has: obj => "loading" in obj, get: obj => obj.loading, set: (obj, value) => { obj.loading = value; } }, metadata: _metadata }, _loading_initializers, _loading_extraInitializers);
|
||
|
|
__esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
|
||
|
|
UpladminDashboard = _classThis = _classDescriptor.value;
|
||
|
|
if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
||
|
|
}
|
||
|
|
static demo = demoFunc;
|
||
|
|
#monitors_accessor_storage = __runInitializers(this, _monitors_initializers, []);
|
||
|
|
get monitors() { return this.#monitors_accessor_storage; }
|
||
|
|
set monitors(value) { this.#monitors_accessor_storage = value; }
|
||
|
|
#incidents_accessor_storage = (__runInitializers(this, _monitors_extraInitializers), __runInitializers(this, _incidents_initializers, []));
|
||
|
|
get incidents() { return this.#incidents_accessor_storage; }
|
||
|
|
set incidents(value) { this.#incidents_accessor_storage = value; }
|
||
|
|
#overallStatus_accessor_storage = (__runInitializers(this, _incidents_extraInitializers), __runInitializers(this, _overallStatus_initializers, null));
|
||
|
|
get overallStatus() { return this.#overallStatus_accessor_storage; }
|
||
|
|
set overallStatus(value) { this.#overallStatus_accessor_storage = value; }
|
||
|
|
#loading_accessor_storage = (__runInitializers(this, _overallStatus_extraInitializers), __runInitializers(this, _loading_initializers, false));
|
||
|
|
get loading() { return this.#loading_accessor_storage; }
|
||
|
|
set loading(value) { this.#loading_accessor_storage = value; }
|
||
|
|
static styles = [
|
||
|
|
plugins.domtools.elementBasic.staticStyles,
|
||
|
|
sharedStyles.commonStyles,
|
||
|
|
css `
|
||
|
|
:host {
|
||
|
|
display: block;
|
||
|
|
font-family: ${unsafeCSS(sharedStyles.fonts.base)};
|
||
|
|
}
|
||
|
|
|
||
|
|
.dashboard {
|
||
|
|
display: grid;
|
||
|
|
gap: ${unsafeCSS(sharedStyles.spacing.lg)};
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Overall Status Banner */
|
||
|
|
.status-banner {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: ${unsafeCSS(sharedStyles.spacing.md)};
|
||
|
|
padding: ${unsafeCSS(sharedStyles.spacing.lg)};
|
||
|
|
border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)};
|
||
|
|
border: 1px solid;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-banner.operational {
|
||
|
|
background: ${cssManager.bdTheme('rgba(34, 197, 94, 0.1)', 'rgba(34, 197, 94, 0.15)')};
|
||
|
|
border-color: ${sharedStyles.colors.status.operational};
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-banner.degraded {
|
||
|
|
background: ${cssManager.bdTheme('rgba(234, 179, 8, 0.1)', 'rgba(234, 179, 8, 0.15)')};
|
||
|
|
border-color: ${sharedStyles.colors.status.degraded};
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-banner.partial_outage {
|
||
|
|
background: ${cssManager.bdTheme('rgba(249, 115, 22, 0.1)', 'rgba(249, 115, 22, 0.15)')};
|
||
|
|
border-color: ${sharedStyles.colors.status.partialOutage};
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-banner.major_outage {
|
||
|
|
background: ${cssManager.bdTheme('rgba(239, 68, 68, 0.1)', 'rgba(239, 68, 68, 0.15)')};
|
||
|
|
border-color: ${sharedStyles.colors.status.majorOutage};
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-banner.maintenance {
|
||
|
|
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.1)', 'rgba(59, 130, 246, 0.15)')};
|
||
|
|
border-color: ${sharedStyles.colors.status.maintenance};
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-indicator {
|
||
|
|
width: 48px;
|
||
|
|
height: 48px;
|
||
|
|
border-radius: 50%;
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
flex-shrink: 0;
|
||
|
|
color: white;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-indicator dees-icon {
|
||
|
|
--icon-size: 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-indicator.operational { background: ${sharedStyles.colors.status.operational}; }
|
||
|
|
.status-indicator.degraded { background: ${sharedStyles.colors.status.degraded}; }
|
||
|
|
.status-indicator.partial_outage { background: ${sharedStyles.colors.status.partialOutage}; }
|
||
|
|
.status-indicator.major_outage { background: ${sharedStyles.colors.status.majorOutage}; }
|
||
|
|
.status-indicator.maintenance { background: ${sharedStyles.colors.status.maintenance}; }
|
||
|
|
|
||
|
|
.status-content {
|
||
|
|
flex: 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-title {
|
||
|
|
font-size: 18px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: ${sharedStyles.colors.text.primary};
|
||
|
|
margin-bottom: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-message {
|
||
|
|
font-size: 14px;
|
||
|
|
color: ${sharedStyles.colors.text.secondary};
|
||
|
|
}
|
||
|
|
|
||
|
|
.status-meta {
|
||
|
|
font-size: 12px;
|
||
|
|
color: ${sharedStyles.colors.text.muted};
|
||
|
|
margin-top: 4px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Stats Grid Container */
|
||
|
|
.stats-container {
|
||
|
|
margin: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
dees-statsgrid {
|
||
|
|
--tile-padding: 20px;
|
||
|
|
--value-font-size: 28px;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Content Grid */
|
||
|
|
.content-grid {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: 1fr 1fr;
|
||
|
|
gap: ${unsafeCSS(sharedStyles.spacing.lg)};
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-width: 900px) {
|
||
|
|
.content-grid {
|
||
|
|
grid-template-columns: 1fr;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Section Card */
|
||
|
|
.section-card {
|
||
|
|
background: ${sharedStyles.colors.background.secondary};
|
||
|
|
border: 1px solid ${sharedStyles.colors.border.default};
|
||
|
|
border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)};
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
|
||
|
|
.section-header {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: space-between;
|
||
|
|
padding: ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.lg)};
|
||
|
|
border-bottom: 1px solid ${sharedStyles.colors.border.default};
|
||
|
|
}
|
||
|
|
|
||
|
|
.section-title {
|
||
|
|
font-size: 15px;
|
||
|
|
font-weight: 600;
|
||
|
|
color: ${sharedStyles.colors.text.primary};
|
||
|
|
}
|
||
|
|
|
||
|
|
.section-action {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 4px;
|
||
|
|
font-size: 13px;
|
||
|
|
font-weight: 500;
|
||
|
|
color: ${sharedStyles.colors.accent.primary};
|
||
|
|
background: none;
|
||
|
|
border: none;
|
||
|
|
cursor: pointer;
|
||
|
|
transition: opacity ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
|
||
|
|
}
|
||
|
|
|
||
|
|
.section-action:hover {
|
||
|
|
opacity: 0.8;
|
||
|
|
}
|
||
|
|
|
||
|
|
.section-action dees-icon {
|
||
|
|
--icon-size: 14px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.section-body {
|
||
|
|
padding: ${unsafeCSS(sharedStyles.spacing.md)};
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Status By Category */
|
||
|
|
.category-list {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: ${unsafeCSS(sharedStyles.spacing.sm)};
|
||
|
|
}
|
||
|
|
|
||
|
|
.category-item {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: ${unsafeCSS(sharedStyles.spacing.md)};
|
||
|
|
padding: ${unsafeCSS(sharedStyles.spacing.sm)} ${unsafeCSS(sharedStyles.spacing.md)};
|
||
|
|
background: ${sharedStyles.colors.background.primary};
|
||
|
|
border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
|
||
|
|
}
|
||
|
|
|
||
|
|
.category-name {
|
||
|
|
flex: 1;
|
||
|
|
font-size: 14px;
|
||
|
|
font-weight: 500;
|
||
|
|
color: ${sharedStyles.colors.text.primary};
|
||
|
|
}
|
||
|
|
|
||
|
|
.category-stats {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: ${unsafeCSS(sharedStyles.spacing.sm)};
|
||
|
|
}
|
||
|
|
|
||
|
|
.category-count {
|
||
|
|
font-size: 13px;
|
||
|
|
color: ${sharedStyles.colors.text.muted};
|
||
|
|
}
|
||
|
|
|
||
|
|
.category-bar {
|
||
|
|
width: 80px;
|
||
|
|
height: 6px;
|
||
|
|
background: ${sharedStyles.colors.background.muted};
|
||
|
|
border-radius: 3px;
|
||
|
|
overflow: hidden;
|
||
|
|
}
|
||
|
|
|
||
|
|
.category-bar-fill {
|
||
|
|
height: 100%;
|
||
|
|
background: ${sharedStyles.colors.status.operational};
|
||
|
|
border-radius: 3px;
|
||
|
|
transition: width ${unsafeCSS(sharedStyles.durations.normal)} ${unsafeCSS(sharedStyles.easings.default)};
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Active Incidents */
|
||
|
|
.incident-list {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
gap: ${unsafeCSS(sharedStyles.spacing.sm)};
|
||
|
|
}
|
||
|
|
|
||
|
|
.incident-item {
|
||
|
|
display: flex;
|
||
|
|
align-items: flex-start;
|
||
|
|
gap: ${unsafeCSS(sharedStyles.spacing.md)};
|
||
|
|
padding: ${unsafeCSS(sharedStyles.spacing.md)};
|
||
|
|
background: ${sharedStyles.colors.background.primary};
|
||
|
|
border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
|
||
|
|
border-left: 3px solid;
|
||
|
|
cursor: pointer;
|
||
|
|
transition: background ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
|
||
|
|
}
|
||
|
|
|
||
|
|
.incident-item:hover {
|
||
|
|
background: ${sharedStyles.colors.background.muted};
|
||
|
|
}
|
||
|
|
|
||
|
|
.incident-item.critical { border-left-color: ${sharedStyles.colors.status.majorOutage}; }
|
||
|
|
.incident-item.major { border-left-color: ${sharedStyles.colors.status.partialOutage}; }
|
||
|
|
.incident-item.minor { border-left-color: ${sharedStyles.colors.status.degraded}; }
|
||
|
|
.incident-item.maintenance { border-left-color: ${sharedStyles.colors.status.maintenance}; }
|
||
|
|
|
||
|
|
.incident-content {
|
||
|
|
flex: 1;
|
||
|
|
min-width: 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
.incident-title {
|
||
|
|
font-size: 14px;
|
||
|
|
font-weight: 500;
|
||
|
|
color: ${sharedStyles.colors.text.primary};
|
||
|
|
margin-bottom: 4px;
|
||
|
|
white-space: nowrap;
|
||
|
|
overflow: hidden;
|
||
|
|
text-overflow: ellipsis;
|
||
|
|
}
|
||
|
|
|
||
|
|
.incident-meta {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: ${unsafeCSS(sharedStyles.spacing.sm)};
|
||
|
|
font-size: 12px;
|
||
|
|
color: ${sharedStyles.colors.text.muted};
|
||
|
|
}
|
||
|
|
|
||
|
|
.incident-status {
|
||
|
|
display: inline-flex;
|
||
|
|
align-items: center;
|
||
|
|
gap: 4px;
|
||
|
|
padding: 2px 8px;
|
||
|
|
font-size: 10px;
|
||
|
|
font-weight: 600;
|
||
|
|
text-transform: uppercase;
|
||
|
|
border-radius: 9999px;
|
||
|
|
background: ${sharedStyles.colors.background.muted};
|
||
|
|
color: ${sharedStyles.colors.text.secondary};
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Quick Actions */
|
||
|
|
.quick-actions {
|
||
|
|
display: grid;
|
||
|
|
grid-template-columns: repeat(2, 1fr);
|
||
|
|
gap: ${unsafeCSS(sharedStyles.spacing.sm)};
|
||
|
|
}
|
||
|
|
|
||
|
|
.quick-action {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
gap: 8px;
|
||
|
|
padding: ${unsafeCSS(sharedStyles.spacing.lg)};
|
||
|
|
background: ${sharedStyles.colors.background.primary};
|
||
|
|
border: 1px solid ${sharedStyles.colors.border.default};
|
||
|
|
border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
|
||
|
|
cursor: pointer;
|
||
|
|
transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
|
||
|
|
color: ${sharedStyles.colors.text.secondary};
|
||
|
|
}
|
||
|
|
|
||
|
|
.quick-action:hover {
|
||
|
|
background: ${sharedStyles.colors.background.muted};
|
||
|
|
border-color: ${sharedStyles.colors.border.strong};
|
||
|
|
color: ${sharedStyles.colors.text.primary};
|
||
|
|
}
|
||
|
|
|
||
|
|
.quick-action-icon {
|
||
|
|
display: flex;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
.quick-action-icon dees-icon {
|
||
|
|
--icon-size: 24px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.quick-action-label {
|
||
|
|
font-size: 13px;
|
||
|
|
font-weight: 500;
|
||
|
|
color: ${sharedStyles.colors.text.primary};
|
||
|
|
text-align: center;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Empty State */
|
||
|
|
.empty-state {
|
||
|
|
display: flex;
|
||
|
|
flex-direction: column;
|
||
|
|
align-items: center;
|
||
|
|
justify-content: center;
|
||
|
|
padding: ${unsafeCSS(sharedStyles.spacing.xl)};
|
||
|
|
text-align: center;
|
||
|
|
color: ${sharedStyles.colors.text.muted};
|
||
|
|
}
|
||
|
|
|
||
|
|
.empty-icon {
|
||
|
|
margin-bottom: ${unsafeCSS(sharedStyles.spacing.sm)};
|
||
|
|
opacity: 0.5;
|
||
|
|
}
|
||
|
|
|
||
|
|
.empty-icon dees-icon {
|
||
|
|
--icon-size: 32px;
|
||
|
|
}
|
||
|
|
|
||
|
|
.empty-text {
|
||
|
|
font-size: 14px;
|
||
|
|
color: ${sharedStyles.colors.text.muted};
|
||
|
|
}
|
||
|
|
`
|
||
|
|
];
|
||
|
|
get statsTiles() {
|
||
|
|
const activeIncidents = this.incidents.filter(i => !['resolved', 'postmortem'].includes(i.status));
|
||
|
|
const operationalCount = this.monitors.filter(m => m.currentStatus === 'operational').length;
|
||
|
|
const degradedCount = this.monitors.filter(m => m.currentStatus === 'degraded').length;
|
||
|
|
const outageCount = this.monitors.filter(m => ['partial_outage', 'major_outage'].includes(m.currentStatus)).length;
|
||
|
|
const avgUptime = this.monitors.length > 0
|
||
|
|
? this.monitors.reduce((sum, m) => sum + m.uptime30d, 0) / this.monitors.length
|
||
|
|
: 100;
|
||
|
|
const uptimeColor = avgUptime >= 99.9
|
||
|
|
? sharedStyles.colors.status.operational.cssText
|
||
|
|
: avgUptime >= 99
|
||
|
|
? sharedStyles.colors.status.degraded.cssText
|
||
|
|
: sharedStyles.colors.status.majorOutage.cssText;
|
||
|
|
return [
|
||
|
|
{
|
||
|
|
id: 'uptime',
|
||
|
|
title: 'Average Uptime (30d)',
|
||
|
|
value: avgUptime,
|
||
|
|
unit: '%',
|
||
|
|
type: 'percentage',
|
||
|
|
color: uptimeColor,
|
||
|
|
icon: 'lucide:barChart3',
|
||
|
|
description: avgUptime >= 99.9 ? 'Excellent' : avgUptime >= 99 ? 'Good' : 'Needs attention',
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'operational',
|
||
|
|
title: 'Operational Services',
|
||
|
|
value: operationalCount,
|
||
|
|
type: 'number',
|
||
|
|
icon: 'lucide:checkCircle',
|
||
|
|
color: sharedStyles.colors.status.operational.cssText,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'issues',
|
||
|
|
title: 'Services with Issues',
|
||
|
|
value: degradedCount + outageCount,
|
||
|
|
type: 'number',
|
||
|
|
icon: 'lucide:alertTriangle',
|
||
|
|
color: (degradedCount + outageCount) > 0 ? sharedStyles.colors.status.degraded.cssText : undefined,
|
||
|
|
},
|
||
|
|
{
|
||
|
|
id: 'incidents',
|
||
|
|
title: 'Active Incidents',
|
||
|
|
value: activeIncidents.length,
|
||
|
|
type: 'number',
|
||
|
|
icon: 'lucide:alertCircle',
|
||
|
|
color: activeIncidents.length > 0 ? sharedStyles.colors.status.majorOutage.cssText : undefined,
|
||
|
|
},
|
||
|
|
];
|
||
|
|
}
|
||
|
|
render() {
|
||
|
|
const activeIncidents = this.incidents.filter(i => !['resolved', 'postmortem'].includes(i.status));
|
||
|
|
return html `
|
||
|
|
<div class="dashboard">
|
||
|
|
<!-- Overall Status Banner -->
|
||
|
|
${this.renderStatusBanner()}
|
||
|
|
|
||
|
|
<!-- Stats Grid using dees-statsgrid -->
|
||
|
|
<div class="stats-container">
|
||
|
|
<dees-statsgrid
|
||
|
|
.tiles=${this.statsTiles}
|
||
|
|
.minTileWidth=${200}
|
||
|
|
.gap=${16}
|
||
|
|
></dees-statsgrid>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Content Grid -->
|
||
|
|
<div class="content-grid">
|
||
|
|
<!-- Active Incidents -->
|
||
|
|
<div class="section-card">
|
||
|
|
<div class="section-header">
|
||
|
|
<span class="section-title">Active Incidents</span>
|
||
|
|
<button class="section-action" @click="${this.handleViewAllIncidents}">
|
||
|
|
View All <dees-icon .icon=${'lucide:arrowRight'}></dees-icon>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
<div class="section-body">
|
||
|
|
${activeIncidents.length > 0 ? html `
|
||
|
|
<div class="incident-list">
|
||
|
|
${activeIncidents.slice(0, 5).map(incident => this.renderIncidentItem(incident))}
|
||
|
|
</div>
|
||
|
|
` : html `
|
||
|
|
<div class="empty-state">
|
||
|
|
<div class="empty-icon"><dees-icon .icon=${'lucide:partyPopper'}></dees-icon></div>
|
||
|
|
<div class="empty-text">No active incidents</div>
|
||
|
|
</div>
|
||
|
|
`}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Status by Category -->
|
||
|
|
<div class="section-card">
|
||
|
|
<div class="section-header">
|
||
|
|
<span class="section-title">Status by Category</span>
|
||
|
|
<button class="section-action" @click="${this.handleViewAllMonitors}">
|
||
|
|
View All <dees-icon .icon=${'lucide:arrowRight'}></dees-icon>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
<div class="section-body">
|
||
|
|
${this.renderCategoryStatus()}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<!-- Quick Actions -->
|
||
|
|
<div class="section-card">
|
||
|
|
<div class="section-header">
|
||
|
|
<span class="section-title">Quick Actions</span>
|
||
|
|
</div>
|
||
|
|
<div class="section-body">
|
||
|
|
<div class="quick-actions">
|
||
|
|
<button class="quick-action" @click="${this.handleNewIncident}">
|
||
|
|
<span class="quick-action-icon"><dees-icon .icon=${'lucide:alertCircle'}></dees-icon></span>
|
||
|
|
<span class="quick-action-label">Report Incident</span>
|
||
|
|
</button>
|
||
|
|
<button class="quick-action" @click="${this.handleNewMonitor}">
|
||
|
|
<span class="quick-action-icon"><dees-icon .icon=${'lucide:radio'}></dees-icon></span>
|
||
|
|
<span class="quick-action-label">Add Monitor</span>
|
||
|
|
</button>
|
||
|
|
<button class="quick-action" @click="${this.handleScheduleMaintenance}">
|
||
|
|
<span class="quick-action-icon"><dees-icon .icon=${'lucide:wrench'}></dees-icon></span>
|
||
|
|
<span class="quick-action-label">Schedule Maintenance</span>
|
||
|
|
</button>
|
||
|
|
<button class="quick-action" @click="${this.handleViewConfig}">
|
||
|
|
<span class="quick-action-icon"><dees-icon .icon=${'lucide:settings'}></dees-icon></span>
|
||
|
|
<span class="quick-action-label">Configuration</span>
|
||
|
|
</button>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
renderStatusBanner() {
|
||
|
|
const status = this.overallStatus || this.calculateOverallStatus();
|
||
|
|
const statusIcons = {
|
||
|
|
operational: 'lucide:check',
|
||
|
|
degraded: 'lucide:alertTriangle',
|
||
|
|
partial_outage: 'lucide:zap',
|
||
|
|
major_outage: 'lucide:x',
|
||
|
|
maintenance: 'lucide:wrench',
|
||
|
|
};
|
||
|
|
const statusTitles = {
|
||
|
|
operational: 'All Systems Operational',
|
||
|
|
degraded: 'Degraded Performance',
|
||
|
|
partial_outage: 'Partial System Outage',
|
||
|
|
major_outage: 'Major System Outage',
|
||
|
|
maintenance: 'Scheduled Maintenance',
|
||
|
|
};
|
||
|
|
return html `
|
||
|
|
<div class="status-banner ${status.status}">
|
||
|
|
<div class="status-indicator ${status.status}">
|
||
|
|
<dees-icon .icon=${statusIcons[status.status]}></dees-icon>
|
||
|
|
</div>
|
||
|
|
<div class="status-content">
|
||
|
|
<div class="status-title">${statusTitles[status.status]}</div>
|
||
|
|
<div class="status-message">${status.message}</div>
|
||
|
|
<div class="status-meta">
|
||
|
|
Last updated: ${new Date(status.lastUpdated).toLocaleString()}
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
renderIncidentItem(incident) {
|
||
|
|
const formatTime = (timestamp) => {
|
||
|
|
const now = Date.now();
|
||
|
|
const diff = now - timestamp;
|
||
|
|
const hours = Math.floor(diff / (1000 * 60 * 60));
|
||
|
|
if (hours < 1)
|
||
|
|
return `${Math.floor(diff / (1000 * 60))}m ago`;
|
||
|
|
if (hours < 24)
|
||
|
|
return `${hours}h ago`;
|
||
|
|
return `${Math.floor(hours / 24)}d ago`;
|
||
|
|
};
|
||
|
|
return html `
|
||
|
|
<div class="incident-item ${incident.severity}" @click="${() => this.handleIncidentClick(incident)}">
|
||
|
|
<div class="incident-content">
|
||
|
|
<div class="incident-title">${incident.title}</div>
|
||
|
|
<div class="incident-meta">
|
||
|
|
<span class="incident-status">${incident.status}</span>
|
||
|
|
<span>•</span>
|
||
|
|
<span>${formatTime(incident.startTime)}</span>
|
||
|
|
<span>•</span>
|
||
|
|
<span>${incident.affectedServices.length} services</span>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
renderCategoryStatus() {
|
||
|
|
const categories = [...new Set(this.monitors.map(m => m.category || 'Uncategorized'))];
|
||
|
|
if (categories.length === 0) {
|
||
|
|
return html `
|
||
|
|
<div class="empty-state">
|
||
|
|
<div class="empty-icon"><dees-icon .icon=${'lucide:barChart3'}></dees-icon></div>
|
||
|
|
<div class="empty-text">No monitors configured</div>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
return html `
|
||
|
|
<div class="category-list">
|
||
|
|
${categories.map(category => {
|
||
|
|
const categoryMonitors = this.monitors.filter(m => (m.category || 'Uncategorized') === category);
|
||
|
|
const operational = categoryMonitors.filter(m => m.currentStatus === 'operational').length;
|
||
|
|
const percentage = (operational / categoryMonitors.length) * 100;
|
||
|
|
return html `
|
||
|
|
<div class="category-item">
|
||
|
|
<span class="category-name">${category}</span>
|
||
|
|
<div class="category-stats">
|
||
|
|
<span class="category-count">${operational}/${categoryMonitors.length}</span>
|
||
|
|
<div class="category-bar">
|
||
|
|
<div class="category-bar-fill" style="width: ${percentage}%"></div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
})}
|
||
|
|
</div>
|
||
|
|
`;
|
||
|
|
}
|
||
|
|
calculateOverallStatus() {
|
||
|
|
const hasOutage = this.monitors.some(m => ['partial_outage', 'major_outage'].includes(m.currentStatus));
|
||
|
|
const hasDegraded = this.monitors.some(m => m.currentStatus === 'degraded');
|
||
|
|
const hasMaintenance = this.monitors.some(m => m.currentStatus === 'maintenance');
|
||
|
|
const affectedCount = this.monitors.filter(m => m.currentStatus !== 'operational').length;
|
||
|
|
let status = 'operational';
|
||
|
|
let message = 'All systems are operating normally.';
|
||
|
|
if (hasOutage) {
|
||
|
|
status = this.monitors.some(m => m.currentStatus === 'major_outage') ? 'major_outage' : 'partial_outage';
|
||
|
|
message = `${affectedCount} services are experiencing issues.`;
|
||
|
|
}
|
||
|
|
else if (hasDegraded) {
|
||
|
|
status = 'degraded';
|
||
|
|
message = `${affectedCount} services are experiencing degraded performance.`;
|
||
|
|
}
|
||
|
|
else if (hasMaintenance) {
|
||
|
|
status = 'maintenance';
|
||
|
|
message = `${affectedCount} services are under maintenance.`;
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
status,
|
||
|
|
message,
|
||
|
|
lastUpdated: Date.now(),
|
||
|
|
affectedServices: affectedCount,
|
||
|
|
totalServices: this.monitors.length,
|
||
|
|
};
|
||
|
|
}
|
||
|
|
handleViewAllIncidents() {
|
||
|
|
this.dispatchEvent(new CustomEvent('navigateIncidents', { bubbles: true, composed: true }));
|
||
|
|
}
|
||
|
|
handleViewAllMonitors() {
|
||
|
|
this.dispatchEvent(new CustomEvent('navigateMonitors', { bubbles: true, composed: true }));
|
||
|
|
}
|
||
|
|
handleIncidentClick(incident) {
|
||
|
|
this.dispatchEvent(new CustomEvent('incidentSelect', {
|
||
|
|
detail: { incident },
|
||
|
|
bubbles: true,
|
||
|
|
composed: true
|
||
|
|
}));
|
||
|
|
}
|
||
|
|
handleNewIncident() {
|
||
|
|
this.dispatchEvent(new CustomEvent('createIncident', { bubbles: true, composed: true }));
|
||
|
|
}
|
||
|
|
handleNewMonitor() {
|
||
|
|
this.dispatchEvent(new CustomEvent('createMonitor', { bubbles: true, composed: true }));
|
||
|
|
}
|
||
|
|
handleScheduleMaintenance() {
|
||
|
|
this.dispatchEvent(new CustomEvent('scheduleMaintenance', { bubbles: true, composed: true }));
|
||
|
|
}
|
||
|
|
handleViewConfig() {
|
||
|
|
this.dispatchEvent(new CustomEvent('navigateConfig', { bubbles: true, composed: true }));
|
||
|
|
}
|
||
|
|
constructor() {
|
||
|
|
super(...arguments);
|
||
|
|
__runInitializers(this, _loading_extraInitializers);
|
||
|
|
}
|
||
|
|
static {
|
||
|
|
__runInitializers(_classThis, _classExtraInitializers);
|
||
|
|
}
|
||
|
|
};
|
||
|
|
return UpladminDashboard = _classThis;
|
||
|
|
})();
|
||
|
|
export { UpladminDashboard };
|
||
|
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXBsYWRtaW4tZGFzaGJvYXJkLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vdHNfd2ViL2VsZW1lbnRzL3VwbGFkbWluLWRhc2hib2FyZC91cGxhZG1pbi1kYXNoYm9hcmQudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsT0FBTyxFQUNMLFdBQVcsRUFDWCxRQUFRLEVBQ1IsSUFBSSxFQUNKLGFBQWEsRUFFYixHQUFHLEVBQ0gsVUFBVSxFQUNWLFNBQVMsRUFDVCxLQUFLLEdBQ04sTUFBTSw2QkFBNkIsQ0FBQztBQUNyQyxPQUFPLEtBQUssWUFBWSxNQUFNLCtCQUErQixDQUFDO0FBRzlELE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSw4QkFBOEIsQ0FBQztJQVczQyxpQkFBaUI7NEJBRDdCLGFBQWEsQ0FBQyxvQkFBb0IsQ0FBQzs7OztzQkFDRyxXQUFXOzs7Ozs7Ozs7Ozs7O2lDQUFuQixTQUFRLFdBQVc7Ozs7b0NBRy9DLFFBQVEsQ0FBQyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsQ0FBQztxQ0FHekIsUUFBUSxDQUFDLEVBQUUsSUFBSSxFQUFFLEtBQUssRUFBRSxDQUFDO3lDQUd6QixRQUFRLENBQUMsRUFBRSxJQUFJLEVBQUUsTUFBTSxFQUFFLENBQUM7bUNBRzFCLFFBQVEsQ0FBQyxFQUFFLElBQUksRUFBRSxPQUFPLEVBQUUsQ0FBQztZQVI1Qiw2S0FBUyxRQUFRLDZCQUFSLFFBQVEsMkZBQXdCO1lBR3pDLGdMQUFTLFNBQVMsNkJBQVQsU0FBUyw2RkFBMEI7WUFHNUMsNExBQVMsYUFBYSw2QkFBYixhQUFhLHFHQUErQjtZQUdyRCwwS0FBUyxPQUFPLDZCQUFQLE9BQU8seUZBQWtCO1lBYnBDLDZLQThvQkM7Ozs7UUE3b0JRLE1BQU0sQ0FBQyxJQUFJLEdBQUcsUUFBUSxDQUFDO1FBRzlCLDZFQUFzQyxFQUFFLEVBQUM7UUFBekMsSUFBUyxRQUFRLDhDQUF3QjtRQUF6QyxJQUFTLFFBQVEsb0RBQXdCO1FBR3pDLHNJQUF5QyxFQUFFLEdBQUM7UUFBNUMsSUFBUyxTQUFTLCtDQUEwQjtRQUE1QyxJQUFTLFNBQVMscURBQTBCO1FBRzVDLCtJQUFnRCxJQUFJLEdBQUM7UUFBckQsSUFBUyxhQUFhLG1EQUErQjtRQUFyRCxJQUFTLGFBQWEseURBQStCO1FBR3JELHVJQUE0QixLQUFLLEdBQUM7UUFBbEMsSUFBUyxPQUFPLDZDQUFrQjtRQUFsQyxJQUFTLE9BQU8sbURBQWtCO1FBRTNCLE1BQU0sQ0FBQyxNQUFNLEdBQUc7WUFDckIsT0FBTyxDQUFDLFFBQVEsQ0FBQyxZQUFZLENBQUMsWUFBWTtZQUMxQyxZQUFZLENBQUMsWUFBWTtZQUN6QixHQUFHLENBQUE7Ozt1QkFHZ0IsU0FBUyxDQUFDLFlBQVksQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDOzs7OztlQUsxQyxTQUFTLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7Ozs7Ozs7ZUFPbEMsU0FBUyxDQUFDLFlBQVksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDO21CQUM5QixTQUFTLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7eUJBQzVCLFNBQVMsQ0FBQyxZQUFZLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQzs7Ozs7c0JBSzFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsd0JBQXdCLEVBQUUseUJBQXlCLENBQUM7d0JBQ3JFLFlBQVksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLFdBQVc7Ozs7c0JBSXhDLFVBQVUsQ0FBQyxPQUFPLENBQUMsd0JBQXdCLEVBQUUseUJBQXlCLENBQUM7d0JBQ3JFLFlBQVksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLFFBQVE7Ozs7c0JBSXJDLFVBQVUsQ0FBQyxPQUFPLENBQUMseUJBQXlCLEVBQUUsMEJBQTBCLENBQUM7d0JBQ3ZFLFlBQVksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLGFBQWE7Ozs7c0JBSTFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsd0JBQXdCLEVBQUUseUJBQXlCLENBQUM7d0JBQ3JFLFlBQVksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLFdBQVc7Ozs7c0JBSXhDLFVBQVUsQ0FBQyxPQUFPLENBQUMseUJBQXlCLEVBQUUsMEJBQTBCLENBQUM7d0JBQ3ZFLFlBQVksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLFdBQVc7Ozs7Ozs7Ozs7Ozs7Ozs7OztvREFrQlYsWUFBWSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsV0FBVztpREFDekMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsUUFBUTt1REFDN0IsWUFBWSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsYUFBYTtxREFDMUMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsV0FBVztvREFDdkMsWUFBWSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsV0FBVzs7Ozs7Ozs7O2lCQVN6RSxZQUFZLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPOzs7Ozs7aUJBTWhDLFlBQVksQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLFNBQVM7Ozs7O2lCQUtsQyxZQUFZLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxLQUFLOzs7Ozs7Ozs7Ozs7Ozs7Ozs7ZUFrQmhDLFNBQVMsQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQzs7Ozs7Ozs7Ozs7c0JBVzNCLFlBQVksQ0FBQyxNQUFNLENBQUMsVUFBVSxDQUFDLFNBQVM7NEJBQ2xDLFlBQVksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU87eUJBQ3JDLFNBQVMsQ0FBQyxZQUFZLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQzs7Ozs7Ozs7bUJBUTdDLFNBQVMsQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxJQUFJLFNBQVMsQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQzttQ0FDeEQsWUFBWSxDQUFDLE1BQU0sQ0FBQyxNQUFNLENBQUMsT0FBTzs7Ozs7O2lCQU1wRCxZQUFZLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxPQUFPOzs7Ozs7Ozs7aUJBU2hDLFlBQVksQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLE9BQU87Ozs7OEJBSXJCLFNBQVMsQ0FBQyxZQUFZLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQyxJQUFJLFNBQVMsQ0FBQyxZQUFZLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQzs7Ozs7Ozs7Ozs7O21CQVk1RixTQUFTLENBQUMsWUFBWSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7Ozs7Ozs7ZUFPdEMsU0FBUyxDQUFDLFlBQVksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDOzs7Ozs7ZUFNbEMsU
|