From c2d808b64cdcb4c21ef5f97099b2b30007dbf4ae Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 23 Apr 2026 18:12:12 +0200 Subject: [PATCH 1/2] feat(frontend): sync ufos with mission control --- .../app/notifications/Notifications.svelte | 7 ++ .../notifications/NotificationsAlerts.svelte | 17 +++- .../NotificationsAlertsLoader.svelte | 24 ++++- .../NotificationsCanisterAlert.svelte | 2 +- .../out-of-sync/OutOfSyncSegmentsModal.svelte | 2 - .../modules/out-of-sync/OutOfSyncForm.svelte | 19 +++- .../out-of-sync/OutOfSyncSegments.svelte | 24 +++-- .../mission-control-ufos.derived.ts | 5 + .../src/lib/derived/out-of-sync.derived.ts | 35 ++++++- src/frontend/src/lib/i18n/en.json | 6 +- src/frontend/src/lib/i18n/zh-cn.json | 6 +- .../access-keys/key.admin.services.ts | 40 +++++++- .../attach-detach/out-of-sync.services.ts | 97 ++++++++++++++++--- .../factory/_factory.attach.services.ts | 31 +++++- .../mission-control.ufos.services.ts | 6 +- src/frontend/src/lib/types/i18n.d.ts | 2 + 16 files changed, 279 insertions(+), 44 deletions(-) diff --git a/src/frontend/src/lib/components/app/notifications/Notifications.svelte b/src/frontend/src/lib/components/app/notifications/Notifications.svelte index 7f2ff64185..65b5561147 100644 --- a/src/frontend/src/lib/components/app/notifications/Notifications.svelte +++ b/src/frontend/src/lib/components/app/notifications/Notifications.svelte @@ -34,6 +34,9 @@ let satelliteCanisterData = $state(undefined); let satelliteWarnings = $state(undefined); + let ufoCanisterData = $state(undefined); + let ufoWarnings = $state(undefined); + let upgradeWarning = $state(false); let canisterWarnings = $state(false); let canisterNotifications = $state(false); @@ -98,6 +101,8 @@ {orbiterWarnings} {satelliteCanisterData} {satelliteWarnings} + {ufoCanisterData} + {ufoWarnings} bind:alerts={hasAlerts} bind:upgradeWarning bind:canisterWarnings @@ -121,6 +126,8 @@ {outOfSyncWarnings} {satelliteCanisterData} {satelliteWarnings} + {ufoCanisterData} + {ufoWarnings} {upgradeWarning} /> {:else if $store.tabId === $store.tabs[1].id} diff --git a/src/frontend/src/lib/components/app/notifications/NotificationsAlerts.svelte b/src/frontend/src/lib/components/app/notifications/NotificationsAlerts.svelte index b388b6d1d8..b5d052e478 100644 --- a/src/frontend/src/lib/components/app/notifications/NotificationsAlerts.svelte +++ b/src/frontend/src/lib/components/app/notifications/NotificationsAlerts.svelte @@ -8,7 +8,9 @@ import { satellite } from '$lib/derived/satellite.derived'; import { i18n } from '$lib/stores/app/i18n.store'; import type { CanisterData, CanisterWarning } from '$lib/types/canister'; - import { overviewLink } from '$lib/utils/nav.utils'; + import { overviewLink, ufoLink } from '$lib/utils/nav.utils'; + import IconUfo from '$lib/components/icons/IconUfo.svelte'; + import { ufo } from '$lib/derived/ufo.derived'; interface Props { missionControlCanisterData: CanisterData | undefined; @@ -17,6 +19,8 @@ orbiterWarnings: CanisterWarning | undefined; satelliteCanisterData: CanisterData | undefined; satelliteWarnings: CanisterWarning | undefined; + ufoCanisterData: CanisterData | undefined; + ufoWarnings: CanisterWarning | undefined; close: () => void; alerts: boolean; upgradeWarning: boolean; @@ -31,6 +35,8 @@ orbiterWarnings, satelliteCanisterData, satelliteWarnings, + ufoCanisterData, + ufoWarnings, close, alerts, upgradeWarning, @@ -69,6 +75,15 @@ segment="satellite" warnings={satelliteWarnings} /> + + {/if} {#if outOfSyncWarnings} diff --git a/src/frontend/src/lib/components/app/notifications/NotificationsAlertsLoader.svelte b/src/frontend/src/lib/components/app/notifications/NotificationsAlertsLoader.svelte index 39ee0bd1a3..d8d5953071 100644 --- a/src/frontend/src/lib/components/app/notifications/NotificationsAlertsLoader.svelte +++ b/src/frontend/src/lib/components/app/notifications/NotificationsAlertsLoader.svelte @@ -2,10 +2,15 @@ import NotificationsCanisterLoader from '$lib/components/app/notifications/NotificationsCanisterLoader.svelte'; import { missionControlId } from '$lib/derived/console/account.mission-control.derived'; import { orbiter } from '$lib/derived/orbiter.derived'; - import { outOfSyncOrbiters, outOfSyncSatellites } from '$lib/derived/out-of-sync.derived'; + import { + outOfSyncOrbiters, + outOfSyncUfos, + outOfSyncSatellites + } from '$lib/derived/out-of-sync.derived'; import { satellite } from '$lib/derived/satellite.derived'; import { versionsLoaded, versionsUpgradeWarning } from '$lib/derived/version.derived'; import type { CanisterData, CanisterWarning } from '$lib/types/canister'; + import { ufo } from '$lib/derived/ufo.derived'; interface Props { missionControlCanisterData: CanisterData | undefined; @@ -14,6 +19,8 @@ orbiterWarnings: CanisterWarning | undefined; satelliteCanisterData: CanisterData | undefined; satelliteWarnings: CanisterWarning | undefined; + ufoCanisterData: CanisterData | undefined; + ufoWarnings: CanisterWarning | undefined; alerts: boolean; upgradeWarning: boolean; canisterWarnings: boolean; @@ -27,6 +34,8 @@ orbiterWarnings = $bindable(undefined), satelliteCanisterData = $bindable(undefined), satelliteWarnings = $bindable(undefined), + ufoCanisterData = $bindable(undefined), + ufoWarnings = $bindable(undefined), alerts = $bindable(false), upgradeWarning = $bindable(false), canisterWarnings = $bindable(false), @@ -39,12 +48,15 @@ let hasCanisterWarnings = $derived( hasWarnings(missionControlWarnings) || hasWarnings(orbiterWarnings) || - hasWarnings(satelliteWarnings) + hasWarnings(satelliteWarnings) || + hasWarnings(ufoWarnings) ); let hasUpgradeWarning = $derived($versionsLoaded && $versionsUpgradeWarning); - let hasOutOfSyncWarning = $derived($outOfSyncSatellites === true || $outOfSyncOrbiters === true); + let hasOutOfSyncWarning = $derived( + $outOfSyncSatellites === true || $outOfSyncOrbiters === true || $outOfSyncUfos === true + ); let hasNotifications = $derived(hasCanisterWarnings || hasUpgradeWarning || hasOutOfSyncWarning); @@ -73,3 +85,9 @@ bind:warnings={satelliteWarnings} bind:data={satelliteCanisterData} /> + + diff --git a/src/frontend/src/lib/components/app/notifications/NotificationsCanisterAlert.svelte b/src/frontend/src/lib/components/app/notifications/NotificationsCanisterAlert.svelte index 20a09a4103..387bffc9a0 100644 --- a/src/frontend/src/lib/components/app/notifications/NotificationsCanisterAlert.svelte +++ b/src/frontend/src/lib/components/app/notifications/NotificationsCanisterAlert.svelte @@ -13,7 +13,7 @@ data: CanisterData | undefined; close: () => void; href: string; - segment: 'satellite' | 'mission_control' | 'orbiter'; + segment: 'satellite' | 'mission_control' | 'orbiter' | 'ufo'; } let { warnings, close, data, href, cyclesIcon, segment }: Props = $props(); diff --git a/src/frontend/src/lib/components/modals/out-of-sync/OutOfSyncSegmentsModal.svelte b/src/frontend/src/lib/components/modals/out-of-sync/OutOfSyncSegmentsModal.svelte index be03680619..8e4c0cdc78 100644 --- a/src/frontend/src/lib/components/modals/out-of-sync/OutOfSyncSegmentsModal.svelte +++ b/src/frontend/src/lib/components/modals/out-of-sync/OutOfSyncSegmentsModal.svelte @@ -28,8 +28,6 @@ const onsubmit = async ($event: SubmitEvent) => { $event.preventDefault(); - $event.preventDefault(); - onProgress(undefined); wizardBusy.start(); diff --git a/src/frontend/src/lib/components/modules/out-of-sync/OutOfSyncForm.svelte b/src/frontend/src/lib/components/modules/out-of-sync/OutOfSyncForm.svelte index a1a7b3312f..1fb8cf78b1 100644 --- a/src/frontend/src/lib/components/modules/out-of-sync/OutOfSyncForm.svelte +++ b/src/frontend/src/lib/components/modules/out-of-sync/OutOfSyncForm.svelte @@ -1,10 +1,15 @@ {#snippet withSegments()}
    + {#if nonNullish(orbiter)} + {@const orbName = orbiterName(orbiter)} + +
  • + + {isEmptyString(orbName) ? $i18n.analytics.orbiter : orbName} + +
  • + {/if} + {#each satellites as satellite (satellite.satellite_id.toText())}
  • @@ -28,15 +40,13 @@
  • {/each} - {#if nonNullish(orbiter)} - {@const orbName = orbiterName(orbiter)} - + {#each ufos as ufo (ufo.ufo_id.toText())}
  • - - {isEmptyString(orbName) ? $i18n.analytics.orbiter : orbName} + + {metadataUiName(ufo)}
  • - {/if} + {/each}
{/snippet} diff --git a/src/frontend/src/lib/derived/mission-control/mission-control-ufos.derived.ts b/src/frontend/src/lib/derived/mission-control/mission-control-ufos.derived.ts index ca57e87925..1dd73741cf 100644 --- a/src/frontend/src/lib/derived/mission-control/mission-control-ufos.derived.ts +++ b/src/frontend/src/lib/derived/mission-control/mission-control-ufos.derived.ts @@ -1,7 +1,12 @@ import { ufosUncertifiedStore } from '$lib/stores/mission-control/ufos.store'; +import { sortUfos } from '$lib/utils/ufo.utils'; import { derived } from 'svelte/store'; export const mctrlUfos = derived( [ufosUncertifiedStore], ([$ufosUncertifiedStore]) => $ufosUncertifiedStore?.data ); + +export const mctrlSortedUfos = derived([mctrlUfos], ([$mctrlUfosStore]) => + ($mctrlUfosStore ?? []).sort(sortUfos) +); diff --git a/src/frontend/src/lib/derived/out-of-sync.derived.ts b/src/frontend/src/lib/derived/out-of-sync.derived.ts index 3df55df615..160863c089 100644 --- a/src/frontend/src/lib/derived/out-of-sync.derived.ts +++ b/src/frontend/src/lib/derived/out-of-sync.derived.ts @@ -1,6 +1,11 @@ -import { consoleOrbiters, consoleSatellites } from '$lib/derived/console/segments.derived'; +import { + consoleOrbiters, + consoleSatellites, + consoleUfos +} from '$lib/derived/console/segments.derived'; import { mctrlOrbiters } from '$lib/derived/mission-control/mission-control-orbiters.derived'; import { mctrlSatellites } from '$lib/derived/mission-control/mission-control-satellites.derived'; +import { mctrlUfos } from '$lib/derived/mission-control/mission-control-ufos.derived'; import { derived } from 'svelte/store'; export const outOfSyncSatellites = derived( @@ -61,3 +66,31 @@ export const outOfSyncOrbiters = derived( return !inSync; } ); + +export const outOfSyncUfos = derived( + [consoleUfos, mctrlUfos], + ([$consoleUfos, $mctrlUfos]): boolean | undefined => { + // Not yet fully loaded + if ($consoleUfos === undefined || $mctrlUfos === undefined) { + return undefined; + } + + // No mission control + if ($mctrlUfos === null) { + return false; + } + + if ($consoleUfos?.length === 0 && $mctrlUfos.length === 0) { + return false; + } + + const inSync = + $consoleUfos?.length === $mctrlUfos.length && + ($consoleUfos ?? []).every( + ({ ufo_id: segment_id }) => + $mctrlUfos.find(({ ufo_id }) => ufo_id.toText() === segment_id.toText()) !== undefined + ); + + return !inSync; + } +); diff --git a/src/frontend/src/lib/i18n/en.json b/src/frontend/src/lib/i18n/en.json index 64c1925d7f..2fbfc6553a 100644 --- a/src/frontend/src/lib/i18n/en.json +++ b/src/frontend/src/lib/i18n/en.json @@ -1100,7 +1100,7 @@ "upgrade_available": "New upgrades available. Visit the Upgrade Dock to check them out.", "short_freezing_threshold": "Short freezing threshold detected. We recommend a longer grace period to give you more time if cycles run out.", "learn_more": "Learn more", - "out_of_sync": "Your satellites and orbiters are out of sync. Reconcile them now." + "out_of_sync": "Your modules are out of sync. Reconcile them now." }, "not_found": { "title": "Oops! You got lost in space.", @@ -1124,7 +1124,9 @@ "syncing_satellites_to_console": "Syncing Satellites to Console...", "syncing_satellites_to_mctrl": "Syncing Satellites to Mission Control... ({0})", "syncing_orbiters_to_console": "Syncing Orbiters to Console...", - "syncing_orbiters_to_mctrl": "Syncing Orbiters to Mission Control... ({0})" + "syncing_orbiters_to_mctrl": "Syncing Orbiters to Mission Control... ({0})", + "syncing_ufos_to_console": "Syncing UFOs to Console...", + "syncing_ufos_to_mctrl": "Syncing UFOs to Mission Control... ({0})" }, "automation": { "title": "Deployments", diff --git a/src/frontend/src/lib/i18n/zh-cn.json b/src/frontend/src/lib/i18n/zh-cn.json index 0242568979..d3a2e17c3e 100644 --- a/src/frontend/src/lib/i18n/zh-cn.json +++ b/src/frontend/src/lib/i18n/zh-cn.json @@ -1102,7 +1102,7 @@ "upgrade_available": "有新版本可用,请前往升级中心查看。", "short_freezing_threshold": "检测到冻结阈值过短,建议设置更长宽限期以便在cycles耗尽时为您争取更多时间。", "learn_more": "了解更多", - "out_of_sync": "您的卫星和轨道器不同步。请立即协调它们。" + "out_of_sync": "您的模块已不同步。立即进行协调。" }, "not_found": { "title": "抱歉!您在太空中迷路了。", @@ -1126,7 +1126,9 @@ "syncing_satellites_to_console": "正在将卫星同步到控制台...", "syncing_satellites_to_mctrl": "正在将卫星同步到任务控制中心... ({0})", "syncing_orbiters_to_console": "正在将轨道器同步到控制台...", - "syncing_orbiters_to_mctrl": "正在将轨道器同步到任务控制中心... ({0})" + "syncing_orbiters_to_mctrl": "正在将轨道器同步到任务控制中心... ({0})", + "syncing_ufos_to_console": "正在将 UFO 同步到控制台...", + "syncing_ufos_to_mctrl": "正在将 UFO 同步到任务控制... ({0})" }, "automation": { "title": "部署", diff --git a/src/frontend/src/lib/services/access-keys/key.admin.services.ts b/src/frontend/src/lib/services/access-keys/key.admin.services.ts index 4111fb48e9..7721992dd3 100644 --- a/src/frontend/src/lib/services/access-keys/key.admin.services.ts +++ b/src/frontend/src/lib/services/access-keys/key.admin.services.ts @@ -26,7 +26,7 @@ export type AdminAccessKeyResult = | { result: 'reject'; reason: string } | { result: 'error'; err: unknown }; -type ApplyAccessKeyFn = (params: { controllerId: Principal }) => Promise; +export type ApplyAccessKeyFn = (params: { controllerId: Principal }) => Promise; type UpdateControllersFn = (params: { controllerId: Principal; @@ -36,7 +36,6 @@ type UpdateControllersFn = (params: { export const setAdminAccessKey = async ({ setAccessKeysFn, attachFn, - canisterId, metadata, ...rest }: { @@ -59,6 +58,41 @@ export const setAdminAccessKey = async ({ await attachFn?.(); }; + return withSetAdminController({ + applyFn: applyAccessKeyFn, + ...rest + }); +}; + +export const setAdminController = async ({ + attachFn, + metadata, + ...rest +}: { + attachFn?: () => Promise; + canisterId: Principal; + identity: Identity; +} & AddAdminAccessKeyParams): Promise => { + const applyAccessKeyFn: ApplyAccessKeyFn = async () => { + await attachFn?.(); + }; + + return withSetAdminController({ + applyFn: applyAccessKeyFn, + ...rest + }); +}; + +const withSetAdminController = async ({ + applyFn, + canisterId, + metadata, + ...rest +}: { + applyFn: ApplyAccessKeyFn; + canisterId: Principal; + identity: Identity; +} & AddAdminAccessKeyParams): Promise => { const updateControllersFn: UpdateControllersFn = ({ currentControllers, controllerId }) => { if (currentControllers.length >= MAX_NUMBER_OF_CONTROLLERS - 1) { return { result: 'reject', reason: get(i18n).errors.canister_controllers }; @@ -75,7 +109,7 @@ export const setAdminAccessKey = async ({ }; return await executeAdminAccessKey({ - applyAccessKeyFn, + applyAccessKeyFn: applyFn, updateControllersFn, canisterId, ...rest diff --git a/src/frontend/src/lib/services/attach-detach/out-of-sync.services.ts b/src/frontend/src/lib/services/attach-detach/out-of-sync.services.ts index c179895aa4..1c5de8493f 100644 --- a/src/frontend/src/lib/services/attach-detach/out-of-sync.services.ts +++ b/src/frontend/src/lib/services/attach-detach/out-of-sync.services.ts @@ -1,12 +1,22 @@ import { setManySegments } from '$lib/api/console.api'; -import { consoleOrbiters, consoleSatellites } from '$lib/derived/console/segments.derived'; +import { + consoleOrbiters, + consoleSatellites, + consoleUfos +} from '$lib/derived/console/segments.derived'; import { mctrlOrbiters } from '$lib/derived/mission-control/mission-control-orbiters.derived'; import { mctrlSatellites } from '$lib/derived/mission-control/mission-control-satellites.derived'; -import { outOfSyncOrbiters, outOfSyncSatellites } from '$lib/derived/out-of-sync.derived'; +import { mctrlUfos } from '$lib/derived/mission-control/mission-control-ufos.derived'; +import { + outOfSyncOrbiters, + outOfSyncSatellites, + outOfSyncUfos +} from '$lib/derived/out-of-sync.derived'; import { execute } from '$lib/services/_progress.services'; import { setMissionControlAsControllerAndAttachOrbiter, - setMissionControlAsControllerAndAttachSatellite + setMissionControlAsControllerAndAttachSatellite, + setMissionControlAsControllerAndAttachUfo } from '$lib/services/factory/_factory.attach.services'; import { loadSegments } from '$lib/services/segments.services'; import { i18n } from '$lib/stores/app/i18n.store'; @@ -16,6 +26,7 @@ import type { MissionControlId } from '$lib/types/mission-control'; import type { Orbiter } from '$lib/types/orbiter'; import { type OutOfSyncProgress, OutOfSyncProgressStep } from '$lib/types/progress-out-of-sync'; import type { Satellite } from '$lib/types/satellite'; +import type { Ufo } from '$lib/types/ufo'; import { isNullish, toNullable } from '@dfinity/utils'; import type { Nullish } from '@dfinity/zod-schemas'; import type { Identity } from '@icp-sdk/core/agent'; @@ -52,8 +63,9 @@ export const reconcileSegments = async ({ const divergentSatellites = get(outOfSyncSatellites); const divergentOrbiters = get(outOfSyncOrbiters); + const divergentUfos = get(outOfSyncUfos); - if (divergentSatellites !== true && divergentOrbiters !== true) { + if (divergentSatellites !== true && divergentOrbiters !== true && divergentUfos !== true) { toasts.warn(get(i18n).errors.reconcile_no_divergence); return { result: 'error' }; } @@ -103,7 +115,7 @@ const reconcileAllSegments = async (params: ReconcileParams) => { await reconcileOrbiters(params); - // TODO: reconcileUfos + await reconcileUfos(params); }; const reconcileSatellites = async ({ @@ -216,6 +228,54 @@ const reconcileOrbiters = async ({ }); }; +const reconcileUfos = async ({ identity, missionControlId, onTextProgress }: ReconcileParams) => { + const consoleUfs = get(consoleUfos); + const mctrlUfs = get(mctrlUfos); + + const attachConsoleUfos = (mctrlUfs ?? []).filter( + ({ ufo_id: segment_id }) => + (consoleUfs ?? []).find(({ ufo_id }) => ufo_id.toText() === segment_id.toText()) === undefined + ); + + const attachMctrlUfos = (consoleUfs ?? []).filter( + ({ ufo_id: segment_id }) => + (mctrlUfs ?? []).find(({ ufo_id }) => ufo_id.toText() === segment_id.toText()) === undefined + ); + + type SegmentWithoutId = Omit; + + const attachFn: AttachFn = async ({ segment: { segmentId, ...rest } }) => { + await setMissionControlAsControllerAndAttachUfo({ + missionControlId, + identity, + ufo: { + ufo_id: segmentId, + ...rest + } + }); + }; + + await attachWithMissionControl({ + onTextProgress, + segmentType: 'ufo', + segments: attachMctrlUfos.map(({ ufo_id: segmentId, ...rest }) => ({ + ...rest, + segmentId + })), + attachFn + }); + + await attachWithConsole({ + identity, + onTextProgress, + segmentType: 'ufo', + segments: attachConsoleUfos.map(({ ufo_id, metadata }) => ({ + segmentId: ufo_id, + metadata + })) + }); +}; + type SegmentWithoutId = Omit | Omit; type AttachSegment = { @@ -238,7 +298,7 @@ const attachWithMissionControl = async ({ attachFn }: Pick & { segments: AttachSegment[]; - segmentType: 'satellite' | 'orbiter'; + segmentType: 'satellite' | 'orbiter' | 'ufo'; attachFn: AttachFn; }) => { // We do the check for the lengths here. Not really graceful but, @@ -259,9 +319,11 @@ const attachWithMissionControl = async ({ }; const text = - segmentType === 'orbiter' - ? get(i18n).out_of_sync.syncing_orbiters_to_mctrl - : get(i18n).out_of_sync.syncing_satellites_to_mctrl; + segmentType === 'ufo' + ? get(i18n).out_of_sync.syncing_ufos_to_mctrl + : segmentType === 'orbiter' + ? get(i18n).out_of_sync.syncing_orbiters_to_mctrl + : get(i18n).out_of_sync.syncing_satellites_to_mctrl; onTextProgress(text.replace('{0}', `${progress.index} / ${progress.total}`)); }; @@ -280,7 +342,7 @@ const attachWithConsole = async ({ segmentType }: { segments: Pick, 'segmentId' | 'metadata'>[]; - segmentType: 'satellite' | 'orbiter'; + segmentType: 'satellite' | 'orbiter' | 'ufo'; } & Pick) => { // We do the check for the lengths here. Not really graceful but, // avoid to duplicate the assertions for both Satellites and Orbiters @@ -289,9 +351,11 @@ const attachWithConsole = async ({ } const text = - segmentType === 'orbiter' - ? get(i18n).out_of_sync.syncing_orbiters_to_console - : get(i18n).out_of_sync.syncing_satellites_to_console; + segmentType === 'ufo' + ? get(i18n).out_of_sync.syncing_ufos_to_console + : segmentType === 'orbiter' + ? get(i18n).out_of_sync.syncing_orbiters_to_console + : get(i18n).out_of_sync.syncing_satellites_to_console; onTextProgress(text); @@ -299,7 +363,12 @@ const attachWithConsole = async ({ identity, args: segments.map(({ segmentId: segment_id, metadata }) => ({ segment_id, - segment_kind: segmentType === 'orbiter' ? { Orbiter: null } : { Satellite: null }, + segment_kind: + segmentType === 'ufo' + ? { Ufo: null } + : segmentType === 'orbiter' + ? { Orbiter: null } + : { Satellite: null }, metadata: toNullable(metadata) })) }); diff --git a/src/frontend/src/lib/services/factory/_factory.attach.services.ts b/src/frontend/src/lib/services/factory/_factory.attach.services.ts index 31da9c88db..08ae34cc95 100644 --- a/src/frontend/src/lib/services/factory/_factory.attach.services.ts +++ b/src/frontend/src/lib/services/factory/_factory.attach.services.ts @@ -4,14 +4,15 @@ import { setControllers as setSatelliteControllers } from '$lib/api/satellites.a import { consoleOrbiters, consoleSatellites } from '$lib/derived/console/segments.derived'; import { type SetAccessKeysFn, - setAdminAccessKey + setAdminAccessKey, + setAdminController } from '$lib/services/access-keys/key.admin.services'; import { i18n } from '$lib/stores/app/i18n.store'; import type { AddAdminAccessKeyParams } from '$lib/types/access-keys'; import type { MissionControlId } from '$lib/types/mission-control'; import type { Orbiter } from '$lib/types/orbiter'; import type { Satellite } from '$lib/types/satellite'; -import type { UfoId } from '$lib/types/ufo'; +import type { Ufo, UfoId } from '$lib/types/ufo'; import { metadataUiName } from '$lib/utils/metadata-ui.utils'; import { orbiterName } from '$lib/utils/orbiter.utils'; import type { Identity } from '@icp-sdk/core/agent'; @@ -312,6 +313,32 @@ export const setMissionControlAsControllerAndAttachOrbiter = async ({ return { result: 'ok' }; }; +export const setMissionControlAsControllerAndAttachUfo = async ({ + ufo, + missionControlId, + identity +}: { + ufo: Ufo; + missionControlId: MissionControlId; + identity: Identity; +}): Promise => { + const { ufo_id: ufoId } = ufo; + + const attachFn = async () => { + await setUfo({ missionControlId, ufoId, identity, ufoName: metadataUiName(ufo) }); + }; + + await setAdminController({ + attachFn, + ...CONTROLLER_PARAMS, + canisterId: ufoId, + accessKeyId: missionControlId, + identity + }); + + return { result: 'ok' }; +}; + type AttachSegmentResult = | { result: 'ok' | 'skip' } | { result: 'reject'; reason: string } diff --git a/src/frontend/src/lib/services/mission-control/mission-control.ufos.services.ts b/src/frontend/src/lib/services/mission-control/mission-control.ufos.services.ts index 0bdb59f8a6..419930a40a 100644 --- a/src/frontend/src/lib/services/mission-control/mission-control.ufos.services.ts +++ b/src/frontend/src/lib/services/mission-control/mission-control.ufos.services.ts @@ -35,11 +35,11 @@ export const loadUfos = async ({ const currentVersion = instantMissionControlVersion(); if (notEmptyString(currentVersion) && compare(currentVersion, MISSION_CONTROL_v0_1_3) < 0) { - ufosUncertifiedStore.set(null); + ufosUncertifiedStore.set([]); return { result: 'success' }; } - const load = async (identity: Identity): Promise => { + const load = async (identity: Identity): Promise => { // TODO: Version might not been loading yet. We could refactor to avoid imperatively // loading an additional time the version if (isEmptyString(currentVersion)) { @@ -49,7 +49,7 @@ export const loadUfos = async ({ }); if (compare(instantVersion?.metadata?.current ?? '0.0.0', MISSION_CONTROL_v0_1_3) < 0) { - return null; + return []; } } diff --git a/src/frontend/src/lib/types/i18n.d.ts b/src/frontend/src/lib/types/i18n.d.ts index 5c1e2b8c43..04288fdd3c 100644 --- a/src/frontend/src/lib/types/i18n.d.ts +++ b/src/frontend/src/lib/types/i18n.d.ts @@ -1163,6 +1163,8 @@ interface I18nOut_of_sync { syncing_satellites_to_mctrl: string; syncing_orbiters_to_console: string; syncing_orbiters_to_mctrl: string; + syncing_ufos_to_console: string; + syncing_ufos_to_mctrl: string; } interface I18nAutomation { From 30a97d35b738852cff0e3eba50b18fb1a2e2200e Mon Sep 17 00:00:00 2001 From: David Dal Busco Date: Thu, 23 Apr 2026 18:18:10 +0200 Subject: [PATCH 2/2] chore: lint --- .../app/notifications/NotificationsAlerts.svelte | 4 ++-- .../app/notifications/NotificationsAlertsLoader.svelte | 2 +- .../components/modules/out-of-sync/OutOfSyncForm.svelte | 2 +- .../modules/out-of-sync/OutOfSyncSegments.svelte | 2 +- .../src/lib/services/access-keys/key.admin.services.ts | 8 +++----- 5 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/frontend/src/lib/components/app/notifications/NotificationsAlerts.svelte b/src/frontend/src/lib/components/app/notifications/NotificationsAlerts.svelte index b5d052e478..1bae292e39 100644 --- a/src/frontend/src/lib/components/app/notifications/NotificationsAlerts.svelte +++ b/src/frontend/src/lib/components/app/notifications/NotificationsAlerts.svelte @@ -5,12 +5,12 @@ import IconAnalytics from '$lib/components/icons/IconAnalytics.svelte'; import IconMissionControl from '$lib/components/icons/IconMissionControl.svelte'; import IconSatellite from '$lib/components/icons/IconSatellite.svelte'; + import IconUfo from '$lib/components/icons/IconUfo.svelte'; import { satellite } from '$lib/derived/satellite.derived'; + import { ufo } from '$lib/derived/ufo.derived'; import { i18n } from '$lib/stores/app/i18n.store'; import type { CanisterData, CanisterWarning } from '$lib/types/canister'; import { overviewLink, ufoLink } from '$lib/utils/nav.utils'; - import IconUfo from '$lib/components/icons/IconUfo.svelte'; - import { ufo } from '$lib/derived/ufo.derived'; interface Props { missionControlCanisterData: CanisterData | undefined; diff --git a/src/frontend/src/lib/components/app/notifications/NotificationsAlertsLoader.svelte b/src/frontend/src/lib/components/app/notifications/NotificationsAlertsLoader.svelte index d8d5953071..072d9a2186 100644 --- a/src/frontend/src/lib/components/app/notifications/NotificationsAlertsLoader.svelte +++ b/src/frontend/src/lib/components/app/notifications/NotificationsAlertsLoader.svelte @@ -8,9 +8,9 @@ outOfSyncSatellites } from '$lib/derived/out-of-sync.derived'; import { satellite } from '$lib/derived/satellite.derived'; + import { ufo } from '$lib/derived/ufo.derived'; import { versionsLoaded, versionsUpgradeWarning } from '$lib/derived/version.derived'; import type { CanisterData, CanisterWarning } from '$lib/types/canister'; - import { ufo } from '$lib/derived/ufo.derived'; interface Props { missionControlCanisterData: CanisterData | undefined; diff --git a/src/frontend/src/lib/components/modules/out-of-sync/OutOfSyncForm.svelte b/src/frontend/src/lib/components/modules/out-of-sync/OutOfSyncForm.svelte index 1fb8cf78b1..d66464e6b3 100644 --- a/src/frontend/src/lib/components/modules/out-of-sync/OutOfSyncForm.svelte +++ b/src/frontend/src/lib/components/modules/out-of-sync/OutOfSyncForm.svelte @@ -8,8 +8,8 @@ } from '$lib/derived/console/segments.derived'; import { mctrlOrbiter } from '$lib/derived/mission-control/mission-control-orbiters.derived'; import { mctrlSortedSatellites } from '$lib/derived/mission-control/mission-control-satellites.derived'; - import { i18n } from '$lib/stores/app/i18n.store'; import { mctrlSortedUfos } from '$lib/derived/mission-control/mission-control-ufos.derived'; + import { i18n } from '$lib/stores/app/i18n.store'; interface Props { onsubmit: ($event: SubmitEvent) => Promise; diff --git a/src/frontend/src/lib/components/modules/out-of-sync/OutOfSyncSegments.svelte b/src/frontend/src/lib/components/modules/out-of-sync/OutOfSyncSegments.svelte index 9c24ab6cd6..17f672bb18 100644 --- a/src/frontend/src/lib/components/modules/out-of-sync/OutOfSyncSegments.svelte +++ b/src/frontend/src/lib/components/modules/out-of-sync/OutOfSyncSegments.svelte @@ -6,9 +6,9 @@ import { i18n } from '$lib/stores/app/i18n.store'; import type { Orbiter } from '$lib/types/orbiter'; import type { Satellite } from '$lib/types/satellite'; + import type { Ufo } from '$lib/types/ufo'; import { metadataUiName } from '$lib/utils/metadata-ui.utils'; import { orbiterName } from '$lib/utils/orbiter.utils'; - import type { Ufo } from '$lib/types/ufo'; interface Props { label: Snippet; diff --git a/src/frontend/src/lib/services/access-keys/key.admin.services.ts b/src/frontend/src/lib/services/access-keys/key.admin.services.ts index 7721992dd3..170e574682 100644 --- a/src/frontend/src/lib/services/access-keys/key.admin.services.ts +++ b/src/frontend/src/lib/services/access-keys/key.admin.services.ts @@ -58,7 +58,7 @@ export const setAdminAccessKey = async ({ await attachFn?.(); }; - return withSetAdminController({ + return await withSetAdminController({ applyFn: applyAccessKeyFn, ...rest }); @@ -66,7 +66,6 @@ export const setAdminAccessKey = async ({ export const setAdminController = async ({ attachFn, - metadata, ...rest }: { attachFn?: () => Promise; @@ -77,7 +76,7 @@ export const setAdminController = async ({ await attachFn?.(); }; - return withSetAdminController({ + return await withSetAdminController({ applyFn: applyAccessKeyFn, ...rest }); @@ -86,13 +85,12 @@ export const setAdminController = async ({ const withSetAdminController = async ({ applyFn, canisterId, - metadata, ...rest }: { applyFn: ApplyAccessKeyFn; canisterId: Principal; identity: Identity; -} & AddAdminAccessKeyParams): Promise => { +} & Omit): Promise => { const updateControllersFn: UpdateControllersFn = ({ currentControllers, controllerId }) => { if (currentControllers.length >= MAX_NUMBER_OF_CONTROLLERS - 1) { return { result: 'reject', reason: get(i18n).errors.canister_controllers };