import * as plugins from '../plugins.js';
import * as shared from './shared/index.js';
import * as appstate from '../appstate.js';
import * as interfaces from '../../ts_interfaces/index.js';
import { appRouter } from '../router.js';
import {
DeesElement,
customElement,
html,
state,
css,
cssManager,
type TemplateResult,
} from '@design.estate/dees-element';
@customElement('ob-view-appstore')
export class ObViewAppStore extends DeesElement {
@state()
accessor appStoreState: appstate.IAppStoreState = {
apps: [],
upgradeableServices: [],
};
@state()
accessor currentView: 'grid' | 'detail' = 'grid';
@state()
accessor selectedApp: interfaces.requests.ICatalogApp | null = null;
@state()
accessor selectedAppMeta: interfaces.requests.IAppMeta | null = null;
@state()
accessor selectedAppConfig: interfaces.requests.IAppVersionConfig | null = null;
@state()
accessor selectedVersion: string = '';
@state()
accessor editableEnvVars: Array<{ key: string; value: string; description: string; required?: boolean; platformInjected?: boolean }> = [];
@state()
accessor serviceName: string = '';
@state()
accessor loading: boolean = false;
@state()
accessor deployMode: boolean = false;
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
.detail-card {
background: var(--ci-shade-1, #09090b);
border: 1px solid var(--ci-shade-2, #27272a);
border-radius: 8px;
padding: 24px;
margin-bottom: 16px;
}
.detail-header {
display: flex;
align-items: flex-start;
gap: 16px;
margin-bottom: 24px;
}
.detail-icon {
width: 64px;
height: 64px;
border-radius: 12px;
background: var(--ci-shade-2, #27272a);
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
font-weight: 700;
color: var(--ci-shade-5, #a1a1aa);
flex-shrink: 0;
}
.detail-title {
font-size: 24px;
font-weight: 700;
color: var(--ci-shade-7, #e4e4e7);
margin: 0 0 4px 0;
}
.detail-category {
display: inline-block;
padding: 2px 10px;
border-radius: 9999px;
font-size: 12px;
font-weight: 500;
background: var(--ci-shade-2, #27272a);
color: var(--ci-shade-5, #a1a1aa);
margin-bottom: 8px;
}
.detail-description {
font-size: 14px;
color: var(--ci-shade-5, #a1a1aa);
line-height: 1.6;
margin: 0;
}
.detail-meta {
display: flex;
gap: 16px;
margin-top: 8px;
font-size: 13px;
color: var(--ci-shade-4, #71717a);
}
.detail-meta a {
color: var(--ci-shade-5, #a1a1aa);
text-decoration: none;
}
.detail-meta a:hover {
text-decoration: underline;
}
.section-label {
font-size: 13px;
font-weight: 600;
color: var(--ci-shade-5, #a1a1aa);
text-transform: uppercase;
letter-spacing: 0.05em;
margin-bottom: 10px;
}
.badge {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 10px;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
background: rgba(59, 130, 246, 0.15);
color: #60a5fa;
margin-right: 6px;
margin-bottom: 6px;
}
.version-row {
display: flex;
align-items: center;
gap: 16px;
}
.version-select {
background: var(--ci-shade-2, #27272a);
border: 1px solid var(--ci-shade-3, #3f3f46);
border-radius: 6px;
padding: 8px 12px;
color: var(--ci-shade-7, #e4e4e7);
font-size: 14px;
cursor: pointer;
}
.image-tag {
font-family: monospace;
font-size: 13px;
color: var(--ci-shade-5, #a1a1aa);
background: var(--ci-shade-2, #27272a);
padding: 4px 8px;
border-radius: 4px;
}
.env-table {
width: 100%;
border-collapse: collapse;
}
.env-table th {
text-align: left;
font-size: 12px;
font-weight: 500;
color: var(--ci-shade-4, #71717a);
padding: 8px 8px 8px 0;
border-bottom: 1px solid var(--ci-shade-2, #27272a);
}
.env-table td {
padding: 6px 8px 6px 0;
vertical-align: middle;
}
.env-input {
width: 100%;
background: var(--ci-shade-2, #27272a);
border: 1px solid var(--ci-shade-3, #3f3f46);
border-radius: 4px;
padding: 6px 8px;
color: var(--ci-shade-7, #e4e4e7);
font-size: 13px;
font-family: monospace;
box-sizing: border-box;
}
.env-input:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.env-key {
font-family: monospace;
font-size: 13px;
color: var(--ci-shade-6, #d4d4d8);
white-space: nowrap;
}
.env-desc {
font-size: 12px;
color: var(--ci-shade-4, #71717a);
}
.env-badge {
font-size: 10px;
padding: 1px 6px;
border-radius: 3px;
margin-left: 6px;
}
.env-badge.required {
background: rgba(239, 68, 68, 0.15);
color: #f87171;
}
.env-badge.auto {
background: rgba(34, 197, 94, 0.15);
color: #4ade80;
}
.name-input {
background: var(--ci-shade-2, #27272a);
border: 1px solid var(--ci-shade-3, #3f3f46);
border-radius: 6px;
padding: 10px 14px;
color: var(--ci-shade-7, #e4e4e7);
font-size: 14px;
width: 300px;
box-sizing: border-box;
}
.actions-row {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 24px;
}
.btn {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 10px 20px;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: opacity 200ms ease;
}
.btn:hover { opacity: 0.9; }
.btn-primary {
background: var(--ci-shade-7, #e4e4e7);
color: var(--ci-shade-0, #09090b);
}
.btn-secondary {
background: transparent;
border: 1px solid var(--ci-shade-2, #27272a);
color: var(--ci-shade-6, #d4d4d8);
}
.loading-spinner {
padding: 32px;
text-align: center;
color: var(--ci-shade-4, #71717a);
}
`,
];
constructor() {
super();
const sub = appstate.appStoreStatePart
.select((s) => s)
.subscribe((newState) => {
this.appStoreState = newState;
});
this.rxSubscriptions.push(sub);
}
async connectedCallback() {
super.connectedCallback();
await appstate.appStoreStatePart.dispatchAction(appstate.fetchAppTemplatesAction, null);
}
public render(): TemplateResult {
switch (this.currentView) {
case 'detail':
return this.renderDetailView();
default:
return this.renderGridView();
}
}
private renderGridView(): TemplateResult {
const appTemplates = this.appStoreState.apps.map((app) => ({
id: app.id,
name: app.name,
description: app.description,
category: app.category,
iconName: app.iconName,
iconUrl: app.iconUrl,
image: '',
port: 0,
}));
return html`
${app.description}
| Variable | Value | Description |
|---|---|---|
| ${ev.key} ${ev.required ? html`required` : ''} ${ev.platformInjected ? html`auto` : ''} | this.handleEnvVarChange(index, (e.target as HTMLInputElement).value)} /> | ${ev.description || ''} |