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 * as shared from './shared/index.js';
import * as appstate from '../appstate.js';
import * as interfaces from '../../dist_ts_interfaces/index.js';
import { DeesElement, customElement, html, state, css, cssManager, } from '@design.estate/dees-element';
let ObViewSettings = (() => {
    let _classDecorators = [customElement('ob-view-settings')];
    let _classDescriptor;
    let _classExtraInitializers = [];
    let _classThis;
    let _classSuper = DeesElement;
    let _settingsState_decorators;
    let _settingsState_initializers = [];
    let _settingsState_extraInitializers = [];
    let _loginState_decorators;
    let _loginState_initializers = [];
    let _loginState_extraInitializers = [];
    var ObViewSettings = class extends _classSuper {
        static { _classThis = this; }
        static {
            const _metadata = typeof Symbol === "function" && Symbol.metadata ? Object.create(_classSuper[Symbol.metadata] ?? null) : void 0;
            _settingsState_decorators = [state()];
            _loginState_decorators = [state()];
            __esDecorate(this, null, _settingsState_decorators, { kind: "accessor", name: "settingsState", static: false, private: false, access: { has: obj => "settingsState" in obj, get: obj => obj.settingsState, set: (obj, value) => { obj.settingsState = value; } }, metadata: _metadata }, _settingsState_initializers, _settingsState_extraInitializers);
            __esDecorate(this, null, _loginState_decorators, { kind: "accessor", name: "loginState", static: false, private: false, access: { has: obj => "loginState" in obj, get: obj => obj.loginState, set: (obj, value) => { obj.loginState = value; } }, metadata: _metadata }, _loginState_initializers, _loginState_extraInitializers);
            __esDecorate(null, _classDescriptor = { value: _classThis }, _classDecorators, { kind: "class", name: _classThis.name, metadata: _metadata }, null, _classExtraInitializers);
            ObViewSettings = _classThis = _classDescriptor.value;
            if (_metadata) Object.defineProperty(_classThis, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
        }
        #settingsState_accessor_storage = __runInitializers(this, _settingsState_initializers, {
            settings: null,
            backupPasswordConfigured: false,
            managedDcRouterStatus: null,
            storageTargets: [],
            storageClasses: [],
        });
        get settingsState() { return this.#settingsState_accessor_storage; }
        set settingsState(value) { this.#settingsState_accessor_storage = value; }
        #loginState_accessor_storage = (__runInitializers(this, _settingsState_extraInitializers), __runInitializers(this, _loginState_initializers, {
            identity: null,
            phase: 'anonymous',
            generation: 0,
        }));
        get loginState() { return this.#loginState_accessor_storage; }
        set loginState(value) { this.#loginState_accessor_storage = value; }
        constructor() {
            super();
            __runInitializers(this, _loginState_extraInitializers);
            const settingsSub = appstate.settingsStatePart
                .select((s) => s)
                .subscribe((newState) => {
                this.settingsState = newState;
            });
            this.rxSubscriptions.push(settingsSub);
            const loginSub = appstate.loginStatePart
                .select((s) => s)
                .subscribe((newState) => {
                this.loginState = newState;
            });
            this.rxSubscriptions.push(loginSub);
        }
        static styles = [
            cssManager.defaultStyles,
            shared.viewHostCss,
            css `
      dees-tile {
        display: block;
        margin-bottom: 24px;
      }

      .gateway-header {
        height: 36px;
        display: flex;
        align-items: center;
        padding: 0 16px;
        width: 100%;
        box-sizing: border-box;
      }

      .gateway-heading {
        flex: 1;
        display: flex;
        align-items: baseline;
        gap: 8px;
        min-width: 0;
      }

      .gateway-title {
        font-size: 13px;
        font-weight: 500;
        letter-spacing: -0.01em;
        color: var(--dees-color-text-secondary);
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }

      .gateway-subtitle {
        font-size: 12px;
        color: var(--dees-color-text-muted);
        letter-spacing: -0.01em;
        white-space: nowrap;
        overflow: hidden;
        text-overflow: ellipsis;
      }

      .gateway-content {
        padding: 20px;
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 16px;
      }

      .gateway-mode-row,
      .gateway-status-row {
        display: flex;
        justify-content: space-between;
        align-items: center;
        gap: 12px;
        padding: 16px 20px;
        border-bottom: 1px solid ${cssManager.bdTheme('#f4f4f5', '#27272a')};
      }

      .gateway-mode-row {
        justify-content: flex-start;
      }

      .gateway-mode-button {
        border: 1px solid ${cssManager.bdTheme('#d4d4d8', '#3f3f46')};
        border-radius: 999px;
        background: ${cssManager.bdTheme('#ffffff', '#18181b')};
        color: ${cssManager.bdTheme('#3f3f46', '#d4d4d8')};
        padding: 8px 12px;
        font: inherit;
        cursor: pointer;
      }

      .gateway-mode-button.active {
        border-color: ${cssManager.bdTheme('#2563eb', '#60a5fa')};
        background: ${cssManager.bdTheme('#eff6ff', '#172554')};
        color: ${cssManager.bdTheme('#1d4ed8', '#bfdbfe')};
      }

      .gateway-status-label {
        font-size: 12px;
        font-weight: 600;
        text-transform: uppercase;
        color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
      }

      .gateway-status-value {
        margin-top: 4px;
        font-size: 14px;
        color: ${cssManager.bdTheme('#18181b', '#fafafa')};
      }

      .gateway-status-error,
      .gateway-disabled {
        color: ${cssManager.bdTheme('#b91c1c', '#fca5a5')};
        font-size: 13px;
      }

      .gateway-disabled {
        grid-column: 1 / -1;
      }

      .gateway-actions {
        display: flex;
        gap: 8px;
      }

      .gateway-field.full {
        grid-column: 1 / -1;
      }

      .gateway-readonly {
        padding: 10px 12px;
        border: 1px solid ${cssManager.bdTheme('#e4e4e7', '#27272a')};
        border-radius: 8px;
        background: ${cssManager.bdTheme('#fafafa', '#18181b')};
      }

      .gateway-readonly-label {
        font-size: 12px;
        font-weight: 600;
        color: ${cssManager.bdTheme('#52525b', '#d4d4d8')};
      }

      .gateway-readonly-value {
        margin-top: 4px;
        font-size: 13px;
        color: ${cssManager.bdTheme('#18181b', '#fafafa')};
        word-break: break-all;
      }

      .gateway-readonly-hint {
        margin-top: 4px;
        font-size: 12px;
        color: ${cssManager.bdTheme('#71717a', '#a1a1aa')};
      }

      dees-input-text {
        width: 100%;
      }

      .gateway-footer {
        display: flex;
        flex-direction: row;
        justify-content: flex-end;
        align-items: center;
        gap: 0;
        height: 36px;
        width: 100%;
        box-sizing: border-box;
      }

      .tile-button {
        padding: 0 16px;
        height: 100%;
        text-align: center;
        font-size: 12px;
        font-weight: 500;
        cursor: pointer;
        user-select: none;
        transition: all 0.15s ease;
        background: transparent;
        border: none;
        border-left: 1px solid var(--dees-color-border-subtle);
        color: var(--dees-color-text-muted);
        white-space: nowrap;
        display: flex;
        align-items: center;
        gap: 6px;
      }

      .storage-content {
        padding: 20px;
        display: grid;
        grid-template-columns: repeat(2, minmax(0, 1fr));
        gap: 16px;
      }

      .storage-panel {
        border: 1px solid var(--dees-color-border-subtle);
        border-radius: 10px;
        padding: 14px;
        background: ${cssManager.bdTheme('#fafafa', '#18181b')};
      }

      .storage-panel.full {
        grid-column: 1 / -1;
      }

      .storage-panel-title {
        font-size: 13px;
        font-weight: 600;
        color: var(--dees-color-text-primary);
        margin-bottom: 8px;
      }

      .storage-row {
        display: flex;
        justify-content: space-between;
        gap: 12px;
        padding: 8px 0;
        border-top: 1px solid var(--dees-color-border-subtle);
        font-size: 13px;
      }

      .storage-row:first-of-type {
        border-top: none;
      }

      .storage-meta {
        color: var(--dees-color-text-muted);
        text-align: right;
      }

      .storage-panel dees-table {
        display: block;
        margin-top: 8px;
      }

      .storage-name {
        font-weight: 600;
        color: var(--dees-color-text-primary);
      }

      .storage-description {
        margin-top: 4px;
        color: var(--dees-color-text-muted);
        font-size: 12px;
      }

      .storage-badge {
        display: inline-flex;
        align-items: center;
        border-radius: 999px;
        padding: 3px 8px;
        background: ${cssManager.bdTheme('#f4f4f5', '#27272a')};
        color: var(--dees-color-text-secondary);
        font-size: 12px;
      }

      .storage-config {
        font-family: monospace;
        color: var(--dees-color-text-muted);
        overflow-wrap: anywhere;
      }

      .storage-empty {
        padding: 24px;
        text-align: center;
        color: var(--dees-color-text-muted);
      }

      .storage-select {
        width: 100%;
        margin-top: 8px;
        padding: 10px 12px;
        border: 1px solid var(--dees-color-border-subtle);
        border-radius: 8px;
        background: ${cssManager.bdTheme('#ffffff', '#09090b')};
        color: var(--dees-color-text-primary);
      }

      .tile-button:first-child {
        border-left: none;
      }

      .tile-button:hover {
        background: var(--dees-color-hover);
        color: var(--dees-color-text-primary);
      }

      .tile-button.primary {
        color: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8%)', 'hsl(213.1 93.9% 67.8%)')};
        font-weight: 600;
      }

      .tile-button.primary:hover {
        background: ${cssManager.bdTheme('hsl(217.2 91.2% 59.8% / 0.08)', 'hsl(213.1 93.9% 67.8% / 0.08)')};
        color: ${cssManager.bdTheme('hsl(217.2 91.2% 50%)', 'hsl(213.1 93.9% 75%)')};
      }

      @media (max-width: 700px) {
        .gateway-content {
          grid-template-columns: 1fr;
        }

        .gateway-status-row {
          align-items: flex-start;
          flex-direction: column;
        }

        .storage-content {
          grid-template-columns: 1fr;
        }
      }
    `,
        ];
        async connectedCallback() {
            super.connectedCallback();
            await appstate.dispatchProtectedAction(appstate.fetchSettingsAction, null);
        }
        render() {
            return html `
      <ob-sectionheading>Settings</ob-sectionheading>
      ${this.renderAdminUiSettings()}
      ${this.renderExternalGatewaySettings()}
      ${this.renderStorageSettings()}
      <sz-settings-view
        .settings=${this.settingsState.settings || {
                darkMode: true,
                cloudflareToken: '',
                cloudflareZoneId: '',
                adminUiDomain: '',
                dcrouterMode: 'managed',
                dcrouterManagedImage: 'code.foss.global/serve.zone/dcrouter:latest',
                dcrouterManagedOpsPort: 3300,
                dcrouterManagedHttpPort: 80,
                dcrouterManagedHttpsPort: 443,
                dcrouterManagedDataDir: './.nogit/dcrouter-data',
                dcrouterGatewayClientId: '',
                platformStorageClass: 'local-hot',
                autoRenewCerts: false,
                renewalThreshold: 30,
                acmeEmail: '',
                httpPort: 80,
                httpsPort: 443,
                forceHttps: false,
            }}
        .currentUser=${this.loginState.identity?.username || 'admin'}
        @setting-change=${(e) => {
                const { key, value } = e.detail;
                appstate.dispatchProtectedAction(appstate.updateSettingsAction, {
                    settings: { [key]: value },
                });
            }}
        @save=${(e) => {
                appstate.dispatchProtectedAction(appstate.updateSettingsAction, {
                    settings: e.detail,
                });
            }}
        @change-password=${(e) => {
                console.log('Change password requested:', e.detail);
            }}
        @reset=${() => {
                appstate.dispatchProtectedAction(appstate.fetchSettingsAction, null);
            }}
      ></sz-settings-view>
    `;
        }
        renderStorageSettings() {
            const settings = this.settingsState.settings;
            const storageClasses = this.settingsState.storageClasses;
            const databaseClasses = storageClasses.filter((storageClassArg) => storageClassArg.purpose === 'database');
            const selectedPlatformClass = settings?.platformStorageClass ||
                databaseClasses.find((storageClassArg) => storageClassArg.isDefault)?.name ||
                'local-hot';
            return html `
      <dees-tile>
        <div slot="header" class="gateway-header">
          <div class="gateway-heading">
            <span class="gateway-title">Storage Classes</span>
            <span class="gateway-subtitle">Route service and platform volumes to local, network, backup, or custom Docker storage</span>
          </div>
        </div>
        <div class="storage-content">
          <div class="storage-panel full">
            <div class="storage-panel-title">Platform Data Storage</div>
            <div class="gateway-readonly-hint">
              MongoDB, SmartStorage, Valkey, ClickHouse, and MariaDB use this class unless a provider-specific setting overrides it.
            </div>
            ${databaseClasses.length ? html `
              <select
                class="storage-select"
                .value=${selectedPlatformClass}
                @change=${(event) => this.updateGatewayDraft('platformStorageClass', event.target.value)}
              >
                ${databaseClasses.map((storageClassArg) => html `
                  <option value=${storageClassArg.name} ?selected=${storageClassArg.name === selectedPlatformClass}>
                    ${storageClassArg.displayName} (${storageClassArg.name})
                  </option>
                `)}
              </select>
            ` : html `<div class="storage-empty">No database storage classes configured.</div>`}
          </div>
          <div class="storage-panel">
            ${this.renderStorageClassTable(storageClasses)}
          </div>
          <div class="storage-panel">
            ${this.renderStorageTargetTable(this.settingsState.storageTargets)}
          </div>
        </div>
        <div slot="footer" class="gateway-footer">
          <button class="tile-button primary" type="button" @click=${() => this.saveStorageSettings()}>
            Save Storage Settings
          </button>
        </div>
      </dees-tile>
    `;
        }
        renderStorageClassTable(storageClassesArg) {
            return html `
      <dees-table
        .heading1=${'Classes'}
        .heading2=${'Logical classes selected by services, app templates, and platform providers'}
        .data=${storageClassesArg}
        .dataName=${'storage class'}
        .rowKey=${'name'}
        .showColumnFilters=${true}
        .displayFunction=${(storageClassArg) => ({
                Name: html `
            <div>
              <div class="storage-name">${storageClassArg.displayName}</div>
              <div class="storage-description">${storageClassArg.name}</div>
              ${storageClassArg.description ? html `<div class="storage-description">${storageClassArg.description}</div>` : null}
            </div>
          `,
                Purpose: html `<span class="storage-badge">${storageClassArg.purpose}</span>`,
                Target: storageClassArg.targetName,
                Driver: storageClassArg.driver || '-',
                Default: storageClassArg.isDefault ? html `<span class="storage-badge">Default</span>` : '-',
            })}
        .dataActions=${[
                {
                    name: 'New class',
                    iconName: 'lucide:Plus',
                    type: ['header'],
                    actionFunc: async () => {
                        await this.openStorageClassDialog();
                    },
                },
                {
                    name: 'Edit class',
                    iconName: 'lucide:Pencil',
                    type: ['inRow', 'contextmenu'],
                    actionFunc: async (actionDataArg) => {
                        await this.openStorageClassDialog(actionDataArg.item);
                    },
                },
                {
                    name: 'Delete class',
                    iconName: 'lucide:Trash2',
                    type: ['inRow', 'contextmenu'],
                    actionRelevancyCheckFunc: (storageClassArg) => !storageClassArg.isDefault,
                    actionFunc: async (actionDataArg) => {
                        await this.deleteStorageClass(actionDataArg.item);
                    },
                },
            ]}
      ></dees-table>
    `;
        }
        renderStorageTargetTable(storageTargetsArg) {
            const storageClasses = this.settingsState.storageClasses;
            return html `
      <dees-table
        .heading1=${'Targets'}
        .heading2=${'Physical Docker volume backends for local, network, backup, and custom storage'}
        .data=${storageTargetsArg}
        .dataName=${'storage target'}
        .rowKey=${'name'}
        .showColumnFilters=${true}
        .displayFunction=${(targetArg) => ({
                Name: html `
            <div>
              <div class="storage-name">${targetArg.displayName}</div>
              <div class="storage-description">${targetArg.name}</div>
            </div>
          `,
                Kind: html `<span class="storage-badge">${targetArg.kind}</span>`,
                Config: html `<span class="storage-config">${this.describeStorageTargetConfig(targetArg)}</span>`,
                Classes: storageClasses.filter((storageClassArg) => storageClassArg.targetName === targetArg.name).length,
            })}
        .dataActions=${[
                {
                    name: 'New target',
                    iconName: 'lucide:Plus',
                    type: ['header'],
                    actionFunc: async () => {
                        await this.openStorageTargetDialog();
                    },
                },
                {
                    name: 'Edit target',
                    iconName: 'lucide:Pencil',
                    type: ['inRow', 'contextmenu'],
                    actionFunc: async (actionDataArg) => {
                        await this.openStorageTargetDialog(actionDataArg.item);
                    },
                },
                {
                    name: 'Delete target',
                    iconName: 'lucide:Trash2',
                    type: ['inRow', 'contextmenu'],
                    actionRelevancyCheckFunc: (targetArg) => {
                        return !storageClasses.some((storageClassArg) => storageClassArg.targetName === targetArg.name);
                    },
                    actionFunc: async (actionDataArg) => {
                        await this.deleteStorageTarget(actionDataArg.item);
                    },
                },
            ]}
      ></dees-table>
    `;
        }
        describeStorageTargetConfig(targetArg) {
            const config = targetArg.config || {};
            if (targetArg.kind === 'local' || targetArg.kind === 'backup') {
                return config.path || 'Docker managed volumes';
            }
            if (targetArg.kind === 'nfs') {
                return `${config.server || 'server'}:${config.exportPath || 'exportPath'}`;
            }
            if (targetArg.kind === 'smb') {
                return `//${config.server || 'server'}/${config.share || 'share'}`;
            }
            return [config.driver, config.device].filter(Boolean).join(' ') || 'Custom driver options';
        }
        async openStorageTargetDialog(targetArg) {
            const isEdit = !!targetArg;
            const config = targetArg?.config || {};
            const kindOptions = ['local', 'nfs', 'smb', 'backup', 'custom'];
            await plugins.deesCatalog.DeesModal.createAndShow({
                heading: targetArg ? `Edit storage target: ${targetArg.name}` : 'Create storage target',
                width: 'large',
                mobileFullscreen: true,
                content: html `
        ${this.renderStorageDialogStyles()}
        <div class="storage-dialog">
          ${targetArg
                    ? this.renderStorageDialogReadonly('Name', targetArg.name, 'Storage target names are stable references and cannot be renamed here.')
                    : this.renderStorageDialogTextField('name', 'Name', '', 'Lowercase stable name, for example nfs-apps or backup-nas.')}
          ${this.renderStorageDialogTextField('displayName', 'Display Name', targetArg?.displayName || '', 'Human-readable label shown in the settings UI.')}
          <label class="storage-dialog-field">
            <span>Kind</span>
            <select data-field="kind">
              ${kindOptions.map((kindArg) => html `
                <option value=${kindArg} ?selected=${kindArg === (targetArg?.kind || 'local')}>${kindArg}</option>
              `)}
            </select>
          </label>
          ${this.renderStorageDialogTextField('path', 'Local Path', config.path || '', 'Optional bind root for local and backup targets.')}
          ${this.renderStorageDialogTextField('server', 'Server', config.server || '', 'NFS or SMB server hostname/IP.')}
          ${this.renderStorageDialogTextField('exportPath', 'NFS Export Path', config.exportPath || '', 'NFS export path, for example /exports/onebox.')}
          ${this.renderStorageDialogTextField('share', 'SMB Share', config.share || '', 'SMB share name without leading slashes.')}
          ${this.renderStorageDialogTextField('username', 'SMB Username', config.username || '', 'Optional SMB username.')}
          ${this.renderStorageDialogTextField('password', 'SMB Password', config.password || '', 'Optional SMB password.', true)}
          ${this.renderStorageDialogTextField('domain', 'SMB Domain', config.domain || '', 'Optional SMB workgroup/domain.')}
          ${this.renderStorageDialogTextField('version', 'SMB Version', config.version || '', 'Optional CIFS version, for example 3.0.')}
          ${this.renderStorageDialogTextField('driver', 'Custom Driver', config.driver || '', 'Docker volume driver for custom targets.')}
          ${this.renderStorageDialogTextField('device', 'Custom Device', config.device || '', 'Docker local driver device override.')}
          <label class="storage-dialog-field full">
            <span>Driver Options JSON</span>
            <textarea data-field="options" .value=${this.stringifyOptions(config.options)}></textarea>
            <small>Optional JSON object merged into Docker volume options.</small>
          </label>
        </div>
      `,
                menuOptions: [
                    {
                        name: 'Cancel',
                        action: async (modalRef) => {
                            await modalRef?.destroy();
                        },
                    },
                    {
                        name: isEdit ? 'Save target' : 'Create target',
                        action: async (modalRef) => {
                            if (!modalRef)
                                return;
                            try {
                                const storageTarget = this.readStorageTargetFromModal(modalRef, targetArg);
                                await appstate.dispatchProtectedAction(appstate.upsertStorageTargetAction, {
                                    storageTarget,
                                });
                                await modalRef.destroy();
                                await plugins.deesCatalog.DeesToast.success(`Storage target ${storageTarget.name} saved`);
                            }
                            catch (err) {
                                await plugins.deesCatalog.DeesToast.error(this.getErrorMessage(err));
                            }
                        },
                    },
                ],
            });
        }
        async openStorageClassDialog(storageClassArg) {
            const isEdit = !!storageClassArg;
            const purposeOptions = ['runtime', 'database', 'backup'];
            const targetOptions = this.settingsState.storageTargets;
            await plugins.deesCatalog.DeesModal.createAndShow({
                heading: storageClassArg ? `Edit storage class: ${storageClassArg.name}` : 'Create storage class',
                width: 'large',
                mobileFullscreen: true,
                content: html `
        ${this.renderStorageDialogStyles()}
        <div class="storage-dialog">
          ${storageClassArg
                    ? this.renderStorageDialogReadonly('Name', storageClassArg.name, 'Storage class names are stable references and cannot be renamed here.')
                    : this.renderStorageDialogTextField('name', 'Name', '', 'Lowercase stable name, for example nfs-runtime or backup-cold.')}
          ${this.renderStorageDialogTextField('displayName', 'Display Name', storageClassArg?.displayName || '', 'Human-readable label shown in forms and tables.')}
          <label class="storage-dialog-field full">
            <span>Description</span>
            <textarea data-field="description" .value=${storageClassArg?.description || ''}></textarea>
          </label>
          <label class="storage-dialog-field">
            <span>Target</span>
            <select data-field="targetName">
              ${targetOptions.map((targetArg) => html `
                <option value=${targetArg.name} ?selected=${targetArg.name === (storageClassArg?.targetName || targetOptions[0]?.name)}>
                  ${targetArg.displayName} (${targetArg.name})
                </option>
              `)}
            </select>
          </label>
          <label class="storage-dialog-field">
            <span>Purpose</span>
            <select data-field="purpose">
              ${purposeOptions.map((purposeArg) => html `
                <option value=${purposeArg} ?selected=${purposeArg === (storageClassArg?.purpose || 'runtime')}>${purposeArg}</option>
              `)}
            </select>
          </label>
          ${this.renderStorageDialogTextField('driver', 'Driver Override', storageClassArg?.driver || '', 'Optional Docker volume driver override.')}
          <label class="storage-dialog-field checkbox-field">
            <input data-field="isDefault" type="checkbox" .checked=${!!storageClassArg?.isDefault} />
            <span>Use as default for this purpose</span>
          </label>
          <label class="storage-dialog-field full">
            <span>Class Options JSON</span>
            <textarea data-field="options" .value=${this.stringifyOptions(storageClassArg?.options)}></textarea>
            <small>Optional JSON object merged into Docker volume options after target options.</small>
          </label>
        </div>
      `,
                menuOptions: [
                    {
                        name: 'Cancel',
                        action: async (modalRef) => {
                            await modalRef?.destroy();
                        },
                    },
                    {
                        name: isEdit ? 'Save class' : 'Create class',
                        action: async (modalRef) => {
                            if (!modalRef)
                                return;
                            try {
                                const storageClass = this.readStorageClassFromModal(modalRef, storageClassArg);
                                await appstate.dispatchProtectedAction(appstate.upsertStorageClassAction, {
                                    storageClass,
                                });
                                await modalRef.destroy();
                                await plugins.deesCatalog.DeesToast.success(`Storage class ${storageClass.name} saved`);
                            }
                            catch (err) {
                                await plugins.deesCatalog.DeesToast.error(this.getErrorMessage(err));
                            }
                        },
                    },
                ],
            });
        }
        renderStorageDialogStyles() {
            return html `
      <style>
        .storage-dialog {
          display: grid;
          grid-template-columns: repeat(2, minmax(0, 1fr));
          gap: 14px;
        }

        .storage-dialog-field {
          display: flex;
          flex-direction: column;
          gap: 6px;
          min-width: 0;
          font-size: 13px;
          color: var(--dees-color-text-secondary);
        }

        .storage-dialog-field.full,
        .storage-dialog-readonly.full {
          grid-column: 1 / -1;
        }

        .storage-dialog-field > span,
        .storage-dialog-readonly-label {
          font-weight: 600;
          color: var(--dees-color-text-primary);
        }

        .storage-dialog-field select,
        .storage-dialog-field textarea,
        .storage-dialog-readonly-value {
          width: 100%;
          box-sizing: border-box;
          border: 1px solid var(--dees-color-border-subtle);
          border-radius: 8px;
          background: ${cssManager.bdTheme('#ffffff', '#09090b')};
          color: var(--dees-color-text-primary);
          padding: 10px 12px;
          font: inherit;
        }

        .storage-dialog-field textarea {
          min-height: 92px;
          resize: vertical;
          font-family: monospace;
        }

        .storage-dialog-field dees-input-text {
          width: 100%;
        }

        .storage-dialog-field small,
        .storage-dialog-readonly-hint {
          color: var(--dees-color-text-muted);
          font-size: 12px;
        }

        .storage-dialog-readonly {
          display: flex;
          flex-direction: column;
          gap: 6px;
        }

        .checkbox-field {
          flex-direction: row;
          align-items: center;
          padding-top: 26px;
        }

        .checkbox-field input {
          width: 16px;
          height: 16px;
        }

        @media (max-width: 700px) {
          .storage-dialog {
            grid-template-columns: 1fr;
          }
        }
      </style>
    `;
        }
        renderStorageDialogTextField(keyArg, labelArg, valueArg, descriptionArg, isPasswordArg = false) {
            return html `
      <label class="storage-dialog-field">
        <dees-input-text
          data-field=${keyArg}
          .key=${keyArg}
          .label=${labelArg}
          .value=${valueArg}
          .description=${descriptionArg}
          .isPasswordBool=${isPasswordArg}
        ></dees-input-text>
      </label>
    `;
        }
        renderStorageDialogReadonly(labelArg, valueArg, hintArg) {
            return html `
      <div class="storage-dialog-readonly">
        <div class="storage-dialog-readonly-label">${labelArg}</div>
        <div class="storage-dialog-readonly-value">${valueArg}</div>
        <div class="storage-dialog-readonly-hint">${hintArg}</div>
      </div>
    `;
        }
        readStorageTargetFromModal(modalRef, existingTargetArg) {
            const contentEl = this.getStorageModalContent(modalRef);
            const name = existingTargetArg?.name || this.normalizeStorageName(this.getStorageModalFieldValue(contentEl, 'name'));
            const displayName = this.getStorageModalFieldValue(contentEl, 'displayName') || name;
            const kind = (this.getStorageModalFieldValue(contentEl, 'kind') || existingTargetArg?.kind || 'local');
            const config = {};
            for (const key of ['path', 'server', 'exportPath', 'share', 'username', 'password', 'domain', 'version', 'driver', 'device']) {
                const value = this.getStorageModalFieldValue(contentEl, key);
                if (value) {
                    config[key] = value;
                }
            }
            const options = this.parseOptionsJson(this.getStorageModalFieldValue(contentEl, 'options'), 'Driver Options JSON');
            if (options) {
                config.options = options;
            }
            return {
                id: existingTargetArg?.id,
                name,
                displayName,
                kind,
                config,
            };
        }
        readStorageClassFromModal(modalRef, existingClassArg) {
            const contentEl = this.getStorageModalContent(modalRef);
            const name = existingClassArg?.name || this.normalizeStorageName(this.getStorageModalFieldValue(contentEl, 'name'));
            const displayName = this.getStorageModalFieldValue(contentEl, 'displayName') || name;
            const targetName = this.getStorageModalFieldValue(contentEl, 'targetName') || existingClassArg?.targetName || this.settingsState.storageTargets[0]?.name || '';
            if (!targetName) {
                throw new Error('Create a storage target before creating a storage class.');
            }
            const purpose = (this.getStorageModalFieldValue(contentEl, 'purpose') || existingClassArg?.purpose || 'runtime');
            const driver = this.getStorageModalFieldValue(contentEl, 'driver') || undefined;
            const description = this.getStorageModalFieldValue(contentEl, 'description') || undefined;
            const options = this.parseOptionsJson(this.getStorageModalFieldValue(contentEl, 'options'), 'Class Options JSON');
            return {
                id: existingClassArg?.id,
                name,
                displayName,
                description,
                targetName,
                purpose,
                isDefault: this.getStorageModalCheckboxValue(contentEl, 'isDefault'),
                driver,
                options,
            };
        }
        getStorageModalContent(modalRef) {
            const contentEl = modalRef.shadowRoot?.querySelector('.content');
            if (!contentEl) {
                throw new Error('Unable to read storage form content.');
            }
            return contentEl;
        }
        getStorageModalFieldValue(contentEl, fieldArg) {
            const field = contentEl.querySelector(`[data-field="${fieldArg}"]`);
            return field?.value?.trim() || '';
        }
        getStorageModalCheckboxValue(contentEl, fieldArg) {
            const field = contentEl.querySelector(`[data-field="${fieldArg}"]`);
            return !!field?.checked;
        }
        parseOptionsJson(valueArg, labelArg) {
            if (!valueArg)
                return undefined;
            let parsed;
            try {
                parsed = JSON.parse(valueArg);
            }
            catch {
                throw new Error(`${labelArg} must be valid JSON.`);
            }
            if (!parsed || Array.isArray(parsed) || typeof parsed !== 'object') {
                throw new Error(`${labelArg} must be a JSON object.`);
            }
            const options = {};
            for (const [key, value] of Object.entries(parsed)) {
                if (value === undefined || value === null)
                    continue;
                options[key] = String(value);
            }
            return Object.keys(options).length ? options : undefined;
        }
        stringifyOptions(optionsArg) {
            return optionsArg && Object.keys(optionsArg).length ? JSON.stringify(optionsArg, null, 2) : '';
        }
        normalizeStorageName(nameArg) {
            const name = nameArg.trim().toLowerCase();
            if (!/^[a-z0-9][a-z0-9_.-]{1,80}$/.test(name)) {
                throw new Error('Storage names must be 2-81 lowercase characters using letters, numbers, dot, dash, or underscore.');
            }
            return name;
        }
        async deleteStorageClass(storageClassArg) {
            if (storageClassArg.isDefault) {
                await plugins.deesCatalog.DeesToast.warning('Default storage classes cannot be deleted.');
                return;
            }
            if (!globalThis.confirm(`Delete storage class ${storageClassArg.name}?`))
                return;
            try {
                await appstate.dispatchProtectedAction(appstate.deleteStorageClassAction, {
                    name: storageClassArg.name,
                });
                await plugins.deesCatalog.DeesToast.success(`Storage class ${storageClassArg.name} deleted`);
            }
            catch (err) {
                await plugins.deesCatalog.DeesToast.error(this.getErrorMessage(err));
            }
        }
        async deleteStorageTarget(targetArg) {
            const usedByClasses = this.settingsState.storageClasses.filter((storageClassArg) => storageClassArg.targetName === targetArg.name);
            if (usedByClasses.length) {
                await plugins.deesCatalog.DeesToast.warning(`Target ${targetArg.name} is still used by ${usedByClasses.length} storage class(es).`);
                return;
            }
            if (!globalThis.confirm(`Delete storage target ${targetArg.name}?`))
                return;
            try {
                await appstate.dispatchProtectedAction(appstate.deleteStorageTargetAction, {
                    name: targetArg.name,
                });
                await plugins.deesCatalog.DeesToast.success(`Storage target ${targetArg.name} deleted`);
            }
            catch (err) {
                await plugins.deesCatalog.DeesToast.error(this.getErrorMessage(err));
            }
        }
        getErrorMessage(errArg) {
            return errArg instanceof Error ? errArg.message : String(errArg);
        }
        renderAdminUiSettings() {
            const settings = this.settingsState.settings;
            return html `
      <dees-tile>
        <div slot="header" class="gateway-header">
          <div class="gateway-heading">
            <span class="gateway-title">Onebox Admin UI</span>
            <span class="gateway-subtitle">Configure the required WebAuthn hostname for this Onebox dashboard</span>
          </div>
        </div>
        <div class="gateway-content">
          ${this.renderGatewayInput('adminUiDomain', 'Admin UI Domain', settings?.adminUiDomain || '', 'Required, for example onebox.example.com. This HTTPS hostname becomes the passkey RP ID and is pinned after enrollment; route visibility is configured separately.')}
          ${this.renderGatewayReadonly('Local Target', 'Onebox OpsServer on port 3000', 'The external gateway forwards to CoreTraffic, which forwards this hostname to the Onebox Admin UI.')}
        </div>
        <div slot="footer" class="gateway-footer">
          <button class="tile-button primary" type="button" @click=${() => this.saveAdminUiSettings()}>
            Save Admin UI Domain
          </button>
        </div>
      </dees-tile>
    `;
        }
        renderExternalGatewaySettings() {
            const settings = this.settingsState.settings;
            const mode = settings?.dcrouterMode || 'managed';
            return html `
      <dees-tile>
        <div slot="header" class="gateway-header">
          <div class="gateway-heading">
            <span class="gateway-title">dcrouter Gateway</span>
            <span class="gateway-subtitle">Run a local managed dcrouter or delegate routing to an external dcrouter</span>
          </div>
        </div>
        <div class="gateway-mode-row">
          ${this.renderModeButton('managed', 'Managed Local', mode)}
          ${this.renderModeButton('external', 'External dcrouter', mode)}
          ${this.renderModeButton('disabled', 'Disabled', mode)}
        </div>
        ${mode === 'managed' ? this.renderManagedGatewayStatus() : null}
        <div class="gateway-content">
          ${mode === 'managed' ? html `
            ${this.renderGatewayInput('dcrouterManagedImage', 'dcrouter Image', settings?.dcrouterManagedImage || 'code.foss.global/serve.zone/dcrouter:latest', 'OCI image used for the managed local gateway.')}
            ${this.renderGatewayInput('dcrouterManagedDataDir', 'Data Directory', settings?.dcrouterManagedDataDir || './.nogit/dcrouter-data', 'Host directory mounted into the dcrouter container.')}
            ${this.renderGatewayInput('dcrouterManagedOpsPort', 'Local Ops Port', String(settings?.dcrouterManagedOpsPort || 3300), 'Bound to 127.0.0.1 for Onebox to call dcrouter APIs.')}
            ${this.renderGatewayInput('dcrouterManagedHttpPort', 'Public HTTP Port', String(settings?.dcrouterManagedHttpPort || 80), 'Host port owned by dcrouter for HTTP ingress.')}
            ${this.renderGatewayInput('dcrouterManagedHttpsPort', 'Public HTTPS Port', String(settings?.dcrouterManagedHttpsPort || 443), 'Host port owned by dcrouter for HTTPS ingress.')}
            ${this.renderGatewayReadonly('Gateway Client ID', settings?.dcrouterGatewayClientId || 'Created when managed dcrouter starts', 'Diagnostic only. Onebox manages this local client automatically.')}
          ` : mode === 'external' ? html `
            ${this.renderGatewayInput('dcrouterGatewayUrl', 'Gateway URL', settings?.dcrouterGatewayUrl || '', 'Base URL of the dcrouter OpsServer.')}
            ${this.renderGatewayInput('dcrouterGatewayApiToken', 'API Token', settings?.dcrouterGatewayApiToken || '', 'Requires gateway-client access in dcrouter.', true)}
            ${this.renderGatewayReadonly('Gateway Client ID', settings?.dcrouterGatewayClientId || 'Derived from token', 'Configure this in dcrouter Gateway Clients, not in Onebox.')}
            ${this.renderGatewayInput('dcrouterTargetHost', 'Target Host', settings?.dcrouterTargetHost || '', 'Defaults to the configured server IP when empty.')}
            ${this.renderGatewayInput('dcrouterTargetPort', 'Target Port', String(settings?.dcrouterTargetPort || 80), 'Internal HTTP port dcrouter forwards to.')}
          ` : html `
            <div class="gateway-disabled">dcrouter route delegation is disabled. Onebox will keep using its local CoreTraffic directly.</div>
          `}
        </div>
        <div slot="footer" class="gateway-footer">
          <button class="tile-button primary" type="button" @click=${() => this.saveExternalGatewaySettings()}>
            Save dcrouter Settings
          </button>
        </div>
      </dees-tile>
    `;
        }
        renderModeButton(mode, label, activeMode) {
            return html `
      <button
        class="gateway-mode-button ${activeMode === mode ? 'active' : ''}"
        @click=${() => this.updateGatewayDraft('dcrouterMode', mode)}
      >${label}</button>
    `;
        }
        renderManagedGatewayStatus() {
            const status = this.settingsState.managedDcRouterStatus;
            const stateText = status?.running ? (status.healthy ? 'Running' : 'Starting') : 'Stopped';
            return html `
      <div class="gateway-status-row">
        <div>
          <div class="gateway-status-label">Managed dcrouter</div>
          <div class="gateway-status-value">${stateText}${status?.gatewayUrl ? ` at ${status.gatewayUrl}` : ''}</div>
          ${status?.message ? html `<div class="gateway-status-error">${status.message}</div>` : null}
        </div>
        <div class="gateway-actions">
          <dees-button .text=${'Start'} .type=${'default'} @click=${() => appstate.dispatchProtectedAction(appstate.startManagedDcRouterAction, null)}></dees-button>
          <dees-button .text=${'Restart'} .type=${'default'} @click=${() => appstate.dispatchProtectedAction(appstate.restartManagedDcRouterAction, null)}></dees-button>
          <dees-button .text=${'Stop'} .type=${'default'} @click=${() => appstate.dispatchProtectedAction(appstate.stopManagedDcRouterAction, null)}></dees-button>
        </div>
      </div>
    `;
        }
        renderGatewayInput(key, label, value, hint, isPassword = false) {
            return html `
      <div class="gateway-field ${key === 'dcrouterGatewayUrl' || key === 'adminUiDomain' ? 'full' : ''}">
        <dees-input-text
          .key=${key}
          .label=${label}
          .value=${value}
          .description=${hint}
          .isPasswordBool=${isPassword}
          @input=${(event) => this.updateGatewayDraft(key, event.target.value)}
        ></dees-input-text>
      </div>
    `;
        }
        renderGatewayReadonly(label, value, hint) {
            return html `
      <div class="gateway-readonly">
        <div class="gateway-readonly-label">${label}</div>
        <div class="gateway-readonly-value">${value}</div>
        <div class="gateway-readonly-hint">${hint}</div>
      </div>
    `;
        }
        updateGatewayDraft(key, value) {
            const currentSettings = this.settingsState.settings || {};
            const numberKeys = new Set([
                'dcrouterTargetPort',
                'dcrouterManagedOpsPort',
                'dcrouterManagedHttpPort',
                'dcrouterManagedHttpsPort',
            ]);
            const nextValue = numberKeys.has(key) ? Number(value) || 0 : value;
            this.settingsState = {
                ...this.settingsState,
                settings: {
                    ...currentSettings,
                    [key]: nextValue,
                },
            };
        }
        async saveExternalGatewaySettings() {
            const settings = this.settingsState.settings;
            if (!settings)
                return;
            await appstate.dispatchProtectedAction(appstate.updateSettingsAction, {
                settings: {
                    dcrouterMode: settings.dcrouterMode || 'managed',
                    dcrouterManagedImage: settings.dcrouterManagedImage || 'code.foss.global/serve.zone/dcrouter:latest',
                    dcrouterManagedOpsPort: Number(settings.dcrouterManagedOpsPort) || 3300,
                    dcrouterManagedHttpPort: Number(settings.dcrouterManagedHttpPort) || 80,
                    dcrouterManagedHttpsPort: Number(settings.dcrouterManagedHttpsPort) || 443,
                    dcrouterManagedDataDir: settings.dcrouterManagedDataDir || './.nogit/dcrouter-data',
                    dcrouterGatewayUrl: settings.dcrouterGatewayUrl || '',
                    dcrouterGatewayApiToken: settings.dcrouterGatewayApiToken || '',
                    dcrouterTargetHost: settings.dcrouterTargetHost || '',
                    dcrouterTargetPort: Number(settings.dcrouterTargetPort) || 80,
                },
            });
            await appstate.dispatchProtectedAction(appstate.fetchManagedDcRouterStatusAction, null);
        }
        async saveAdminUiSettings() {
            const settings = this.settingsState.settings;
            if (!settings)
                return;
            await appstate.dispatchProtectedAction(appstate.updateSettingsAction, {
                settings: {
                    adminUiDomain: settings.adminUiDomain || '',
                },
            });
        }
        async saveStorageSettings() {
            const settings = this.settingsState.settings;
            if (!settings)
                return;
            await appstate.dispatchProtectedAction(appstate.updateSettingsAction, {
                settings: {
                    platformStorageClass: settings.platformStorageClass || 'local-hot',
                },
            });
        }
        static {
            __runInitializers(_classThis, _classExtraInitializers);
        }
    };
    return ObViewSettings = _classThis;
})();
export { ObViewSettings };
//# sourceMappingURL=data:application/json;base64, 