This commit is contained in:
2025-12-24 10:57:43 +00:00
commit ba79b4bfb6
118 changed files with 292546 additions and 0 deletions

View File

@@ -0,0 +1 @@
export * from './upladmin-monitor-form.js';

View File

@@ -0,0 +1,66 @@
import { html, css, cssManager } from '@design.estate/dees-element';
import type { IServiceStatus } from '../../interfaces/index.js';
import './upladmin-monitor-form.js';
export const demoFunc = () => html`
<style>
${css`
.demo-container {
display: flex;
flex-direction: column;
gap: 32px;
padding: 24px;
max-width: 900px;
margin: 0 auto;
background: ${cssManager.bdTheme('#fafafa', '#0a0a0a')};
min-height: 100vh;
}
.demo-section {
margin-bottom: 24px;
}
.demo-section h3 {
margin: 0 0 16px 0;
font-size: 14px;
font-weight: 600;
color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
text-transform: uppercase;
letter-spacing: 0.05em;
}
`}
</style>
<div class="demo-container">
<div class="demo-section">
<h3>Create New Monitor</h3>
<upladmin-monitor-form
.availableMonitors=${[
{ id: 'api-server', name: 'api-server', displayName: 'API Server', currentStatus: 'operational', lastChecked: Date.now(), uptime30d: 99.98, uptime90d: 99.95, responseTime: 45 },
{ id: 'database', name: 'database', displayName: 'Database', currentStatus: 'operational', lastChecked: Date.now(), uptime30d: 99.99, uptime90d: 99.98, responseTime: 5 },
] as IServiceStatus[]}
.categories=${['Core Services', 'Infrastructure', 'External Services', 'Web Services']}
></upladmin-monitor-form>
</div>
<div class="demo-section">
<h3>Edit Existing Monitor</h3>
<upladmin-monitor-form
.monitor=${{
id: 'cdn',
name: 'cdn',
displayName: 'Content Delivery Network',
description: 'Global CDN for static assets and media files',
category: 'Infrastructure',
dependencies: ['api-server'],
currentStatus: 'degraded',
}}
.availableMonitors=${[
{ id: 'api-server', name: 'api-server', displayName: 'API Server', currentStatus: 'operational', lastChecked: Date.now(), uptime30d: 99.98, uptime90d: 99.95, responseTime: 45 },
{ id: 'database', name: 'database', displayName: 'Database', currentStatus: 'operational', lastChecked: Date.now(), uptime30d: 99.99, uptime90d: 99.98, responseTime: 5 },
] as IServiceStatus[]}
.categories=${['Core Services', 'Infrastructure', 'External Services', 'Web Services']}
></upladmin-monitor-form>
</div>
</div>
`;

View File

