import * as plugins from '../../../plugins.js';
import * as appstate from '../../../appstate.js';
import * as shared from '../../shared/index.js';
import {
DeesElement,
css,
cssManager,
customElement,
html,
state,
} from '@design.estate/dees-element';
type TBaseOsImageBuild = any;
@customElement('cloudly-view-baseos')
export class CloudlyViewBaseOs extends DeesElement {
@state() private builds: TBaseOsImageBuild[] = [];
@state() private isLoading = false;
private refreshTimer?: number;
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css`
.layout {
display: grid;
grid-template-columns: 420px 1fr;
gap: 16px;
padding: 24px 0;
}
.builds {
display: flex;
flex-direction: column;
gap: 12px;
}
.build {
border: 1px solid #2a2f3a;
border-radius: 12px;
padding: 16px;
background: #10151f;
}
.build-head {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.meta {
color: #9aa4b2;
font-size: 13px;
margin-top: 8px;
}
.logs {
margin-top: 12px;
max-height: 120px;
overflow: auto;
font-family: monospace;
font-size: 12px;
color: #bac4d1;
white-space: pre-wrap;
}
@media (max-width: 900px) {
.layout {
grid-template-columns: 1fr;
}
}
`,
];
public async connectedCallback() {
await super.connectedCallback();
await this.loadBuilds();
this.refreshTimer = window.setInterval(() => this.loadBuilds(), 5000);
}
public async disconnectedCallback() {
await super.disconnectedCallback();
if (this.refreshTimer) {
window.clearInterval(this.refreshTimer);
}
}
public render() {
return html`
BaseOS Images
this.createBuild((eventArg.detail as any).data)}>
${this.builds.length === 0
? html``
: this.builds.map((buildArg) => this.renderBuild(buildArg))}
`;
}
private renderBuild(buildArg: TBaseOsImageBuild) {
const data = buildArg.data;
return html`
${data.hostname || buildArg.id}
${data.architecture} · ${data.cloudlyUrl}
${data.artifact ? `${data.artifact.filename} · ${Math.round(data.artifact.size / 1024 / 1024)} MB · ${data.artifact.sha256.slice(0, 12)}...` : data.errorText || 'Waiting for artifact'}
${data.status === 'ready'
? html`
this.downloadBuild(buildArg.id)}>`
: ''}
${data.logs?.length ? html`
${data.logs.slice(-8).join('\n')}
` : ''}
`;
}
private async loadBuilds() {
try {
const response = await this.fireBaseOsRequest('getBaseOsImageBuilds', {});
this.builds = response.builds || [];
} catch (error) {
console.error('Failed to load BaseOS image builds:', error);
}
}
private async createBuild(formDataArg: any) {
this.isLoading = true;
try {
const response = await this.fireBaseOsRequest('createBaseOsImageBuild', {
build: {
architecture: formDataArg.architecture || 'amd64',
cloudlyUrl: formDataArg.cloudlyUrl || window.location.origin,
hostname: formDataArg.hostname || undefined,
sourceImageUrl: formDataArg.sourceImageUrl || undefined,
wifi: formDataArg.wifiSsid
? {
ssid: formDataArg.wifiSsid,
password: formDataArg.wifiPassword || undefined,
}
: undefined,
sshPublicKey: formDataArg.sshPublicKey || undefined,
},
});
this.builds = [response.build, ...this.builds];
plugins.deesCatalog.DeesToast.createAndShow({ message: 'BaseOS image build queued', type: 'success' });
} catch (error: any) {
plugins.deesCatalog.DeesToast.createAndShow({ message: `Failed to create image build: ${error.message}`, type: 'error' });
} finally {
this.isLoading = false;
}
}
private async downloadBuild(buildIdArg: string) {
const response = await this.fireBaseOsRequest('createBaseOsImageDownloadUrl', {
buildId: buildIdArg,
});
window.location.href = response.url;
}
private async fireBaseOsRequest(methodArg: string, payloadArg: Record) {
appstate.apiClient.identity = appstate.loginStatePart.getState()?.identity || null as any;
if (!appstate.apiClient.typedsocketClient) {
await appstate.apiClient.start();
}
const request = appstate.apiClient.typedsocketClient.createTypedRequest(methodArg);
return await request.fire({
identity: appstate.apiClient.identity,
...payloadArg,
});
}
}
declare global {
interface HTMLElementTagNameMap {
'cloudly-view-baseos': CloudlyViewBaseOs;
}
}