Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions src/app/admin/api-keys/api-keys.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,12 +223,15 @@ export class APIKeyService {
);
if (details?.reason !== 'done') return;
details.loading('Removing API key...');
await remove({
id: key.id,
query_params: {},
path: 'api_keys',
});
details.close();
try {
await remove({
id: key.id,
query_params: {},
path: 'api_keys',
});
} finally {
details.close();
}
notifySuccess('Successfully removed API key.');
this._change.set(Date.now());
}
Expand Down
2 changes: 1 addition & 1 deletion src/app/admin/brokers.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ export class AdminBrokersComponent extends AsyncHandler implements OnInit {
},
this._dialog,
);
if (!details) return;
if (details.reason !== 'done') return;
details.loading('Deleting broker...');
const err = await removeBroker(item.id).catch((_) => _);
details.close();
Expand Down
2 changes: 1 addition & 1 deletion src/app/admin/build-list.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ export class PlaceBuildListComponent implements OnInit {
},
this._dialog,
);
if (!details) return;
if (details.reason !== 'done') return;
details.loading(i18n('ADMIN.BUILD_LIST_REMOVE_LOADING'));
const err = await cancelBuildJob(i.id).catch((_) => _);
details.close();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -270,7 +270,7 @@ export class PlaceClusterTaskListComponent
ref.close();
},
(err) => {
ref.componentInstance.loading.set(null);
ref.componentInstance?.loading.set('');
this.killing.set(null);
notifyError(
i18n('ADMIN.CLUSTER_PROCESS_KILL_ERROR', {
Expand Down
2 changes: 1 addition & 1 deletion src/app/admin/edge.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ export class PlaceEdgeComponent implements OnInit {
},
this._dialog,
);
if (!details) return;
if (details.reason !== 'done') return;
details.loading('Removing edge...');
const err = await removeEdge(i.id).catch((_) => _);
details.close();
Expand Down
2 changes: 1 addition & 1 deletion src/app/admin/extensions.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,7 @@ export class PlaceExtensionsComponent implements OnInit {
await this.updateDomain(ext_list).catch((e) =>
notifyError(`Error removing extension: ${e}`),
);
ref.componentInstance.loading.set('');
ref.componentInstance?.loading.set('');
ref.close();
});
}
Expand Down
9 changes: 7 additions & 2 deletions src/app/admin/resource-imports.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,13 @@ export class ResourceImportsComponent implements OnInit {

if (resp?.reason !== 'done') return;
resp.loading(i18n('ADMIN.RESOURCE_IMPORTS_ALL_LOADING'));
await Promise.all(missing.map((_) => this.importResource(_, false)));
resp.close();
try {
await Promise.all(
missing.map((_) => this.importResource(_, false)),
);
} finally {
resp.close();
}
notifySuccess(
i18n('ADMIN.RESOURCE_IMPORTS_ALL_SUCCESS', {
count: missing.length,
Expand Down
2 changes: 1 addition & 1 deletion src/app/admin/signage-plugins/signage-plugins.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ export class AdminSignagePluginsComponent
},
this._dialog,
);
if (!details) return;
if (details.reason !== 'done') return;
details.loading(i18n('ADMIN.SIGNAGE_PLUGINS_REMOVE_LOADING'));
const err = await removeSignagePlugin(item.id).catch((_) => _);
details.close();
Expand Down
2 changes: 1 addition & 1 deletion src/app/admin/staff-api.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ export class PlaceStaffAPIComponent implements OnInit {
},
this._dialog,
);
if (!details || !details.reason) return;
if (details.reason !== 'done') return;
details.loading('Removing tenant from domain...');
const system = await del(`/api/staff/v1/tenants/${tenant.id}`).catch(
(err) => {
Expand Down
7 changes: 5 additions & 2 deletions src/app/admin/storage/storage.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -229,8 +229,11 @@ export class StorageComponent implements OnInit {
);
if (resp.reason !== 'done') return;
resp.loading(i18n('ADMIN.STORAGE_REMOVE_LOADING'));
await removeStorage(item.id);
resp.close();
try {
await removeStorage(item.id);
} finally {
resp.close();
}
this.loadStorage();
}

Expand Down
15 changes: 9 additions & 6 deletions src/app/admin/upload-library.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -539,12 +539,15 @@ export class UploadLibraryComponent extends AsyncHandler implements OnInit {
);
if (result?.reason !== 'done') return;
result.loading(i18n('ADMIN.UPLOADS_LIB_REMOVE_LOADING'));
await remove({
id: upload.id,
query_params: {},
path: 'uploads',
});
result.close();
try {
await remove({
id: upload.id,
query_params: {},
path: 'uploads',
});
} finally {
result.close();
}
this.refresh.update((value) => value + 1);
}
}
6 changes: 3 additions & 3 deletions src/app/domains/domain-state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export class DomainStateService {
),
waitForEvent(ref.afterClosed()),
]);
if (!details) return;
if (details?.reason !== 'done') return;
this._changed.set(new Date().valueOf());
}

