2026-02-24 18:15:44 +00:00
|
|
|
import * as plugins from '../plugins.js';
|
|
|
|
|
import * as appstate from '../appstate.js';
|
|
|
|
|
import * as interfaces from '../../ts_interfaces/index.js';
|
2026-03-21 19:36:25 +00:00
|
|
|
import { appRouter } from '../router.js';
|
2026-02-24 18:15:44 +00:00
|
|
|
import {
|
|
|
|
|
DeesElement,
|
|
|
|
|
customElement,
|
|
|
|
|
html,
|
|
|
|
|
state,
|
|
|
|
|
css,
|
|
|
|
|
cssManager,
|
|
|
|
|
type TemplateResult,
|
|
|
|
|
} from '@design.estate/dees-element';
|
|
|
|
|
|
2026-05-21 18:38:44 +00:00
|
|
|
interface IUnresolvedView {
|
|
|
|
|
slug?: string;
|
|
|
|
|
name: string;
|
|
|
|
|
iconName?: string;
|
|
|
|
|
element?: Promise<any>;
|
|
|
|
|
subViews?: IUnresolvedView[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface IResolvedView {
|
|
|
|
|
slug?: string;
|
|
|
|
|
name: string;
|
|
|
|
|
iconName?: string;
|
|
|
|
|
element?: any;
|
|
|
|
|
subViews?: IResolvedView[];
|
|
|
|
|
}
|
2026-02-24 18:15:44 +00:00
|
|
|
|
|
|
|
|
@customElement('ob-app-shell')
|
|
|
|
|
export class ObAppShell extends DeesElement {
|
|
|
|
|
@state()
|
|
|
|
|
accessor loginState: appstate.ILoginState = { identity: null, isLoggedIn: false };
|
|
|
|
|
|
|
|
|
|
@state()
|
|
|
|
|
accessor uiState: appstate.IUiState = {
|
|
|
|
|
activeView: 'dashboard',
|
2026-05-21 18:38:44 +00:00
|
|
|
activeSubview: null,
|
2026-02-24 18:15:44 +00:00
|
|
|
autoRefresh: true,
|
|
|
|
|
refreshInterval: 30000,
|
|
|
|
|
};
|
|
|
|
|
|
2026-05-24 11:49:43 +00:00
|
|
|
@state()
|
|
|
|
|
accessor systemState: appstate.ISystemState = {
|
|
|
|
|
status: null,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@state()
|
|
|
|
|
accessor globalMessages: plugins.deesCatalog.IGlobalMessage[] = [];
|
|
|
|
|
|
2026-02-24 18:15:44 +00:00
|
|
|
@state()
|
|
|
|
|
accessor loginLoading: boolean = false;
|
|
|
|
|
|
|
|
|
|
@state()
|
|
|
|
|
accessor loginError: string = '';
|
|
|
|
|
|
2026-05-21 18:38:44 +00:00
|
|
|
private viewTabs: IUnresolvedView[] = [
|
|
|
|
|
{
|
|
|
|
|
slug: 'dashboard',
|
|
|
|
|
name: 'Dashboard',
|
|
|
|
|
iconName: 'lucide:layoutDashboard',
|
|
|
|
|
element: (async () => (await import('./ob-view-dashboard.js')).ObViewDashboard)(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
slug: 'apps',
|
|
|
|
|
name: 'Apps',
|
|
|
|
|
iconName: 'lucide:store',
|
|
|
|
|
subViews: [
|
|
|
|
|
{
|
|
|
|
|
slug: 'app-store',
|
|
|
|
|
name: 'App Store',
|
|
|
|
|
iconName: 'lucide:store',
|
|
|
|
|
element: (async () => (await import('./ob-view-appstore.js')).ObViewAppStore)(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
slug: 'services',
|
|
|
|
|
name: 'Services',
|
|
|
|
|
iconName: 'lucide:boxes',
|
|
|
|
|
element: (async () => (await import('./ob-view-services.js')).ObViewServices)(),
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
slug: 'network',
|
|
|
|
|
name: 'Network',
|
|
|
|
|
iconName: 'lucide:network',
|
|
|
|
|
subViews: [
|
|
|
|
|
{
|
|
|
|
|
slug: 'proxy',
|
|
|
|
|
name: 'Proxy',
|
|
|
|
|
iconName: 'lucide:route',
|
|
|
|
|
element: (async () => (await import('./ob-view-network.js')).ObViewNetwork)(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
slug: 'domains',
|
|
|
|
|
name: 'Domains',
|
|
|
|
|
iconName: 'lucide:globe',
|
|
|
|
|
element: (async () => (await import('./ob-view-domains.js')).ObViewDomains)(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
slug: 'dns-records',
|
|
|
|
|
name: 'DNS Records',
|
|
|
|
|
iconName: 'lucide:listTree',
|
|
|
|
|
element: (async () => (await import('./ob-view-dns-records.js')).ObViewDnsRecords)(),
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
slug: 'registry',
|
|
|
|
|
name: 'Registry',
|
|
|
|
|
iconName: 'lucide:package',
|
|
|
|
|
subViews: [
|
|
|
|
|
{
|
|
|
|
|
slug: 'registries',
|
|
|
|
|
name: 'Registries',
|
|
|
|
|
iconName: 'lucide:package',
|
|
|
|
|
element: (async () => (await import('./ob-view-registries.js')).ObViewRegistries)(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
slug: 'tokens',
|
|
|
|
|
name: 'Tokens',
|
|
|
|
|
iconName: 'lucide:key',
|
|
|
|
|
element: (async () => (await import('./ob-view-tokens.js')).ObViewTokens)(),
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
slug: 'settings',
|
|
|
|
|
name: 'Settings',
|
|
|
|
|
iconName: 'lucide:settings',
|
|
|
|
|
element: (async () => (await import('./ob-view-settings.js')).ObViewSettings)(),
|
|
|
|
|
},
|
2026-02-24 18:15:44 +00:00
|
|
|
];
|
|
|
|
|
|
2026-05-21 18:38:44 +00:00
|
|
|
private resolvedViewTabs: IResolvedView[] = [];
|
2026-05-24 11:49:43 +00:00
|
|
|
private suppressedUpdateVersion = '';
|
|
|
|
|
private upgradeFlowRunning = false;
|
2026-02-24 18:15:44 +00:00
|
|
|
|
|
|
|
|
constructor() {
|
|
|
|
|
super();
|
|
|
|
|
document.title = 'Onebox';
|
|
|
|
|
|
|
|
|
|
const loginSubscription = appstate.loginStatePart
|
2026-05-21 18:38:44 +00:00
|
|
|
.select((stateArg: appstate.ILoginState) => stateArg)
|
|
|
|
|
.subscribe((loginState: appstate.ILoginState) => {
|
2026-02-24 18:15:44 +00:00
|
|
|
this.loginState = loginState;
|
2026-05-24 11:49:43 +00:00
|
|
|
this.updateGlobalMessages();
|
2026-02-24 18:15:44 +00:00
|
|
|
if (loginState.isLoggedIn) {
|
|
|
|
|
appstate.systemStatePart.dispatchAction(appstate.fetchSystemStatusAction, null);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
this.rxSubscriptions.push(loginSubscription);
|
|
|
|
|
|
2026-05-24 11:49:43 +00:00
|
|
|
const systemSubscription = appstate.systemStatePart
|
|
|
|
|
.select((stateArg: appstate.ISystemState) => stateArg)
|
|
|
|
|
.subscribe((systemState: appstate.ISystemState) => {
|
|
|
|
|
this.systemState = systemState;
|
|
|
|
|
this.updateGlobalMessages();
|
|
|
|
|
});
|
|
|
|
|
this.rxSubscriptions.push(systemSubscription);
|
|
|
|
|
|
2026-02-24 18:15:44 +00:00
|
|
|
const uiSubscription = appstate.uiStatePart
|
2026-05-21 18:38:44 +00:00
|
|
|
.select((stateArg: appstate.IUiState) => stateArg)
|
|
|
|
|
.subscribe((uiState: appstate.IUiState) => {
|
2026-02-24 18:15:44 +00:00
|
|
|
this.uiState = uiState;
|
2026-05-21 18:38:44 +00:00
|
|
|
this.syncAppdashView(uiState.activeView, uiState.activeSubview);
|
2026-02-24 18:15:44 +00:00
|
|
|
});
|
|
|
|
|
this.rxSubscriptions.push(uiSubscription);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 18:38:44 +00:00
|
|
|
private async resolveViewTabs(tabs: IUnresolvedView[]): Promise<IResolvedView[]> {
|
|
|
|
|
return Promise.all(
|
|
|
|
|
tabs.map(async (tab) => {
|
|
|
|
|
const resolvedTab: IResolvedView = {
|
|
|
|
|
slug: tab.slug,
|
|
|
|
|
name: tab.name,
|
|
|
|
|
iconName: tab.iconName,
|
|
|
|
|
};
|
|
|
|
|
if (tab.element) {
|
|
|
|
|
resolvedTab.element = await tab.element;
|
|
|
|
|
}
|
|
|
|
|
if (tab.subViews) {
|
|
|
|
|
resolvedTab.subViews = await this.resolveViewTabs(tab.subViews);
|
|
|
|
|
}
|
|
|
|
|
return resolvedTab;
|
|
|
|
|
}),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private slugFor(view: IResolvedView): string {
|
|
|
|
|
return view.slug ?? view.name.toLowerCase().replace(/\s+/g, '-');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private findParent(view: IResolvedView): IResolvedView | undefined {
|
|
|
|
|
return this.resolvedViewTabs.find((viewTab) => viewTab.subViews?.includes(view));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private findViewBySlug(viewSlug: string, subviewSlug: string | null): IResolvedView | undefined {
|
|
|
|
|
const topLevelView = this.resolvedViewTabs.find((view) => this.slugFor(view) === viewSlug);
|
|
|
|
|
if (!topLevelView) return undefined;
|
|
|
|
|
if (subviewSlug && topLevelView.subViews) {
|
|
|
|
|
return topLevelView.subViews.find((subview) => this.slugFor(subview) === subviewSlug) ?? topLevelView;
|
|
|
|
|
}
|
|
|
|
|
return topLevelView;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private get currentViewTab(): IResolvedView | undefined {
|
|
|
|
|
if (this.resolvedViewTabs.length === 0) return undefined;
|
|
|
|
|
return this.findViewBySlug(this.uiState.activeView, this.uiState.activeSubview) ?? this.resolvedViewTabs[0];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static override styles = [
|
2026-02-24 18:15:44 +00:00
|
|
|
cssManager.defaultStyles,
|
|
|
|
|
css`
|
|
|
|
|
:host {
|
|
|
|
|
display: block;
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100%;
|
|
|
|
|
}
|
|
|
|
|
.maincontainer {
|
|
|
|
|
width: 100%;
|
|
|
|
|
height: 100vh;
|
|
|
|
|
}
|
|
|
|
|
`,
|
|
|
|
|
];
|
|
|
|
|
|
2026-05-21 18:38:44 +00:00
|
|
|
public override render(): TemplateResult {
|
2026-02-24 18:15:44 +00:00
|
|
|
return html`
|
|
|
|
|
<div class="maincontainer">
|
|
|
|
|
<dees-simple-login name="Onebox">
|
|
|
|
|
<dees-simple-appdash
|
|
|
|
|
name="Onebox"
|
|
|
|
|
.viewTabs=${this.resolvedViewTabs}
|
2026-05-21 18:38:44 +00:00
|
|
|
.selectedView=${this.currentViewTab}
|
2026-05-24 11:49:43 +00:00
|
|
|
.globalMessages=${this.globalMessages}
|
2026-02-24 18:15:44 +00:00
|
|
|
>
|
|
|
|
|
</dees-simple-appdash>
|
|
|
|
|
</dees-simple-login>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 18:38:44 +00:00
|
|
|
public override async firstUpdated() {
|
|
|
|
|
this.resolvedViewTabs = await this.resolveViewTabs(this.viewTabs);
|
2026-02-24 18:15:44 +00:00
|
|
|
this.requestUpdate();
|
|
|
|
|
await this.updateComplete;
|
|
|
|
|
|
|
|
|
|
const simpleLogin = this.shadowRoot!.querySelector('dees-simple-login') as any;
|
|
|
|
|
if (simpleLogin) {
|
|
|
|
|
simpleLogin.addEventListener('login', (e: CustomEvent) => {
|
|
|
|
|
this.login(e.detail.data.username, e.detail.data.password);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const appDash = this.shadowRoot!.querySelector('dees-simple-appdash') as any;
|
|
|
|
|
if (appDash) {
|
|
|
|
|
appDash.addEventListener('view-select', (e: CustomEvent) => {
|
2026-05-21 18:38:44 +00:00
|
|
|
const view = e.detail.view as IResolvedView;
|
|
|
|
|
const parent = this.findParent(view);
|
|
|
|
|
const currentState = appstate.uiStatePart.getState();
|
|
|
|
|
if (parent) {
|
|
|
|
|
const parentSlug = this.slugFor(parent);
|
|
|
|
|
const subviewSlug = this.slugFor(view);
|
|
|
|
|
if (currentState.activeView === parentSlug && currentState.activeSubview === subviewSlug) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
appRouter.navigateToView(parentSlug, subviewSlug);
|
|
|
|
|
} else {
|
|
|
|
|
const slug = this.slugFor(view);
|
|
|
|
|
if (currentState.activeView === slug && !currentState.activeSubview) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
appRouter.navigateToView(slug);
|
|
|
|
|
}
|
2026-02-24 18:15:44 +00:00
|
|
|
});
|
|
|
|
|
appDash.addEventListener('logout', async () => {
|
|
|
|
|
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (appDash && this.resolvedViewTabs.length > 0) {
|
2026-05-21 18:38:44 +00:00
|
|
|
const currentUiState = appstate.uiStatePart.getState();
|
|
|
|
|
const initialView =
|
|
|
|
|
this.findViewBySlug(currentUiState.activeView, currentUiState.activeSubview) ||
|
|
|
|
|
this.resolvedViewTabs[0];
|
2026-02-24 18:15:44 +00:00
|
|
|
await appDash.loadView(initialView);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const loginState = appstate.loginStatePart.getState();
|
|
|
|
|
if (loginState.identity?.jwt) {
|
|
|
|
|
if (loginState.identity.expiresAt > Date.now()) {
|
2026-03-21 19:36:25 +00:00
|
|
|
this.loginState = loginState;
|
|
|
|
|
if (simpleLogin) {
|
|
|
|
|
await simpleLogin.switchToSlottedContent();
|
|
|
|
|
}
|
2026-02-24 18:15:44 +00:00
|
|
|
try {
|
|
|
|
|
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
|
|
|
interfaces.requests.IReq_GetSystemStatus
|
|
|
|
|
>('/typedrequest', 'getSystemStatus');
|
|
|
|
|
const response = await typedRequest.fire({ identity: loginState.identity });
|
|
|
|
|
appstate.systemStatePart.setState({ status: response.status });
|
|
|
|
|
} catch (err) {
|
|
|
|
|
console.warn('Stored session invalid, returning to login:', err);
|
|
|
|
|
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
|
2026-03-21 19:36:25 +00:00
|
|
|
if (simpleLogin) {
|
|
|
|
|
window.location.reload();
|
|
|
|
|
}
|
2026-02-24 18:15:44 +00:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
await appstate.loginStatePart.dispatchAction(appstate.logoutAction, null);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async login(username: string, password: string) {
|
|
|
|
|
const domtools = await this.domtoolsPromise;
|
|
|
|
|
const simpleLogin = this.shadowRoot!.querySelector('dees-simple-login') as any;
|
|
|
|
|
const form = simpleLogin?.shadowRoot?.querySelector('dees-form') as any;
|
|
|
|
|
|
|
|
|
|
if (form) {
|
|
|
|
|
form.setStatus('pending', 'Logging in...');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const newState = await appstate.loginStatePart.dispatchAction(appstate.loginAction, {
|
|
|
|
|
username,
|
|
|
|
|
password,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (newState.identity) {
|
|
|
|
|
if (form) {
|
|
|
|
|
form.setStatus('success', 'Logged in!');
|
|
|
|
|
}
|
|
|
|
|
if (simpleLogin) {
|
|
|
|
|
await simpleLogin.switchToSlottedContent();
|
|
|
|
|
}
|
|
|
|
|
await appstate.systemStatePart.dispatchAction(appstate.fetchSystemStatusAction, null);
|
|
|
|
|
} else {
|
|
|
|
|
if (form) {
|
|
|
|
|
form.setStatus('error', 'Login failed!');
|
|
|
|
|
await domtools.convenience.smartdelay.delayFor(2000);
|
|
|
|
|
form.reset();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-24 11:49:43 +00:00
|
|
|
private updateGlobalMessages(): void {
|
|
|
|
|
const updateStatus = this.systemState.status?.onebox.update;
|
|
|
|
|
if (
|
|
|
|
|
!this.loginState.isLoggedIn ||
|
|
|
|
|
!updateStatus?.updateAvailable ||
|
|
|
|
|
!updateStatus.latestVersion ||
|
|
|
|
|
updateStatus.latestVersion === this.suppressedUpdateVersion
|
|
|
|
|
) {
|
|
|
|
|
this.globalMessages = [];
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.globalMessages = [
|
|
|
|
|
{
|
|
|
|
|
id: `onebox-update-${updateStatus.latestVersion}`,
|
|
|
|
|
type: 'info',
|
|
|
|
|
icon: 'lucide:download',
|
|
|
|
|
message: `Onebox ${updateStatus.latestVersion} is available. Current version: ${updateStatus.currentVersion}.`,
|
|
|
|
|
dismissible: false,
|
|
|
|
|
actions: [
|
|
|
|
|
{
|
|
|
|
|
name: 'Update Now',
|
|
|
|
|
iconName: 'lucide:download',
|
|
|
|
|
action: () => this.startOneboxUpgradeFlow(),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Release Notes',
|
|
|
|
|
iconName: 'lucide:fileText',
|
|
|
|
|
action: () => this.openUpdateUrl(updateStatus.changelogUrl || updateStatus.releaseUrl),
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: 'Later',
|
|
|
|
|
iconName: 'lucide:clock',
|
|
|
|
|
action: () => {
|
|
|
|
|
this.suppressedUpdateVersion = updateStatus.latestVersion || '';
|
|
|
|
|
this.updateGlobalMessages();
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async startOneboxUpgradeFlow(): Promise<void> {
|
|
|
|
|
if (this.upgradeFlowRunning) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const identity = appstate.loginStatePart.getState().identity;
|
|
|
|
|
const updateStatus = this.systemState.status?.onebox.update;
|
|
|
|
|
if (!identity || !updateStatus?.latestVersion) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.upgradeFlowRunning = true;
|
|
|
|
|
const updater = await plugins.deesCatalog.DeesUpdater.createAndShow({
|
|
|
|
|
currentVersion: updateStatus.currentVersion,
|
|
|
|
|
updatedVersion: updateStatus.latestVersion,
|
|
|
|
|
moreInfoUrl: updateStatus.releaseUrl,
|
|
|
|
|
changelogUrl: updateStatus.changelogUrl,
|
|
|
|
|
successAction: 'reload',
|
|
|
|
|
successDelayMs: 30000,
|
|
|
|
|
successActionLabel: 'Reloading Onebox UI',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
updater.updateProgress({
|
|
|
|
|
percentage: 10,
|
|
|
|
|
indeterminate: true,
|
|
|
|
|
statusText: 'Requesting upgrade...',
|
|
|
|
|
terminalLines: ['Requesting Onebox upgrade'],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
|
|
|
interfaces.requests.IReq_StartOneboxUpgrade
|
|
|
|
|
>('/typedrequest', 'startOneboxUpgrade');
|
|
|
|
|
const response = await typedRequest.fire({ identity });
|
|
|
|
|
|
|
|
|
|
if (!response.upgrade.accepted) {
|
|
|
|
|
updater.markUpdateError(response.upgrade.message);
|
|
|
|
|
await this.delay(5000);
|
|
|
|
|
await updater.destroy();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updater.appendProgressLine(response.upgrade.message);
|
|
|
|
|
if (response.upgrade.pid) {
|
|
|
|
|
updater.appendProgressLine(`Upgrade process PID: ${response.upgrade.pid}`);
|
|
|
|
|
}
|
|
|
|
|
if (response.upgrade.logPath) {
|
|
|
|
|
updater.appendProgressLine(`Upgrade log: ${response.upgrade.logPath}`);
|
|
|
|
|
}
|
|
|
|
|
updater.updateProgress({
|
|
|
|
|
percentage: 45,
|
|
|
|
|
indeterminate: true,
|
|
|
|
|
statusText: 'Installer started...',
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await this.waitForOneboxUpgrade(updater, response.upgrade.targetVersion, identity);
|
|
|
|
|
await updater.markUpdateReady();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
updater.markUpdateError(this.getErrorMessage(error));
|
|
|
|
|
await this.delay(5000);
|
|
|
|
|
await updater.destroy();
|
|
|
|
|
} finally {
|
|
|
|
|
this.upgradeFlowRunning = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async waitForOneboxUpgrade(
|
|
|
|
|
updaterArg: plugins.deesCatalog.DeesUpdater,
|
|
|
|
|
targetVersionArg: string,
|
|
|
|
|
identityArg: interfaces.data.IIdentity,
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
const normalizedTargetVersion = this.normalizeVersion(targetVersionArg);
|
|
|
|
|
const timeoutAt = Date.now() + 90000;
|
|
|
|
|
let attempt = 0;
|
|
|
|
|
|
|
|
|
|
updaterArg.appendProgressLine('Waiting for Onebox to restart with the new version');
|
|
|
|
|
while (Date.now() < timeoutAt) {
|
|
|
|
|
await this.delay(5000);
|
|
|
|
|
attempt++;
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const typedRequest = new plugins.domtools.plugins.typedrequest.TypedRequest<
|
|
|
|
|
interfaces.requests.IReq_GetSystemStatus
|
|
|
|
|
>('/typedrequest', 'getSystemStatus');
|
|
|
|
|
const response = await typedRequest.fire({ identity: identityArg });
|
|
|
|
|
const onlineVersion = this.normalizeVersion(response.status.onebox.version);
|
|
|
|
|
updaterArg.appendProgressLine(`Onebox API answered with ${onlineVersion}`);
|
|
|
|
|
|
|
|
|
|
if (onlineVersion === normalizedTargetVersion) {
|
|
|
|
|
updaterArg.updateProgress({
|
|
|
|
|
percentage: 100,
|
|
|
|
|
indeterminate: false,
|
|
|
|
|
statusText: `Onebox ${normalizedTargetVersion} is online.`,
|
|
|
|
|
});
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
updaterArg.appendProgressLine('Onebox API is restarting...');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updaterArg.updateProgress({
|
|
|
|
|
percentage: Math.min(95, 45 + attempt * 5),
|
|
|
|
|
indeterminate: true,
|
|
|
|
|
statusText: `Waiting for Onebox ${normalizedTargetVersion}...`,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updaterArg.appendProgressLine('Timed out waiting for the version check; reloading the UI anyway');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private openUpdateUrl(urlArg: string): void {
|
|
|
|
|
window.open(urlArg, '_blank', 'noopener,noreferrer');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private async delay(millisecondsArg: number): Promise<void> {
|
|
|
|
|
const domtools = await this.domtoolsPromise;
|
|
|
|
|
await domtools.convenience.smartdelay.delayFor(millisecondsArg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private getErrorMessage(errorArg: unknown): string {
|
|
|
|
|
return errorArg instanceof Error ? errorArg.message : String(errorArg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private normalizeVersion(versionArg: string): string {
|
|
|
|
|
const trimmedVersion = versionArg.trim();
|
|
|
|
|
return trimmedVersion.startsWith('v') ? trimmedVersion : `v${trimmedVersion}`;
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-21 18:38:44 +00:00
|
|
|
private syncAppdashView(viewName: string, subviewName: string | null): void {
|
2026-02-24 18:15:44 +00:00
|
|
|
const appDash = this.shadowRoot?.querySelector('dees-simple-appdash') as any;
|
|
|
|
|
if (!appDash || this.resolvedViewTabs.length === 0) return;
|
2026-05-21 18:38:44 +00:00
|
|
|
|
|
|
|
|
const targetTab = this.findViewBySlug(viewName, subviewName);
|
|
|
|
|
if (!targetTab || appDash.selectedView === targetTab) return;
|
|
|
|
|
|
2026-02-24 18:15:44 +00:00
|
|
|
appDash.loadView(targetTab);
|
|
|
|
|
}
|
|
|
|
|
}
|