feat(update): add Onebox self-upgrade flow
This commit is contained in:
@@ -41,6 +41,14 @@ export class ObAppShell extends DeesElement {
|
||||
refreshInterval: 30000,
|
||||
};
|
||||
|
||||
@state()
|
||||
accessor systemState: appstate.ISystemState = {
|
||||
status: null,
|
||||
};
|
||||
|
||||
@state()
|
||||
accessor globalMessages: plugins.deesCatalog.IGlobalMessage[] = [];
|
||||
|
||||
@state()
|
||||
accessor loginLoading: boolean = false;
|
||||
|
||||
@@ -126,6 +134,8 @@ export class ObAppShell extends DeesElement {
|
||||
];
|
||||
|
||||
private resolvedViewTabs: IResolvedView[] = [];
|
||||
private suppressedUpdateVersion = '';
|
||||
private upgradeFlowRunning = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
@@ -135,12 +145,21 @@ export class ObAppShell extends DeesElement {
|
||||
.select((stateArg: appstate.ILoginState) => stateArg)
|
||||
.subscribe((loginState: appstate.ILoginState) => {
|
||||
this.loginState = loginState;
|
||||
this.updateGlobalMessages();
|
||||
if (loginState.isLoggedIn) {
|
||||
appstate.systemStatePart.dispatchAction(appstate.fetchSystemStatusAction, null);
|
||||
}
|
||||
});
|
||||
this.rxSubscriptions.push(loginSubscription);
|
||||
|
||||
const systemSubscription = appstate.systemStatePart
|
||||
.select((stateArg: appstate.ISystemState) => stateArg)
|
||||
.subscribe((systemState: appstate.ISystemState) => {
|
||||
this.systemState = systemState;
|
||||
this.updateGlobalMessages();
|
||||
});
|
||||
this.rxSubscriptions.push(systemSubscription);
|
||||
|
||||
const uiSubscription = appstate.uiStatePart
|
||||
.select((stateArg: appstate.IUiState) => stateArg)
|
||||
.subscribe((uiState: appstate.IUiState) => {
|
||||
@@ -214,6 +233,7 @@ export class ObAppShell extends DeesElement {
|
||||
name="Onebox"
|
||||
.viewTabs=${this.resolvedViewTabs}
|
||||
.selectedView=${this.currentViewTab}
|
||||
.globalMessages=${this.globalMessages}
|
||||
>
|
||||
</dees-simple-appdash>
|
||||
</dees-simple-login>
|
||||
@@ -324,6 +344,177 @@ export class ObAppShell extends DeesElement {
|
||||
}
|
||||
}
|
||||
|
||||
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}`;
|
||||
}
|
||||
|
||||
private syncAppdashView(viewName: string, subviewName: string | null): void {
|
||||
const appDash = this.shadowRoot?.querySelector('dees-simple-appdash') as any;
|
||||
if (!appDash || this.resolvedViewTabs.length === 0) return;
|
||||
|
||||
Reference in New Issue
Block a user