Expand All @@ -263,7 +263,7 @@ export class DomainStateService {
},
this._dialog,
);
if (!details) return;
if (details.reason !== 'done') return;
details.loading('Deleting domain application...');
const err = await removeApplication(item.id).catch((_) => _);
details.close();
Expand Down Expand Up @@ -294,7 +294,7 @@ export class DomainStateService {
),
waitForEvent(ref.afterClosed()),
]);
if (!details) return;
if (details?.reason !== 'done') return;
this._changed.set(new Date().valueOf());
}

Expand Down
8 changes: 4 additions & 4 deletions src/app/drivers/driver-state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class DriverStateService {
},
this._dialog,
);
if (!details?.reason) return details.close();
if (details.reason !== 'done') return details.close();
details.loading('Updating driver...');
const success = await updateDriver(item.id, {
...item,
Expand All @@ -133,7 +133,7 @@ export class DriverStateService {
},
this._dialog,
);
if (!details?.reason) return details.close();
if (details.reason !== 'done') return details.close();
details.loading('Recompiling driver... This may take a while.');
await recompileDriver(item.id).catch(async (e) => {
console.log('Error:', e);
Expand All @@ -160,7 +160,7 @@ export class DriverStateService {
},
this._dialog,
);
if (!details?.reason) return details.close();
if (details.reason !== 'done') return details.close();
details.loading('Reload driver... This may take a while.');
const success = await reloadDriver(item.id).catch(() => false);
if (success === false) {
Expand All @@ -186,7 +186,7 @@ export class DriverStateService {
},
this._dialog,
);
if (!details?.reason) return;
if (details.reason !== 'done') return;
const system = await removeSystemModule(
this.active_item.id,
device.id,
Expand Down
39 changes: 30 additions & 9 deletions src/app/overlays/confirm-modal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
OnInit,
Output,
computed,
effect,
inject,
signal,
} from '@angular/core';
Expand Down Expand Up @@ -104,11 +105,6 @@ export interface ConfirmModalData {
close_delay?: number;
}

/**
* Renders a receipt as tab separated rows, for pasting into a ticket or
* spreadsheet. Failed rows are marked so a partial run is not mistaken for a
* complete one.
*/
/**
* Readable text for whatever an option's `details()` rejected with.
*
Expand All @@ -133,6 +129,11 @@ export function describeError(error: unknown): string {
return 'Unknown error';
}

/**
* Renders a receipt as tab separated rows, for pasting into a ticket or
* spreadsheet. Failed rows are marked so a partial run is not mistaken for a
* complete one.
*/
export function receiptToTsv(result: ConfirmModalResult): string {
const rows = [
...result.items.map((item) => [item.type, item.name, item.id]),
Expand All @@ -156,17 +157,26 @@ export const CONFIRM_METADATA = {
height: 'auto',
};

export interface ConfirmRepsonse {
reason: 'done' | '' | null;
export interface ConfirmResponse {
/**
* `'done'` when the user confirmed. `undefined` when the modal was
* dismissed (Cancel, Escape or backdrop click).
*/
reason?: 'done';
metadata?: { options?: ConfirmModalSelection };
loading: (_: string) => void;
close: () => void;
}

/**
* Opens a confirm modal and resolves when the user confirms or dismisses it.
* Always resolves to an object, so callers must check
* `details.reason !== 'done'` before they run the action.
*/
export async function openConfirmModal(
data: ConfirmModalData,
dialog: MatDialog,
): Promise<ConfirmRepsonse> {
): Promise<ConfirmResponse> {
const ref = dialog.open<ConfirmModalComponent, ConfirmModalData>(
ConfirmModalComponent,
{
Expand All @@ -182,7 +192,7 @@ export async function openConfirmModal(
),
lastValueFrom(ref.afterClosed()),
])),
loading: (s) => ref.componentInstance.loading.set(s),
loading: (s) => ref.componentInstance?.loading.set(s),
close: () => ref.close(),
};
}
Expand Down Expand Up @@ -552,6 +562,17 @@ export class ConfirmModalComponent extends AsyncHandler implements OnInit {
/** Allow the user to close the modal */
public readonly enableClose = () => (this._dialog_ref.disableClose = false);

constructor() {
super();
// Block Escape and backdrop dismissal while the action runs. Material
// nulls `componentInstance` on close, so a dismissal mid-action makes
// the caller's later `loading.set()` throw. The receipt stays
// dismissable.
effect(() => {
this._dialog_ref.disableClose = !!this.loading() && !this.result();
});
}

public ngOnInit() {
// An option that starts enabled has never been toggled, so nothing has
// asked it for a breakdown. Without this it would count as selected
Expand Down
24 changes: 13 additions & 11 deletions src/app/systems/system-state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -279,7 +279,7 @@ export class SystemStateService extends AsyncHandler {
content: `Are you sure you want to start this system?<br>All stopped modules within the system will boot up.`,
icon: { type: 'icon', class: 'backoffice-controller-play' },
});
if (!details?.reason) return;
if (details.reason !== 'done') return;
details.loading('Starting system...');
const error = await startSystem(this.active_item.id)
.then(() => null)
Expand Down Expand Up @@ -307,7 +307,7 @@ export class SystemStateService extends AsyncHandler {
content: `Are you sure you want to stop this system?<br>All modules will be immediately stopped regardless of any other systems they may be in.`,
icon: { type: 'icon', class: 'backoffice-controller-stop' },
});
if (!details?.reason) return;
if (details.reason !== 'done') return;
details.loading('Stopping system...');
const error = await stopSystem(this.active_item.id)
.then(() => null)
Expand Down Expand Up @@ -383,7 +383,7 @@ export class SystemStateService extends AsyncHandler {
),
waitForEvent(ref.afterClosed()),
]);
if (!details?.reason) return ref.close();
if (details?.reason !== 'action') return ref.close();
const system = ref.componentInstance.item as PlaceSystem;
if (!system) return ref.close();
await addSystemModule(system.id, device.id).catch((_e) => {
Expand Down Expand Up @@ -424,7 +424,7 @@ export class SystemStateService extends AsyncHandler {
),
waitForEvent(ref.afterClosed()),
]);
if (!details?.reason) return ref.close();
if (details?.reason !== 'action') return ref.close();
const t = await this.addTrigger(
ref.componentInstance.item as PlaceTrigger,
);
Expand Down Expand Up @@ -463,7 +463,7 @@ export class SystemStateService extends AsyncHandler {
),
waitForEvent(ref.afterClosed()),
]);
if (!details?.reason) return;
if (details?.reason !== 'action') return;
instance.loading = 'Saving trigger settings...';

