diff --git a/src/frontend/src/lib/components/modals/Modals.svelte b/src/frontend/src/lib/components/modals/Modals.svelte
index 05cadab43..a95fbdf04 100644
--- a/src/frontend/src/lib/components/modals/Modals.svelte
+++ b/src/frontend/src/lib/components/modals/Modals.svelte
@@ -2,6 +2,7 @@
import { nonNullish } from '@dfinity/utils';
import AuthConfigModal from '$lib/components/modals/auth/AuthConfigModal.svelte';
import UserDetailsModal from '$lib/components/modals/auth/UserDetailsModal.svelte';
+ import AutomationKeysConfigModal from '$lib/components/modals/automation/AutomationKeysConfigModal.svelte';
import CreateAutomationModal from '$lib/components/modals/automation/CreateAutomationModal.svelte';
import ApplyChangeModal from '$lib/components/modals/changes/ApplyChangeModal.svelte';
import RejectChangeModal from '$lib/components/modals/changes/RejectChangeModal.svelte';
@@ -168,3 +169,7 @@
{#if modal?.type === 'create_automation' && nonNullish(modal.detail)}
{/if}
+
+{#if modal?.type === 'edit_automation_keys_config' && nonNullish(modal.detail)}
+
+{/if}
diff --git a/src/frontend/src/lib/components/modals/automation/AutomationKeysConfigModal.svelte b/src/frontend/src/lib/components/modals/automation/AutomationKeysConfigModal.svelte
new file mode 100644
index 000000000..1ddbd8c06
--- /dev/null
+++ b/src/frontend/src/lib/components/modals/automation/AutomationKeysConfigModal.svelte
@@ -0,0 +1,82 @@
+
+
+
+ {#if step === 'ready'}
+
+
{$i18n.core.configuration_applied}
+
+
+ {:else if step === 'in_progress'}
+
+ {$i18n.core.updating_configuration}
+
+ {:else}
+
+ {/if}
+
+
+
diff --git a/src/frontend/src/lib/components/satellites/auth/AuthSettingsLoader.svelte b/src/frontend/src/lib/components/satellites/auth/AuthSettingsLoader.svelte
index d3cf1cd8b..12a210d18 100644
--- a/src/frontend/src/lib/components/satellites/auth/AuthSettingsLoader.svelte
+++ b/src/frontend/src/lib/components/satellites/auth/AuthSettingsLoader.svelte
@@ -4,7 +4,6 @@
import SpinnerParagraph from '$lib/components/ui/SpinnerParagraph.svelte';
import Warning from '$lib/components/ui/Warning.svelte';
import { authIdentity } from '$lib/derived/auth.derived';
- import { satelliteAuthConfig } from '$lib/derived/satellite/satellite-configs.derived';
import { getRuleUser } from '$lib/services/satellite/collection/collection.services';
import { loadSatelliteConfig } from '$lib/services/satellite/satellite-config.services';
import { i18n } from '$lib/stores/app/i18n.store';
@@ -37,8 +36,6 @@
});
};
- $inspect($satelliteAuthConfig);
-
const load = async () => {
await Promise.all([loadConfig(), loadRule()]);
};
diff --git a/src/frontend/src/lib/components/satellites/automation/AutomationSettings.svelte b/src/frontend/src/lib/components/satellites/automation/AutomationSettings.svelte
new file mode 100644
index 000000000..d7160439d
--- /dev/null
+++ b/src/frontend/src/lib/components/satellites/automation/AutomationSettings.svelte
@@ -0,0 +1,18 @@
+
+
+
+ {#snippet content(config: SatelliteDid.OpenIdAutomationProviderConfig)}
+
+ {/snippet}
+
diff --git a/src/frontend/src/lib/components/satellites/automation/guards/GitHubConfigGuard.svelte b/src/frontend/src/lib/components/satellites/automation/guards/GitHubConfigGuard.svelte
new file mode 100644
index 000000000..a56c5f828
--- /dev/null
+++ b/src/frontend/src/lib/components/satellites/automation/guards/GitHubConfigGuard.svelte
@@ -0,0 +1,32 @@
+
+
+{#if $githubConfig === undefined}
+ {$i18n.core.loading_config}
+{:else if $githubConfig === null}
+
+{:else}
+
+ {@render content($githubConfig)}
+
+{/if}
diff --git a/src/frontend/src/lib/components/satellites/automation/settings/AutomationKeysConfigForm.svelte b/src/frontend/src/lib/components/satellites/automation/settings/AutomationKeysConfigForm.svelte
new file mode 100644
index 000000000..b12079aa8
--- /dev/null
+++ b/src/frontend/src/lib/components/satellites/automation/settings/AutomationKeysConfigForm.svelte
@@ -0,0 +1,124 @@
+
+
+{$i18n.automation.keys}
+
+
+ {$i18n.automation.edit_automation_keys}
+
+
+
diff --git a/src/frontend/src/lib/components/satellites/automation/settings/AutomationKeysSettings.svelte b/src/frontend/src/lib/components/satellites/automation/settings/AutomationKeysSettings.svelte
new file mode 100644
index 000000000..18bb1b9f6
--- /dev/null
+++ b/src/frontend/src/lib/components/satellites/automation/settings/AutomationKeysSettings.svelte
@@ -0,0 +1,114 @@
+
+
+
+
{$i18n.automation.keys}
+
+
+
+
+
+ {#snippet label()}
+ {$i18n.controllers.scope}
+ {/snippet}
+
+ {scope === 'submit' ? $i18n.controllers.submit : $i18n.controllers.write}
+
+
+
+
+
+
+
+ {#snippet label()}
+ {$i18n.automation.access_duration}
+ {/snippet}
+
+
+ {#if maxTimeToLive === BigInt(TWO_MINUTES_NS)}
+ {$i18n.core.two_minutes}
+ {:else if maxTimeToLive === BigInt(FIVE_MINUTES_NS)}
+ {$i18n.core.five_minutes}
+ {:else if maxTimeToLive === BigInt(TEN_MINUTES_NS)}
+ {$i18n.core.ten_minutes}
+ {:else if maxTimeToLive === BigInt(FIFTEEN_MINUTES_NS)}
+ {$i18n.core.fifteen_minutes}
+ {:else if maxTimeToLive === BigInt(THIRTY_MINUTES_NS)}
+ {$i18n.core.thirty_minutes}
+ {:else if maxTimeToLive === BigInt(FORTY_FIVE_MINUTES_NS)}
+ {$i18n.core.forty_five_minutes}
+ {:else if maxTimeToLive === BigInt(AN_HOUR_NS)}
+ {$i18n.core.an_hour}
+ {:else}
+ {secondsToDuration(maxTimeToLive ?? 0n)}
+ {/if}
+
+
+
+
+
+
+
+
diff --git a/src/frontend/src/lib/constants/automation.constants.ts b/src/frontend/src/lib/constants/automation.constants.ts
new file mode 100644
index 000000000..c596ac02d
--- /dev/null
+++ b/src/frontend/src/lib/constants/automation.constants.ts
@@ -0,0 +1,3 @@
+import { TEN_MINUTES_NS } from '$lib/constants/duration.constants';
+
+export const AUTOMATION_DEFAULT_MAX_SESSION_TIME_TO_LIVE = TEN_MINUTES_NS;
diff --git a/src/frontend/src/lib/constants/duration.constants.ts b/src/frontend/src/lib/constants/duration.constants.ts
index 2b2103243..4bc205458 100644
--- a/src/frontend/src/lib/constants/duration.constants.ts
+++ b/src/frontend/src/lib/constants/duration.constants.ts
@@ -2,8 +2,13 @@ const MINUTE_NANOSECONDS = 60n * 1_000_000_000n;
const HOUR_NANOSECONDS = 60n * MINUTE_NANOSECONDS;
const DAY_NANOSECONDS = 24n * HOUR_NANOSECONDS;
-// 1 day in nanoseconds
-export const AN_HOUR_NS = HOUR_NANOSECONDS;
+export const TWO_MINUTES_NS = 2n * MINUTE_NANOSECONDS;
+export const FIVE_MINUTES_NS = 5n * MINUTE_NANOSECONDS;
+export const TEN_MINUTES_NS = 10n * MINUTE_NANOSECONDS;
+export const FIFTEEN_MINUTES_NS = 15n * MINUTE_NANOSECONDS;
+export const THIRTY_MINUTES_NS = 30n * MINUTE_NANOSECONDS;
+export const FORTY_FIVE_MINUTES_NS = 45n * MINUTE_NANOSECONDS;
+export const AN_HOUR_NS = HOUR_NANOSECONDS; // One hour max for automation
export const TWO_HOURS_NS = 2n * AN_HOUR_NS;
export const FOUR_HOURS_NS = 4n * AN_HOUR_NS;
export const EIGHT_HOURS_NS = 8n * AN_HOUR_NS;
@@ -11,4 +16,4 @@ export const HALF_DAY_NS = 12n * AN_HOUR_NS;
export const ONE_DAY_NS = DAY_NANOSECONDS;
export const A_WEEK_NS = 7n * ONE_DAY_NS;
export const TWO_WEEKS_NS = 2n * A_WEEK_NS;
-export const A_MONTH_NS = 30n * ONE_DAY_NS; // 30 days. Max.
+export const A_MONTH_NS = 30n * ONE_DAY_NS; // 30 days. max for authentication
diff --git a/src/frontend/src/lib/derived/satellite/github.derived.ts b/src/frontend/src/lib/derived/satellite/github.derived.ts
new file mode 100644
index 000000000..e6529c76f
--- /dev/null
+++ b/src/frontend/src/lib/derived/satellite/github.derived.ts
@@ -0,0 +1,14 @@
+import { satelliteAutomationConfig } from '$lib/derived/satellite/satellite-configs.derived';
+import { fromNullable, isNullish } from '@dfinity/utils';
+import { derived } from 'svelte/store';
+
+export const githubConfig = derived([satelliteAutomationConfig], ([$satelliteAutomationConfig]) => {
+ // Undefined not loaded or null as set as such
+ if (isNullish($satelliteAutomationConfig)) {
+ return $satelliteAutomationConfig;
+ }
+
+ const openid = fromNullable($satelliteAutomationConfig.openid);
+ const github = openid?.providers.find(([key]) => 'GitHub' in key);
+ return github?.[1] ?? null;
+});
diff --git a/src/frontend/src/lib/i18n/en.json b/src/frontend/src/lib/i18n/en.json
index ebc0e0fb4..b3f305d59 100644
--- a/src/frontend/src/lib/i18n/en.json
+++ b/src/frontend/src/lib/i18n/en.json
@@ -98,6 +98,12 @@
"staging": "Staging",
"test": "Test",
"unspecified": "Unspecified",
+ "two_minutes": "Two minutes",
+ "five_minutes": "Five minutes",
+ "ten_minutes": "Ten minutes",
+ "fifteen_minutes": "Fifteen minutes",
+ "thirty_minutes": "Thirty minutes",
+ "forty_five_minutes": "Forty-five minutes",
"an_hour": "An hour",
"two_hours": "Two hours",
"four_hours": "Four hours",
@@ -753,7 +759,8 @@
"build_repo_key_invalid_github_url": "Only GitHub repositories are supported. Please enter the GitHub URL of your repository.",
"build_repo_key_owner_not_found": "Repository owner not found in URL. Please enter a complete GitHub repository URL.",
"build_repo_key_repo_not_found": "Repository name not found in URL. Please enter a complete GitHub repository URL.",
- "create_automation_config": "An error occurred while saving your automation configuration."
+ "save_automation_config": "An error occurred while saving your automation configuration.",
+ "automation_config_undefined": "The automation configuration is undefined, which is unexpected. Try reloading the page."
},
"document": {
"owner": "Owner",
@@ -1119,6 +1126,9 @@
"view_workflow": "View workflow run on GitHub",
"view_commit": "View commit on GitHub",
"view_branch": "View branch run on GitHub",
- "view_contributor": "View contributor on GitHub"
+ "view_contributor": "View contributor on GitHub",
+ "keys": "Automation Keys",
+ "access_duration": "Access Duration",
+ "edit_automation_keys": "Configure how your workflows access your Satellite and how long their keys remain valid."
}
}
diff --git a/src/frontend/src/lib/i18n/zh-cn.json b/src/frontend/src/lib/i18n/zh-cn.json
index 698f3271e..03591e063 100644
--- a/src/frontend/src/lib/i18n/zh-cn.json
+++ b/src/frontend/src/lib/i18n/zh-cn.json
@@ -99,6 +99,12 @@
"staging": "预发环境",
"test": "测试环境",
"unspecified": "未定义状态",
+ "two_minutes": "两分钟",
+ "five_minutes": "五分钟",
+ "ten_minutes": "十分钟",
+ "fifteen_minutes": "十五分钟",
+ "thirty_minutes": "三十分钟",
+ "forty_five_minutes": "四十五分钟",
"an_hour": "一小时",
"two_hours": "两小时",
"four_hours": "四小时",
@@ -755,7 +761,8 @@
"build_repo_key_invalid_github_url": "仅支持 GitHub 仓库。请输入您的 GitHub 仓库 URL。",
"build_repo_key_owner_not_found": "在 URL 中未找到仓库所有者。请输入完整的 GitHub 仓库 URL。",
"build_repo_key_repo_not_found": "在 URL 中未找到仓库名称。请输入完整的 GitHub 仓库 URL。",
- "create_automation_config": "保存自动化配置时发生错误。"
+ "save_automation_config": "保存自动化配置时发生错误。",
+ "automation_config_undefined": "自动化配置未定义,这是意外情况。请尝试重新加载页面。"
},
"document": {
"owner": "所有者",
@@ -1121,6 +1128,9 @@
"view_workflow": "在 GitHub 上查看工作流运行",
"view_commit": "在 GitHub 上查看提交",
"view_branch": "在 GitHub 上查看分支",
- "view_contributor": "在 GitHub 上查看贡献者"
+ "view_contributor": "在 GitHub 上查看贡献者",
+ "keys": "自动化密钥",
+ "access_duration": "访问时长",
+ "edit_automation_keys": "配置工作流访问 Satellite 的方式及其密钥的有效期。"
}
}
diff --git a/src/frontend/src/lib/services/satellite/automation/automation.config.edit.services.ts b/src/frontend/src/lib/services/satellite/automation/automation.config.edit.services.ts
new file mode 100644
index 000000000..bba0acf13
--- /dev/null
+++ b/src/frontend/src/lib/services/satellite/automation/automation.config.edit.services.ts
@@ -0,0 +1,116 @@
+import type { SatelliteDid } from '$declarations';
+import { setAutomationConfig } from '$lib/api/satellites.api';
+import { AUTOMATION_DEFAULT_MAX_SESSION_TIME_TO_LIVE } from '$lib/constants/automation.constants';
+import { loadSatelliteConfig } from '$lib/services/satellite/satellite-config.services';
+import { i18n } from '$lib/stores/app/i18n.store';
+import { toasts } from '$lib/stores/app/toasts.store';
+import type { AddAccessKeyScope } from '$lib/types/access-keys';
+import type { OptionIdentity } from '$lib/types/itentity';
+import type { Satellite } from '$lib/types/satellite';
+import { isNullish, toNullable } from '@dfinity/utils';
+import type { Identity } from '@icp-sdk/core/agent';
+import { get } from 'svelte/store';
+
+export interface UpdateAutomationConfigResult {
+ success: 'ok' | 'error';
+ err?: unknown;
+}
+
+interface UpdateResult {
+ result: 'success' | 'error';
+ err?: unknown;
+}
+
+export const updateAutomationKeysConfig = async ({
+ automationConfig,
+ providerConfig,
+ maxTimeToLive,
+ scope,
+ identity,
+ satellite
+}: {
+ scope: Omit | undefined;
+ maxTimeToLive: bigint | undefined;
+ automationConfig: SatelliteDid.AutomationConfig;
+ providerConfig: SatelliteDid.OpenIdAutomationProviderConfig;
+ satellite: Satellite;
+ identity: OptionIdentity;
+}): Promise => {
+ const labels = get(i18n);
+
+ if (isNullish(identity) || isNullish(identity?.getPrincipal())) {
+ toasts.error({ text: labels.core.not_logged_in });
+ return { success: 'error' };
+ }
+
+ const updateProviderConfig: SatelliteDid.OpenIdAutomationProviderConfig = {
+ ...providerConfig,
+ controller:
+ scope === 'write' && maxTimeToLive === AUTOMATION_DEFAULT_MAX_SESSION_TIME_TO_LIVE
+ ? []
+ : toNullable({
+ max_time_to_live:
+ maxTimeToLive === AUTOMATION_DEFAULT_MAX_SESSION_TIME_TO_LIVE
+ ? []
+ : toNullable(maxTimeToLive),
+ scope: scope === 'write' ? [] : toNullable({ Submit: null })
+ })
+ };
+
+ const updateAutomationConfig: SatelliteDid.SetAutomationConfig = {
+ ...automationConfig,
+ openid: toNullable({
+ observatory_id: [],
+ providers: [[{ GitHub: null }, updateProviderConfig]]
+ })
+ };
+
+ const result = await updateConfig({ identity, satellite, config: updateAutomationConfig });
+
+ if (result.result === 'error') {
+ return {
+ success: 'error',
+ err: result.err
+ };
+ }
+
+ if (result.result === 'success') {
+ // Reload Satellite configuration
+ await loadSatelliteConfig({
+ identity,
+ satelliteId: satellite.satellite_id,
+ reload: true
+ });
+ }
+
+ return { success: 'ok' };
+};
+
+const updateConfig = async ({
+ satellite: { satellite_id: satelliteId },
+ config,
+ identity
+}: {
+ satellite: Satellite;
+ config: SatelliteDid.SetAutomationConfig;
+ identity: Identity;
+}): Promise => {
+ try {
+ await setAutomationConfig({
+ satelliteId,
+ config,
+ identity
+ });
+ } catch (err: unknown) {
+ const labels = get(i18n);
+
+ toasts.error({
+ text: labels.errors.save_automation_config,
+ detail: err
+ });
+
+ return { result: 'error', err };
+ }
+
+ return { result: 'success' };
+};
diff --git a/src/frontend/src/lib/services/satellite/automation/automation.config.services.ts b/src/frontend/src/lib/services/satellite/automation/automation.config.services.ts
index d904c7f89..2983bd9dd 100644
--- a/src/frontend/src/lib/services/satellite/automation/automation.config.services.ts
+++ b/src/frontend/src/lib/services/satellite/automation/automation.config.services.ts
@@ -140,7 +140,7 @@ const setAutomationConfig = async ({
const labels = get(i18n);
toasts.error({
- text: labels.errors.create_automation_config,
+ text: labels.errors.save_automation_config,
detail: err
});
diff --git a/src/frontend/src/lib/types/i18n.d.ts b/src/frontend/src/lib/types/i18n.d.ts
index 98b3a8724..fe4addb46 100644
--- a/src/frontend/src/lib/types/i18n.d.ts
+++ b/src/frontend/src/lib/types/i18n.d.ts
@@ -101,6 +101,12 @@ interface I18nCore {
staging: string;
test: string;
unspecified: string;
+ two_minutes: string;
+ five_minutes: string;
+ ten_minutes: string;
+ fifteen_minutes: string;
+ thirty_minutes: string;
+ forty_five_minutes: string;
an_hour: string;
two_hours: string;
four_hours: string;
@@ -771,7 +777,8 @@ interface I18nErrors {
build_repo_key_invalid_github_url: string;
build_repo_key_owner_not_found: string;
build_repo_key_repo_not_found: string;
- create_automation_config: string;
+ save_automation_config: string;
+ automation_config_undefined: string;
}
interface I18nDocument {
@@ -1158,6 +1165,9 @@ interface I18nAutomation {
view_commit: string;
view_branch: string;
view_contributor: string;
+ keys: string;
+ access_duration: string;
+ edit_automation_keys: string;
}
interface I18n {
diff --git a/src/frontend/src/lib/types/modal.ts b/src/frontend/src/lib/types/modal.ts
index 7c44578c3..2a30031fa 100644
--- a/src/frontend/src/lib/types/modal.ts
+++ b/src/frontend/src/lib/types/modal.ts
@@ -131,6 +131,11 @@ export interface JunoModalWalletDetail {
export type JunoModalConvertIcpToCyclesDetails = Pick;
+export interface JunoModalAutomationConfigDetail extends JunoModalWithSatellite {
+ automationConfig: SatelliteDid.AutomationConfig;
+ providerConfig: SatelliteDid.OpenIdAutomationProviderConfig;
+}
+
export type JunoModalDetail =
| JunoModalUpgradeSatelliteDetail
| JunoModalUpgradeDetail
@@ -150,7 +155,8 @@ export type JunoModalDetail =
| JunoModalChangeDetail
| JunoModalCdnUpgradeDetail
| JunoModalEditAuthConfigDetail
- | JunoModalWalletDetail;
+ | JunoModalWalletDetail
+ | JunoModalAutomationConfigDetail;
export interface JunoModal {
type:
@@ -185,6 +191,7 @@ export interface JunoModal {
| 'upgrade_satellite_with_cdn'
| 'convert_icp_to_cycles'
| 'reconcile_out_of_sync_segments'
- | 'create_automation';
+ | 'create_automation'
+ | 'edit_automation_keys_config';
detail?: T;
}
diff --git a/src/frontend/src/routes/(split)/deployments/+page.svelte b/src/frontend/src/routes/(split)/deployments/+page.svelte
index 12627587f..e8aa01b9a 100644
--- a/src/frontend/src/routes/(split)/deployments/+page.svelte
+++ b/src/frontend/src/routes/(split)/deployments/+page.svelte
@@ -5,6 +5,7 @@
import Loaders from '$lib/components/app/loaders/Loaders.svelte';
import IdentityGuard from '$lib/components/auth/guards/IdentityGuard.svelte';
import Automation from '$lib/components/satellites/automation/Automation.svelte';
+ import AutomationSettings from '$lib/components/satellites/automation/AutomationSettings.svelte';
import SatelliteGuard from '$lib/components/satellites/guards/SatelliteGuard.svelte';
import NoTabs from '$lib/components/ui/NoTabs.svelte';
import Tabs from '$lib/components/ui/Tabs.svelte';
@@ -21,6 +22,10 @@
{
id: Symbol('1'),
labelKey: 'automation.title'
+ },
+ {
+ id: Symbol('2'),
+ labelKey: 'core.settings'
}
];
@@ -43,6 +48,8 @@
{#if $store.tabId === $store.tabs[0].id}
+ {:else if $store.tabId === $store.tabs[1].id}
+
{/if}
{/snippet}