- Implemented `ops-view-logs` for displaying and filtering logs with streaming capabilities. - Created `ops-view-overview` to show server, email, DNS statistics, and charts. - Developed `ops-view-security` for monitoring security metrics, blocked IPs, and authentication attempts. - Added `ops-view-stats` to present comprehensive statistics on server, email, DNS, and security metrics. - Introduced shared styles and components including `ops-sectionheading` for consistent UI.
268 lines
6.9 KiB
TypeScript
268 lines
6.9 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
import * as shared from './shared/index.js';
|
|
import * as appstate from '../appstate.js';
|
|
|
|
import {
|
|
DeesElement,
|
|
customElement,
|
|
html,
|
|
state,
|
|
css,
|
|
cssManager,
|
|
} from '@design.estate/dees-element';
|
|
|
|
@customElement('ops-view-config')
|
|
export class OpsViewConfig extends DeesElement {
|
|
@state()
|
|
private configState: appstate.IConfigState = {
|
|
config: null,
|
|
isLoading: false,
|
|
error: null,
|
|
};
|
|
|
|
@state()
|
|
private editingSection: string | null = null;
|
|
|
|
@state()
|
|
private editedConfig: any = null;
|
|
|
|
constructor() {
|
|
super();
|
|
const subscription = appstate.configStatePart
|
|
.select((stateArg) => stateArg)
|
|
.subscribe((configState) => {
|
|
this.configState = configState;
|
|
});
|
|
this.rxSubscriptions.push(subscription);
|
|
}
|
|
|
|
public static styles = [
|
|
cssManager.defaultStyles,
|
|
shared.viewHostCss,
|
|
css`
|
|
.configSection {
|
|
background: white;
|
|
border: 1px solid #e9ecef;
|
|
border-radius: 8px;
|
|
margin-bottom: 24px;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.sectionHeader {
|
|
background: #f8f9fa;
|
|
padding: 16px 24px;
|
|
border-bottom: 1px solid #e9ecef;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
|
|
.sectionTitle {
|
|
font-size: 18px;
|
|
font-weight: 600;
|
|
color: #333;
|
|
}
|
|
|
|
.sectionContent {
|
|
padding: 24px;
|
|
}
|
|
|
|
.configField {
|
|
margin-bottom: 20px;
|
|
}
|
|
|
|
.fieldLabel {
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: #666;
|
|
margin-bottom: 8px;
|
|
display: block;
|
|
}
|
|
|
|
.fieldValue {
|
|
font-family: 'Consolas', 'Monaco', monospace;
|
|
font-size: 14px;
|
|
color: #333;
|
|
background: #f8f9fa;
|
|
padding: 8px 12px;
|
|
border-radius: 4px;
|
|
border: 1px solid #e9ecef;
|
|
}
|
|
|
|
.configEditor {
|
|
width: 100%;
|
|
min-height: 200px;
|
|
font-family: 'Consolas', 'Monaco', monospace;
|
|
font-size: 14px;
|
|
padding: 12px;
|
|
border: 1px solid #e9ecef;
|
|
border-radius: 4px;
|
|
background: #f8f9fa;
|
|
resize: vertical;
|
|
}
|
|
|
|
.buttonGroup {
|
|
display: flex;
|
|
gap: 8px;
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.warning {
|
|
background: #fff3cd;
|
|
border: 1px solid #ffeaa7;
|
|
border-radius: 4px;
|
|
padding: 12px;
|
|
margin-bottom: 16px;
|
|
color: #856404;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.errorMessage {
|
|
background: #fee;
|
|
border: 1px solid #fcc;
|
|
border-radius: 4px;
|
|
padding: 16px;
|
|
color: #c00;
|
|
margin: 16px 0;
|
|
}
|
|
|
|
.loadingMessage {
|
|
text-align: center;
|
|
padding: 40px;
|
|
color: #666;
|
|
}
|
|
`,
|
|
];
|
|
|
|
public render() {
|
|
return html`
|
|
<ops-sectionheading>Configuration</ops-sectionheading>
|
|
|
|
${this.configState.isLoading ? html`
|
|
<div class="loadingMessage">
|
|
<dees-spinner></dees-spinner>
|
|
<p>Loading configuration...</p>
|
|
</div>
|
|
` : this.configState.error ? html`
|
|
<div class="errorMessage">
|
|
Error loading configuration: ${this.configState.error}
|
|
</div>
|
|
` : this.configState.config ? html`
|
|
<div class="warning">
|
|
<dees-icon name="warning"></dees-icon>
|
|
<span>Changes to configuration will take effect immediately. Please be careful when editing production settings.</span>
|
|
</div>
|
|
|
|
${this.renderConfigSection('server', 'Server Configuration', this.configState.config.server)}
|
|
${this.renderConfigSection('email', 'Email Configuration', this.configState.config.email)}
|
|
${this.renderConfigSection('dns', 'DNS Configuration', this.configState.config.dns)}
|
|
${this.renderConfigSection('security', 'Security Configuration', this.configState.config.security)}
|
|
${this.renderConfigSection('storage', 'Storage Configuration', this.configState.config.storage)}
|
|
` : html`
|
|
<div class="errorMessage">No configuration loaded</div>
|
|
`}
|
|
`;
|
|
}
|
|
|
|
private renderConfigSection(key: string, title: string, config: any) {
|
|
const isEditing = this.editingSection === key;
|
|
|
|
return html`
|
|
<div class="configSection">
|
|
<div class="sectionHeader">
|
|
<h3 class="sectionTitle">${title}</h3>
|
|
<div>
|
|
${isEditing ? html`
|
|
<dees-button
|
|
@click=${() => this.saveConfig(key)}
|
|
type="highlighted"
|
|
>
|
|
Save
|
|
</dees-button>
|
|
<dees-button
|
|
@click=${() => this.cancelEdit()}
|
|
>
|
|
Cancel
|
|
</dees-button>
|
|
` : html`
|
|
<dees-button
|
|
@click=${() => this.startEdit(key, config)}
|
|
>
|
|
Edit
|
|
</dees-button>
|
|
`}
|
|
</div>
|
|
</div>
|
|
<div class="sectionContent">
|
|
${isEditing ? html`
|
|
<textarea
|
|
class="configEditor"
|
|
@input=${(e) => this.editedConfig = e.target.value}
|
|
.value=${JSON.stringify(config, null, 2)}
|
|
></textarea>
|
|
` : html`
|
|
${this.renderConfigFields(config)}
|
|
`}
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
private renderConfigFields(config: any, prefix = '') {
|
|
if (!config || typeof config !== 'object') {
|
|
return html`<div class="fieldValue">${config}</div>`;
|
|
}
|
|
|
|
return Object.entries(config).map(([key, value]) => {
|
|
const fieldName = prefix ? `${prefix}.${key}` : key;
|
|
|
|
if (typeof value === 'object' && !Array.isArray(value)) {
|
|
return html`
|
|
<div class="configField">
|
|
<label class="fieldLabel">${fieldName}</label>
|
|
${this.renderConfigFields(value, fieldName)}
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
return html`
|
|
<div class="configField">
|
|
<label class="fieldLabel">${fieldName}</label>
|
|
<div class="fieldValue">
|
|
${Array.isArray(value) ? value.join(', ') : value}
|
|
</div>
|
|
</div>
|
|
`;
|
|
});
|
|
}
|
|
|
|
private startEdit(section: string, config: any) {
|
|
this.editingSection = section;
|
|
this.editedConfig = JSON.stringify(config, null, 2);
|
|
}
|
|
|
|
private cancelEdit() {
|
|
this.editingSection = null;
|
|
this.editedConfig = null;
|
|
}
|
|
|
|
private async saveConfig(section: string) {
|
|
try {
|
|
const parsedConfig = JSON.parse(this.editedConfig);
|
|
|
|
await appstate.configStatePart.dispatchAction(appstate.updateConfigurationAction, {
|
|
section,
|
|
config: parsedConfig,
|
|
});
|
|
|
|
this.editingSection = null;
|
|
this.editedConfig = null;
|
|
|
|
// Configuration updated successfully
|
|
} catch (error) {
|
|
console.error(`Error updating configuration:`, error);
|
|
}
|
|
}
|
|
} |