const url = `${apiEndpoint()}/systems/${
Expand Down Expand Up @@ -491,7 +491,8 @@ export class SystemStateService extends AsyncHandler {
content: `<p>Are you sure you want remove trigger "${trigger.name}"?</p><p>Configuration will be updated <strong>immediately</strong>.</p>`,
icon: { type: 'icon', content: 'delete' },
});
if (!details?.reason) return;
if (details.reason !== 'done') return;
details.loading('Removing trigger...');
await removeSystemTrigger(this.active_item.id, trigger.id).catch(
(err) => {
details.close();
Expand All @@ -514,7 +515,7 @@ export class SystemStateService extends AsyncHandler {
content: `Are you sure you want to change the module priority?<br>Settings will be updated immediately for the system.`,
icon: { type: 'icon', content: 'layers' },
});
if (!details?.reason) return;
if (details.reason !== 'done') return;
details.loading('Updating module order...');
const list: string[] = [...this.active_item.modules];
moveItemInArray(list, fst, snd);
Expand Down Expand Up @@ -545,7 +546,7 @@ export class SystemStateService extends AsyncHandler {
content: `Are you sure you want to sort modules by class?<br>Modules with the same class name will be grouped together.`,
icon: { type: 'icon', content: 'sort' },
});
if (!details?.reason) return;
if (details.reason !== 'done') return;
details.loading('Sorting modules by class...');
let sorted_modules = [];
if (alphabetical) {
Expand Down Expand Up @@ -616,7 +617,7 @@ export class SystemStateService extends AsyncHandler {
content: `Are you sure you want to change the zone priority?<br>Settings will be updated immediately for the system.`,
icon: { type: 'icon', content: 'layers' },
});
if (!details?.reason) return;
if (details.reason !== 'done') return;
details.loading('Updating zone order...');
const resp = await updateSystem(this.active_item.id, {
...this.active_item,
Expand Down Expand Up @@ -666,7 +667,8 @@ export class SystemStateService extends AsyncHandler {
content: `Remove ${device.driver_id} from this system?<br>If this is not used elsewhere the associated data will be removed immediately.`,
icon: { type: 'icon', content: 'delete' },
});
if (!details?.reason) return;
if (details.reason !== 'done') return;
details.loading('Removing module...');
const system = await removeSystemModule(
this.active_item.id,
device.id,
Expand Down Expand Up @@ -715,7 +717,7 @@ export class SystemStateService extends AsyncHandler {
content: `<p>Are you sure you want remove zone "${zone.name}" from the system?</p>Configuration will be updated immediately.`,
icon: { type: 'icon', content: 'delete' },
});
if (!details?.reason) return;
if (details.reason !== 'done') return;
const zones = this.active_item.zones.filter((z) => z !== zone.id);
const system = await updateSystem(this.active_item.id, {
...this.active_item,
Expand Down
Loading
Loading