@@ -0,0 +1,431 @@
import * as plugins from '../../plugins.js';
import {
DeesElement,
property,
html,
customElement,
type TemplateResult,
css,
cssManager,
unsafeCSS,
state,
} from '@design.estate/dees-element';
import * as sharedStyles from '../../styles/shared.styles.js';
import type { IMonitorFormData, IServiceStatus } from '../../interfaces/index.js';
import { demoFunc } from './upladmin-monitor-form.demo.js';
declare global {
interface HTMLElementTagNameMap {
'upladmin-monitor-form': UpladminMonitorForm;
}
}
type TStatusType = 'operational' | 'degraded' | 'partial_outage' | 'major_outage' | 'maintenance';
@customElement('upladmin-monitor-form')
export class UpladminMonitorForm extends DeesElement {
public static demo = demoFunc;
@property({ type: Object })
accessor monitor: IMonitorFormData | null = null;
@property({ type: Array })
accessor availableMonitors: IServiceStatus[] = [];
@property({ type: Array })
accessor categories: string[] = [];
@property({ type: Boolean })
accessor loading: boolean = false;
@state()
accessor formData: IMonitorFormData = {
name: '',
displayName: '',
description: '',
category: '',
dependencies: [],
currentStatus: 'operational',
};
@state()
accessor errors: Record<string, string> = {};
private statusIcons: Record<TStatusType, string> = {
operational: 'lucide:CheckCircle',
degraded: 'lucide:AlertTriangle',
partial_outage: 'lucide:AlertOctagon',
major_outage: 'lucide:XCircle',
maintenance: 'lucide:Wrench',
};
public static styles = [
plugins.domtools.elementBasic.staticStyles,
sharedStyles.commonStyles,
css`
:host {
display: block;
font-family: ${unsafeCSS(sharedStyles.fonts.base)};
}
.form-container {
background: ${sharedStyles.colors.background.secondary};
border: 1px solid ${sharedStyles.colors.border.default};
border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)};
overflow: hidden;
}
.form-header {
display: flex;
align-items: center;
gap: ${unsafeCSS(sharedStyles.spacing.md)};
padding: ${unsafeCSS(sharedStyles.spacing.lg)};
border-bottom: 1px solid ${sharedStyles.colors.border.default};
background: ${sharedStyles.colors.background.muted};
}
.form-header dees-icon {
--icon-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')};
}
.form-title-wrapper {
flex: 1;
}
.form-title {
font-size: 18px;
font-weight: 600;
color: ${sharedStyles.colors.text.primary};
margin: 0;
}
.form-subtitle {
font-size: 13px;
color: ${sharedStyles.colors.text.muted};
margin-top: 4px;
}
.form-body {
display: grid;
gap: ${unsafeCSS(sharedStyles.spacing.lg)};
padding: ${unsafeCSS(sharedStyles.spacing.lg)};
}
dees-form {
display: contents;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: ${unsafeCSS(sharedStyles.spacing.md)};
}
@media (max-width: 600px) {
.form-row {
grid-template-columns: 1fr;
}
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: ${unsafeCSS(sharedStyles.spacing.sm)};
padding: ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.lg)};
border-top: 1px solid ${sharedStyles.colors.border.default};
background: ${sharedStyles.colors.background.muted};
}
.status-section {
margin-top: ${unsafeCSS(sharedStyles.spacing.sm)};
}
.status-options {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: ${unsafeCSS(sharedStyles.spacing.sm)};
}
.status-option {
display: flex;
align-items: center;
gap: 12px;
padding: 14px 16px;
background: ${sharedStyles.colors.background.primary};
border: 2px solid ${sharedStyles.colors.border.default};
border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
cursor: pointer;
transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
}
.status-option:hover {
border-color: ${sharedStyles.colors.border.strong};
background: ${sharedStyles.colors.background.muted};
}
.status-option.selected {
border-color: ${sharedStyles.colors.accent.primary};
background: ${cssManager.bdTheme('rgba(59, 130, 246, 0.05)', 'rgba(96, 165, 250, 0.1)')};
}
.status-option input {
display: none;
}
.status-option dees-icon {
flex-shrink: 0;
}
.status-option.operational dees-icon { --icon-color: ${sharedStyles.colors.status.operational}; }
.status-option.degraded dees-icon { --icon-color: ${sharedStyles.colors.status.degraded}; }
.status-option.partial_outage dees-icon { --icon-color: ${sharedStyles.colors.status.partialOutage}; }
.status-option.major_outage dees-icon { --icon-color: ${sharedStyles.colors.status.majorOutage}; }
.status-option.maintenance dees-icon { --icon-color: ${sharedStyles.colors.status.maintenance}; }
.status-label {
font-size: 14px;
font-weight: 500;
color: ${sharedStyles.colors.text.primary};
}
.field-label {
display: block;
font-size: 13px;
font-weight: 500;
color: ${sharedStyles.colors.text.primary};
margin-bottom: ${unsafeCSS(sharedStyles.spacing.sm)};
}
.field-label.required::after {
content: ' *';
color: ${sharedStyles.colors.accent.danger};
}
/* Style dees-input components */
dees-input-text,
dees-input-dropdown {
--dees-input-background: ${sharedStyles.colors.background.primary};
--dees-input-border-color: ${sharedStyles.colors.border.default};
}
`
];
async connectedCallback() {
await super.connectedCallback();
if (this.monitor) {
this.formData = { ...this.monitor };
}
}
updated(changedProperties: Map<string, unknown>) {
if (changedProperties.has('monitor') && this.monitor) {
this.formData = { ...this.monitor };
}
}
public render(): TemplateResult {
const isEdit = !!this.monitor?.id;
const statusOptions: Array<{ value: TStatusType; label: string }> = [
{ value: 'operational', label: 'Operational' },
{ value: 'degraded', label: 'Degraded' },
{ value: 'partial_outage', label: 'Partial Outage' },
{ value: 'major_outage', label: 'Major Outage' },
{ value: 'maintenance', label: 'Maintenance' },
];
const categoryOptions = this.categories.map(cat => ({ key: cat, option: cat, payload: null }));
const dependencyOptions = this.availableMonitors
.filter(m => m.id !== this.monitor?.id)
.map(m => ({ key: m.id, option: m.displayName || m.name, payload: null }));
return html`
<div class="form-container">
<div class="form-header">
<dees-icon .icon=${isEdit ? 'lucide:Pencil' : 'lucide:Plus'} .iconSize=${24}></dees-icon>
<div class="form-title-wrapper">
<h2 class="form-title">${isEdit ? 'Edit Monitor' : 'Create Monitor'}</h2>
<p class="form-subtitle">
${isEdit ? 'Update the monitor configuration' : 'Add a new service to monitor'}
</p>
</div>
</div>
<div class="form-body">
<dees-form>
<div class="form-row">
<dees-input-text
key="name"
label="Internal Name"
.value="${this.formData.name}"
placeholder="api-server"
required
description="Lowercase, no spaces. Used as identifier."
@changeSubject="${this.handleNameChange}"
></dees-input-text>
<dees-input-text
key="displayName"
label="Display Name"
.value="${this.formData.displayName}"
placeholder="API Server"
required
description="Human-readable name shown to users."
@changeSubject="${this.handleDisplayNameChange}"
></dees-input-text>
</div>
<dees-input-text
key="description"
label="Description"
inputType="textarea"
.value="${this.formData.description || ''}"
placeholder="Brief description of what this service does..."
@changeSubject="${this.handleDescriptionChange}"
></dees-input-text>
<div class="form-row">
<dees-input-dropdown
key="category"
label="Category"
.options="${categoryOptions}"
.selectedOption="${this.formData.category || ''}"
placeholder="Select category..."
@selectedOption="${this.handleCategoryChange}"
></dees-input-dropdown>
<dees-input-dropdown
key="dependencies"
label="Dependencies"
.options="${dependencyOptions}"
.selectedOptions="${this.formData.dependencies || []}"
multiple
description="Services this monitor depends on."
@selectedOption="${this.handleDependenciesChange}"
></dees-input-dropdown>
</div>
<div class="status-section">
<label class="field-label required">Current Status</label>
<div class="status-options">
${statusOptions.map(opt => html`
<label
class="status-option ${opt.value} ${this.formData.currentStatus === opt.value ? 'selected' : ''}"
@click="${() => this.handleStatusChange(opt.value)}"
>
<input
type="radio"
name="currentStatus"
value="${opt.value}"
?checked="${this.formData.currentStatus === opt.value}"
/>
<dees-icon .icon=${this.statusIcons[opt.value]} .iconSize=${20}></dees-icon>
<span class="status-label">${opt.label}</span>
</label>
`)}
</div>
</div>
</dees-form>
</div>
<div class="form-actions">
<dees-button type="discreet" @click="${this.handleCancel}" ?disabled="${this.loading}">
Cancel
</dees-button>
<dees-button type="highlighted" @click="${this.handleSave}" ?disabled="${this.loading}">
${this.loading ? html`<dees-spinner .size=${16}></dees-spinner>` : ''}
${isEdit ? 'Update Monitor' : 'Create Monitor'}
</dees-button>
</div>
</div>
`;
}
private handleNameChange(e: CustomEvent) {
this.formData = { ...this.formData, name: e.detail };
if (this.errors.name) {
this.errors = { ...this.errors, name: '' };
}
}
private handleDisplayNameChange(e: CustomEvent) {
this.formData = { ...this.formData, displayName: e.detail };
if (this.errors.displayName) {
this.errors = { ...this.errors, displayName: '' };
}
}
private handleDescriptionChange(e: CustomEvent) {
this.formData = { ...this.formData, description: e.detail };
}
private handleCategoryChange(e: CustomEvent) {
this.formData = { ...this.formData, category: e.detail };
}
private handleDependenciesChange(e: CustomEvent) {
const selected = e.detail;
if (Array.isArray(selected)) {
this.formData = { ...this.formData, dependencies: selected };
} else if (selected) {
// Single selection mode, convert to array
this.formData = { ...this.formData, dependencies: [selected] };
}
}
private handleStatusChange(status: TStatusType) {
this.formData = { ...this.formData, currentStatus: status };
}
private validate(): boolean {
const errors: Record<string, string> = {};
if (!this.formData.name?.trim()) {
errors.name = 'Name is required';
} else if (!/^[a-z0-9-]+$/.test(this.formData.name)) {
errors.name = 'Name must be lowercase letters, numbers, and hyphens only';
}
if (!this.formData.displayName?.trim()) {
errors.displayName = 'Display name is required';
}
this.errors = errors;
return Object.keys(errors).length === 0;
}
private handleSave() {
if (!this.validate()) {
return;
}
this.dispatchEvent(new CustomEvent('monitorSave', {
detail: { monitor: { ...this.formData } },
bubbles: true,
composed: true
}));
}
private handleCancel() {
this.dispatchEvent(new CustomEvent('monitorCancel', {
bubbles: true,
composed: true
}));
}
public reset() {
this.formData = {
name: '',
displayName: '',
description: '',
category: '',
dependencies: [],
currentStatus: 'operational',
};
this.errors = {};
}
public setMonitor(monitor: IMonitorFormData) {
this.formData = { ...monitor };
this.errors = {};
}
}