diff --git a/src/console/console.did b/src/console/console.did index 09fe7df16..6d0283695 100644 --- a/src/console/console.did +++ b/src/console/console.did @@ -78,6 +78,7 @@ type Controller = record { expires_at : opt nat64; }; type ControllerScope = variant { Write; Admin; Submit }; +type CreateCanisterArgs = record { subnet_id : opt principal; name : opt text }; type CreateMissionControlArgs = record { subnet_id : opt principal }; type CreateOrbiterArgs = record { block_index : opt nat64; @@ -92,6 +93,12 @@ type CreateSatelliteArgs = record { name : opt text; user : principal; }; +type CreateSegmentArgs = variant { + Orbiter : CreateOrbiterArgs; + MissionControl : CreateMissionControlArgs; + Canister : CreateCanisterArgs; + Satellite : CreateSatelliteArgs; +}; type CustomDomain = record { updated_at : nat64; created_at : nat64; @@ -299,7 +306,7 @@ type SegmentKey = record { segment_id : principal; segment_kind : StorableSegmentKind; }; -type SegmentKind = variant { Orbiter; MissionControl; Satellite }; +type SegmentKind = variant { Orbiter; MissionControl; Canister; Satellite }; type SegmentsDeploymentOptions = record { orbiter : opt text; mission_control_version : opt text; @@ -340,7 +347,7 @@ type SetStorageConfig = record { redirects : opt vec record { text; StorageConfigRedirect }; }; type SignedDelegation = record { signature : blob; delegation : Delegation }; -type StorableSegmentKind = variant { Orbiter; Satellite }; +type StorableSegmentKind = variant { Orbiter; Canister; Satellite }; type StorageConfig = record { iframe : opt StorageConfigIFrame; updated_at : opt nat64; @@ -403,6 +410,7 @@ service : () -> { create_mission_control : (CreateMissionControlArgs) -> (principal); create_orbiter : (CreateOrbiterArgs) -> (principal); create_satellite : (CreateSatelliteArgs) -> (principal); + create_segment : (CreateSegmentArgs) -> (principal); del_controllers : (DeleteControllersArgs) -> (); del_custom_domain : (text) -> (); delete_proposal_assets : (DeleteProposalAssets) -> (); diff --git a/src/console/src/api/factory.rs b/src/console/src/api/factory.rs index 3d1379194..489ac319c 100644 --- a/src/console/src/api/factory.rs +++ b/src/console/src/api/factory.rs @@ -1,3 +1,4 @@ +use crate::factory::canister::create_canister; use crate::factory::mission_control::create_mission_control as create_mission_control_console; use crate::factory::orbiter::create_orbiter as create_orbiter_console; use crate::factory::satellite::create_satellite as create_satellite_console; @@ -6,7 +7,7 @@ use ic_cdk_macros::update; use junobuild_shared::ic::api::caller; use junobuild_shared::ic::UnwrapOrTrap; use junobuild_shared::types::interface::{ - CreateMissionControlArgs, CreateOrbiterArgs, CreateSatelliteArgs, + CreateMissionControlArgs, CreateOrbiterArgs, CreateSatelliteArgs, CreateSegmentArgs, }; #[update] @@ -33,3 +34,19 @@ async fn create_orbiter(args: CreateOrbiterArgs) -> Principal { create_orbiter_console(caller, args).await.unwrap_or_trap() } + +#[update] +async fn create_segment(args: CreateSegmentArgs) -> Principal { + let caller = caller(); + + let result = match args { + CreateSegmentArgs::Satellite(args) => create_satellite_console(caller, args).await, + CreateSegmentArgs::MissionControl(args) => { + create_mission_control_console(caller, args).await + } + CreateSegmentArgs::Orbiter(args) => create_orbiter_console(caller, args).await, + CreateSegmentArgs::Canister(args) => create_canister(caller, args).await, + }; + + result.unwrap_or_trap() +} diff --git a/src/console/src/constants.rs b/src/console/src/constants.rs index ec4b46768..dbb896f43 100644 --- a/src/console/src/constants.rs +++ b/src/console/src/constants.rs @@ -13,6 +13,7 @@ pub const SATELLITE_CREATION_FEE_CYCLES: CyclesTokens = CyclesTokens::from_e12s( pub const ORBITER_CREATION_FEE_CYCLES: CyclesTokens = CyclesTokens::from_e12s(3_000_000_000_000); pub const MISSION_CONTROL_CREATION_FEE_CYCLES: CyclesTokens = CyclesTokens::from_e12s(3_000_000_000_000); +pub const CANISTER_CREATION_FEE_CYCLES: CyclesTokens = CyclesTokens::from_e12s(3_000_000_000_000); // 1 ICP but also the default credit - i.e. a mission control starts with one credit. // A credit which can be used to start one satellite or one orbiter. diff --git a/src/console/src/factory/canister.rs b/src/console/src/factory/canister.rs new file mode 100644 index 000000000..517856c21 --- /dev/null +++ b/src/console/src/factory/canister.rs @@ -0,0 +1,83 @@ +use crate::accounts::get_existing_account; +use crate::constants::FREEZING_THRESHOLD_ONE_YEAR; +use crate::factory::orchestrator::create_segment_with_account; +use crate::factory::services::payment::{process_payment_cycles, refund_payment_cycles}; +use crate::factory::types::CanisterCreator; +use crate::factory::utils::controllers::update_mission_control_controllers; +use crate::fees::get_factory_fee; +use crate::rates::increment_canister_rate; +use crate::segments::add_segment as add_segment_store; +use crate::types::ledger::Fee; +use crate::types::state::{Segment, SegmentKey, StorableSegmentKind}; +use candid::{Nat, Principal}; +use junobuild_shared::constants::shared::CREATE_CANISTER_CYCLES; +use junobuild_shared::ic::api::id; +use junobuild_shared::mgmt::cmc::create_canister_with_cmc; +use junobuild_shared::mgmt::ic::create_canister_with_ic_mgmt; +use junobuild_shared::mgmt::types::cmc::SubnetId; +use junobuild_shared::mgmt::types::ic::CreateCanisterInitSettingsArg; +use junobuild_shared::types::interface::CreateCanisterArgs; +use junobuild_shared::types::state::{SegmentKind, UserId}; + +pub async fn create_canister( + caller: Principal, + args: CreateCanisterArgs, +) -> Result { + let account = get_existing_account(&caller)?; + + let name = args.name.clone(); + let creator: CanisterCreator = CanisterCreator::User((account.owner, None)); + + let fee = get_factory_fee(&SegmentKind::Canister)?.fee_cycles; + + let canister_id = create_segment_with_account( + create_raw_canister, + process_payment_cycles, + refund_payment_cycles, + &increment_canister_rate, + Fee::Cycles(fee), + &account, + creator, + args.into(), + ) + .await?; + + add_segment(&account.owner, &canister_id, &name); + + Ok(canister_id) +} + +async fn create_raw_canister( + creator: CanisterCreator, + subnet_id: Option, +) -> Result { + let CanisterCreator::User((user_id, _)) = creator else { + return Err("Mission Control cannot create a raw canister".to_string()); + }; + + // We temporarily use the Console as a controller to create the canister but + // remove it as soon as it is spin. + let temporary_init_controllers = Vec::from([id(), user_id]); + + let create_settings_arg = CreateCanisterInitSettingsArg { + controllers: temporary_init_controllers, + freezing_threshold: Nat::from(FREEZING_THRESHOLD_ONE_YEAR), + }; + + let mission_control_id = if let Some(subnet_id) = subnet_id { + create_canister_with_cmc(&create_settings_arg, CREATE_CANISTER_CYCLES, &subnet_id).await + } else { + create_canister_with_ic_mgmt(&create_settings_arg, CREATE_CANISTER_CYCLES).await + }?; + + update_mission_control_controllers(&mission_control_id, &user_id).await?; + + Ok(mission_control_id) +} + +fn add_segment(user: &UserId, canister_id: &Principal, name: &Option) { + let metadata = Segment::init_metadata(name); + let canister = Segment::new(canister_id, Some(metadata)); + let key = SegmentKey::from(user, canister_id, StorableSegmentKind::Canister); + add_segment_store(&key, &canister) +} diff --git a/src/console/src/factory/impls.rs b/src/console/src/factory/impls.rs index ef3a17402..5bd22cf04 100644 --- a/src/console/src/factory/impls.rs +++ b/src/console/src/factory/impls.rs @@ -2,7 +2,7 @@ use crate::factory::types::CanisterCreator; use crate::factory::types::CreateSegmentArgs; use candid::Principal; use junobuild_shared::types::interface::{ - CreateMissionControlArgs, CreateOrbiterArgs, CreateSatelliteArgs, + CreateCanisterArgs, CreateMissionControlArgs, CreateOrbiterArgs, CreateSatelliteArgs, }; use junobuild_shared::types::state::{ControllerId, UserId}; @@ -63,3 +63,14 @@ impl From for CreateSegmentArgs { } } } + +impl From for CreateSegmentArgs { + fn from(args: CreateCanisterArgs) -> Self { + Self { + // Unlike Satellite and Orbiter, or same as Mission Control, Canister can only be + // spin using credits or ICRC-2 transfer from. + block_index: None, + subnet_id: args.subnet_id, + } + } +} diff --git a/src/console/src/factory/mod.rs b/src/console/src/factory/mod.rs index 22157225a..182890340 100644 --- a/src/console/src/factory/mod.rs +++ b/src/console/src/factory/mod.rs @@ -1,3 +1,4 @@ +pub mod canister; mod impls; pub mod mission_control; pub mod orbiter; diff --git a/src/console/src/fees/init.rs b/src/console/src/fees/init.rs index f5c1d74a9..46bc82db5 100644 --- a/src/console/src/fees/init.rs +++ b/src/console/src/fees/init.rs @@ -1,6 +1,6 @@ use crate::constants::{ - MISSION_CONTROL_CREATION_FEE_CYCLES, ORBITER_CREATION_FEE_CYCLES, ORBITER_CREATION_FEE_ICP, - SATELLITE_CREATION_FEE_CYCLES, SATELLITE_CREATION_FEE_ICP, + CANISTER_CREATION_FEE_CYCLES, MISSION_CONTROL_CREATION_FEE_CYCLES, ORBITER_CREATION_FEE_CYCLES, + ORBITER_CREATION_FEE_ICP, SATELLITE_CREATION_FEE_CYCLES, SATELLITE_CREATION_FEE_ICP, }; use crate::types::state::{FactoryFee, FactoryFees}; use ic_cdk::api::time; @@ -35,5 +35,13 @@ pub fn init_factory_fees() -> FactoryFees { updated_at: now, }, ), + ( + SegmentKind::Canister, + FactoryFee { + fee_cycles: CANISTER_CREATION_FEE_CYCLES, + fee_icp: None, + updated_at: now, + }, + ), ]) } diff --git a/src/console/src/lib.rs b/src/console/src/lib.rs index d4432531b..1eaba9b0d 100644 --- a/src/console/src/lib.rs +++ b/src/console/src/lib.rs @@ -58,6 +58,7 @@ use junobuild_shared::types::domain::CustomDomains; use junobuild_shared::types::interface::CreateMissionControlArgs; use junobuild_shared::types::interface::CreateOrbiterArgs; use junobuild_shared::types::interface::CreateSatelliteArgs; +use junobuild_shared::types::interface::CreateSegmentArgs; use junobuild_shared::types::interface::{ AssertMissionControlCenterArgs, DeleteControllersArgs, GetCreateCanisterFeeArgs, SetControllersArgs, diff --git a/src/console/src/memory/lifecycle.rs b/src/console/src/memory/lifecycle.rs index 050a94eff..6dda67a0f 100644 --- a/src/console/src/memory/lifecycle.rs +++ b/src/console/src/memory/lifecycle.rs @@ -4,7 +4,7 @@ use crate::fees::init_factory_fees; use crate::memory::manager::{get_memory_upgrades, init_stable_state, STATE}; use crate::rates::init::init_factory_rates; use crate::types::state::{HeapState, ReleasesMetadata, State}; -use crate::upgrade::upgrade_init_factory_fees_and_rates; +use crate::upgrade::{upgrade_init_canister_fees_and_rates, upgrade_init_factory_fees_and_rates}; use ciborium::{from_reader, into_writer}; use ic_cdk_macros::{init, post_upgrade, pre_upgrade}; use junobuild_shared::ic::api::caller; @@ -60,4 +60,7 @@ fn post_upgrade() { // TODO: to be removed, one time upgrade upgrade_init_factory_fees_and_rates(); + + // TODO: to be removed, one time upgrade + upgrade_init_canister_fees_and_rates(); } diff --git a/src/console/src/rates/init.rs b/src/console/src/rates/init.rs index f51bf0364..0ddd76f59 100644 --- a/src/console/src/rates/init.rs +++ b/src/console/src/rates/init.rs @@ -35,5 +35,12 @@ pub fn init_factory_rates() -> FactoryRates { tokens: tokens.clone(), }, ), + ( + SegmentKind::Canister, + FactoryRate { + config: DEFAULT_RATE_CONFIG, + tokens: tokens.clone(), + }, + ), ]) } diff --git a/src/console/src/rates/services.rs b/src/console/src/rates/services.rs index 18413b279..b97d824eb 100644 --- a/src/console/src/rates/services.rs +++ b/src/console/src/rates/services.rs @@ -12,3 +12,7 @@ pub fn increment_mission_controls_rate() -> Result<(), String> { pub fn increment_orbiters_rate() -> Result<(), String> { increment_rate(&SegmentKind::Orbiter) } + +pub fn increment_canister_rate() -> Result<(), String> { + increment_rate(&SegmentKind::Canister) +} diff --git a/src/console/src/segments/store.rs b/src/console/src/segments/store.rs index fc84b1c42..6c2dc5f39 100644 --- a/src/console/src/segments/store.rs +++ b/src/console/src/segments/store.rs @@ -98,7 +98,7 @@ fn filter_segments_range( let end_key = SegmentKey { user: *user, // Fallback to last enum - segment_kind: segment_kind.clone().unwrap_or(StorableSegmentKind::Orbiter), + segment_kind: segment_kind.clone().unwrap_or(StorableSegmentKind::Canister), segment_id: segment_id.unwrap_or(PRINCIPAL_MAX), }; diff --git a/src/console/src/types.rs b/src/console/src/types.rs index cd6f2cd02..e83e1d7f2 100644 --- a/src/console/src/types.rs +++ b/src/console/src/types.rs @@ -150,6 +150,7 @@ pub mod state { // For historical reasons, MissionControl is not stored in the segments stable tree // but within the Account structure Orbiter, + Canister, } } diff --git a/src/console/src/upgrade.rs b/src/console/src/upgrade.rs index 7aac7271f..a33512623 100644 --- a/src/console/src/upgrade.rs +++ b/src/console/src/upgrade.rs @@ -1,7 +1,13 @@ +use crate::constants::CANISTER_CREATION_FEE_CYCLES; use crate::fees::init_factory_fees; use crate::rates::init::init_factory_rates; use crate::store::mutate_heap_state; -use crate::types::state::HeapState; +use crate::types::state::{FactoryFee, FactoryFees, FactoryRate, FactoryRates, HeapState}; +use ic_cdk::api::time; +use junobuild_shared::ic::api::print; +use junobuild_shared::rate::constants::DEFAULT_RATE_CONFIG; +use junobuild_shared::rate::types::RateTokens; +use junobuild_shared::types::state::SegmentKind; pub fn upgrade_init_factory_fees_and_rates() { mutate_heap_state(|state| upgrade_init_factory_fees_and_rates_impl(state)) @@ -11,3 +17,51 @@ fn upgrade_init_factory_fees_and_rates_impl(state: &mut HeapState) { state.factory_fees.get_or_insert_with(init_factory_fees); state.factory_rates.get_or_insert_with(init_factory_rates); } + +pub fn upgrade_init_canister_fees_and_rates() { + mutate_heap_state(|state| { + upgrade_init_factory_fees_and_rates_impl(state); + + upgrade_canister_fees(&mut state.factory_fees) + .unwrap_or_else(|err| print(format!("Error upgrading the Canister fee: {:?}", err))); + + upgrade_canister_rates(&mut state.factory_rates) + .unwrap_or_else(|err| print(format!("Error upgrading the Canister rate: {:?}", err))); + }); +} + +fn upgrade_canister_fees(factory_fees: &mut Option) -> Result<(), String> { + let fees = factory_fees + .as_mut() + .ok_or_else(|| "Factory fees not initialized".to_string())?; + + let fee = FactoryFee { + fee_cycles: CANISTER_CREATION_FEE_CYCLES, + fee_icp: None, + updated_at: time(), + }; + + fees.insert(SegmentKind::Canister, fee); + + Ok(()) +} + +fn upgrade_canister_rates(factory_rates: &mut Option) -> Result<(), String> { + let rates = factory_rates + .as_mut() + .ok_or_else(|| "Factory rates not initialized".to_string())?; + + let tokens: RateTokens = RateTokens { + tokens: 1, + updated_at: time(), + }; + + let rate = FactoryRate { + config: DEFAULT_RATE_CONFIG, + tokens: tokens.clone(), + }; + + rates.insert(SegmentKind::Canister, rate); + + Ok(()) +} diff --git a/src/declarations/console/console.did.d.ts b/src/declarations/console/console.did.d.ts index c3f4c00f5..eefbbb7ff 100644 --- a/src/declarations/console/console.did.d.ts +++ b/src/declarations/console/console.did.d.ts @@ -104,6 +104,10 @@ export interface Controller { expires_at: [] | [bigint]; } export type ControllerScope = { Write: null } | { Admin: null } | { Submit: null }; +export interface CreateCanisterArgs { + subnet_id: [] | [Principal]; + name: [] | [string]; +} export interface CreateMissionControlArgs { subnet_id: [] | [Principal]; } @@ -120,6 +124,11 @@ export interface CreateSatelliteArgs { name: [] | [string]; user: Principal; } +export type CreateSegmentArgs = + | { Orbiter: CreateOrbiterArgs } + | { MissionControl: CreateMissionControlArgs } + | { Canister: CreateCanisterArgs } + | { Satellite: CreateSatelliteArgs }; export interface CustomDomain { updated_at: bigint; created_at: bigint; @@ -362,7 +371,11 @@ export interface SegmentKey { segment_id: Principal; segment_kind: StorableSegmentKind; } -export type SegmentKind = { Orbiter: null } | { MissionControl: null } | { Satellite: null }; +export type SegmentKind = + | { Orbiter: null } + | { MissionControl: null } + | { Canister: null } + | { Satellite: null }; export interface SegmentsDeploymentOptions { orbiter: [] | [string]; mission_control_version: [] | [string]; @@ -406,7 +419,7 @@ export interface SignedDelegation { signature: Uint8Array; delegation: Delegation; } -export type StorableSegmentKind = { Orbiter: null } | { Satellite: null }; +export type StorableSegmentKind = { Orbiter: null } | { Canister: null } | { Satellite: null }; export interface StorageConfig { iframe: [] | [StorageConfigIFrame]; updated_at: [] | [bigint]; @@ -475,6 +488,7 @@ export interface _SERVICE { create_mission_control: ActorMethod<[CreateMissionControlArgs], Principal>; create_orbiter: ActorMethod<[CreateOrbiterArgs], Principal>; create_satellite: ActorMethod<[CreateSatelliteArgs], Principal>; + create_segment: ActorMethod<[CreateSegmentArgs], Principal>; del_controllers: ActorMethod<[DeleteControllersArgs], undefined>; del_custom_domain: ActorMethod<[string], undefined>; delete_proposal_assets: ActorMethod<[DeleteProposalAssets], undefined>; diff --git a/src/declarations/console/console.factory.certified.did.js b/src/declarations/console/console.factory.certified.did.js index cfe6c48a5..0703ecfdc 100644 --- a/src/declarations/console/console.factory.certified.did.js +++ b/src/declarations/console/console.factory.certified.did.js @@ -123,6 +123,16 @@ export const idlFactory = ({ IDL }) => { name: IDL.Opt(IDL.Text), user: IDL.Principal }); + const CreateCanisterArgs = IDL.Record({ + subnet_id: IDL.Opt(IDL.Principal), + name: IDL.Opt(IDL.Text) + }); + const CreateSegmentArgs = IDL.Variant({ + Orbiter: CreateOrbiterArgs, + MissionControl: CreateMissionControlArgs, + Canister: CreateCanisterArgs, + Satellite: CreateSatelliteArgs + }); const DeleteControllersArgs = IDL.Record({ controllers: IDL.Vec(IDL.Principal) }); @@ -220,6 +230,7 @@ export const idlFactory = ({ IDL }) => { const SegmentKind = IDL.Variant({ Orbiter: IDL.Null, MissionControl: IDL.Null, + Canister: IDL.Null, Satellite: IDL.Null }); const CyclesTokens = IDL.Record({ e12s: IDL.Nat64 }); @@ -423,6 +434,7 @@ export const idlFactory = ({ IDL }) => { }); const StorableSegmentKind = IDL.Variant({ Orbiter: IDL.Null, + Canister: IDL.Null, Satellite: IDL.Null }); const ListSegmentsArgs = IDL.Record({ @@ -501,6 +513,7 @@ export const idlFactory = ({ IDL }) => { create_mission_control: IDL.Func([CreateMissionControlArgs], [IDL.Principal], []), create_orbiter: IDL.Func([CreateOrbiterArgs], [IDL.Principal], []), create_satellite: IDL.Func([CreateSatelliteArgs], [IDL.Principal], []), + create_segment: IDL.Func([CreateSegmentArgs], [IDL.Principal], []), del_controllers: IDL.Func([DeleteControllersArgs], [], []), del_custom_domain: IDL.Func([IDL.Text], [], []), delete_proposal_assets: IDL.Func([DeleteProposalAssets], [], []), diff --git a/src/declarations/console/console.factory.did.js b/src/declarations/console/console.factory.did.js index d02b73458..3049be159 100644 --- a/src/declarations/console/console.factory.did.js +++ b/src/declarations/console/console.factory.did.js @@ -123,6 +123,16 @@ export const idlFactory = ({ IDL }) => { name: IDL.Opt(IDL.Text), user: IDL.Principal }); + const CreateCanisterArgs = IDL.Record({ + subnet_id: IDL.Opt(IDL.Principal), + name: IDL.Opt(IDL.Text) + }); + const CreateSegmentArgs = IDL.Variant({ + Orbiter: CreateOrbiterArgs, + MissionControl: CreateMissionControlArgs, + Canister: CreateCanisterArgs, + Satellite: CreateSatelliteArgs + }); const DeleteControllersArgs = IDL.Record({ controllers: IDL.Vec(IDL.Principal) }); @@ -220,6 +230,7 @@ export const idlFactory = ({ IDL }) => { const SegmentKind = IDL.Variant({ Orbiter: IDL.Null, MissionControl: IDL.Null, + Canister: IDL.Null, Satellite: IDL.Null }); const CyclesTokens = IDL.Record({ e12s: IDL.Nat64 }); @@ -423,6 +434,7 @@ export const idlFactory = ({ IDL }) => { }); const StorableSegmentKind = IDL.Variant({ Orbiter: IDL.Null, + Canister: IDL.Null, Satellite: IDL.Null }); const ListSegmentsArgs = IDL.Record({ @@ -501,6 +513,7 @@ export const idlFactory = ({ IDL }) => { create_mission_control: IDL.Func([CreateMissionControlArgs], [IDL.Principal], []), create_orbiter: IDL.Func([CreateOrbiterArgs], [IDL.Principal], []), create_satellite: IDL.Func([CreateSatelliteArgs], [IDL.Principal], []), + create_segment: IDL.Func([CreateSegmentArgs], [IDL.Principal], []), del_controllers: IDL.Func([DeleteControllersArgs], [], []), del_custom_domain: IDL.Func([IDL.Text], [], []), delete_proposal_assets: IDL.Func([DeleteProposalAssets], [], []), diff --git a/src/declarations/console/console.factory.did.mjs b/src/declarations/console/console.factory.did.mjs index d02b73458..3049be159 100644 --- a/src/declarations/console/console.factory.did.mjs +++ b/src/declarations/console/console.factory.did.mjs @@ -123,6 +123,16 @@ export const idlFactory = ({ IDL }) => { name: IDL.Opt(IDL.Text), user: IDL.Principal }); + const CreateCanisterArgs = IDL.Record({ + subnet_id: IDL.Opt(IDL.Principal), + name: IDL.Opt(IDL.Text) + }); + const CreateSegmentArgs = IDL.Variant({ + Orbiter: CreateOrbiterArgs, + MissionControl: CreateMissionControlArgs, + Canister: CreateCanisterArgs, + Satellite: CreateSatelliteArgs + }); const DeleteControllersArgs = IDL.Record({ controllers: IDL.Vec(IDL.Principal) }); @@ -220,6 +230,7 @@ export const idlFactory = ({ IDL }) => { const SegmentKind = IDL.Variant({ Orbiter: IDL.Null, MissionControl: IDL.Null, + Canister: IDL.Null, Satellite: IDL.Null }); const CyclesTokens = IDL.Record({ e12s: IDL.Nat64 }); @@ -423,6 +434,7 @@ export const idlFactory = ({ IDL }) => { }); const StorableSegmentKind = IDL.Variant({ Orbiter: IDL.Null, + Canister: IDL.Null, Satellite: IDL.Null }); const ListSegmentsArgs = IDL.Record({ @@ -501,6 +513,7 @@ export const idlFactory = ({ IDL }) => { create_mission_control: IDL.Func([CreateMissionControlArgs], [IDL.Principal], []), create_orbiter: IDL.Func([CreateOrbiterArgs], [IDL.Principal], []), create_satellite: IDL.Func([CreateSatelliteArgs], [IDL.Principal], []), + create_segment: IDL.Func([CreateSegmentArgs], [IDL.Principal], []), del_controllers: IDL.Func([DeleteControllersArgs], [], []), del_custom_domain: IDL.Func([IDL.Text], [], []), delete_proposal_assets: IDL.Func([DeleteProposalAssets], [], []), diff --git a/src/declarations/observatory/observatory.did.d.ts b/src/declarations/observatory/observatory.did.d.ts index d69e146e9..62f741388 100644 --- a/src/declarations/observatory/observatory.did.d.ts +++ b/src/declarations/observatory/observatory.did.d.ts @@ -117,7 +117,11 @@ export interface Segment { metadata: [] | [Array<[string, string]>]; kind: SegmentKind; } -export type SegmentKind = { Orbiter: null } | { MissionControl: null } | { Satellite: null }; +export type SegmentKind = + | { Orbiter: null } + | { MissionControl: null } + | { Canister: null } + | { Satellite: null }; export interface SetController { metadata: Array<[string, string]>; scope: ControllerScope; diff --git a/src/declarations/observatory/observatory.factory.certified.did.js b/src/declarations/observatory/observatory.factory.certified.did.js index ecb077c60..d88fac45a 100644 --- a/src/declarations/observatory/observatory.factory.certified.did.js +++ b/src/declarations/observatory/observatory.factory.certified.did.js @@ -97,6 +97,7 @@ export const idlFactory = ({ IDL }) => { const SegmentKind = IDL.Variant({ Orbiter: IDL.Null, MissionControl: IDL.Null, + Canister: IDL.Null, Satellite: IDL.Null }); const Segment = IDL.Record({ diff --git a/src/declarations/observatory/observatory.factory.did.js b/src/declarations/observatory/observatory.factory.did.js index 8083ab0e5..c36f6894f 100644 --- a/src/declarations/observatory/observatory.factory.did.js +++ b/src/declarations/observatory/observatory.factory.did.js @@ -97,6 +97,7 @@ export const idlFactory = ({ IDL }) => { const SegmentKind = IDL.Variant({ Orbiter: IDL.Null, MissionControl: IDL.Null, + Canister: IDL.Null, Satellite: IDL.Null }); const Segment = IDL.Record({ diff --git a/src/declarations/observatory/observatory.factory.did.mjs b/src/declarations/observatory/observatory.factory.did.mjs index 8083ab0e5..c36f6894f 100644 --- a/src/declarations/observatory/observatory.factory.did.mjs +++ b/src/declarations/observatory/observatory.factory.did.mjs @@ -97,6 +97,7 @@ export const idlFactory = ({ IDL }) => { const SegmentKind = IDL.Variant({ Orbiter: IDL.Null, MissionControl: IDL.Null, + Canister: IDL.Null, Satellite: IDL.Null }); const Segment = IDL.Record({ diff --git a/src/frontend/src/lib/api/console.api.ts b/src/frontend/src/lib/api/console.api.ts index 305f1dccb..9b71d5516 100644 --- a/src/frontend/src/lib/api/console.api.ts +++ b/src/frontend/src/lib/api/console.api.ts @@ -48,6 +48,12 @@ export const getMissionControlFee = async ({ }): Promise => await getFee({ identity, segmentKind: { MissionControl: null } }); +export const getCanisterFee = async ({ + identity +}: { + identity: OptionIdentity; +}): Promise => await getFee({ identity, segmentKind: { Canister: null } }); + const getFee = async ({ identity, segmentKind diff --git a/src/frontend/src/lib/components/canister-segment/CanisterOverview.svelte b/src/frontend/src/lib/components/canister-segment/CanisterOverview.svelte new file mode 100644 index 000000000..0cfbb8c0d --- /dev/null +++ b/src/frontend/src/lib/components/canister-segment/CanisterOverview.svelte @@ -0,0 +1,93 @@ + + + + +
+ {$i18n.satellites.overview} + +
+
+ + + + + +
+ +
+ + {#snippet label()} + {$i18n.canister.id} + {/snippet} + + + + +
+
+
+ + + +
+ {$i18n.monitoring.runtime} + +
+ +
+
+ + + + diff --git a/src/frontend/src/lib/components/canister-segment/CanisterSettings.svelte b/src/frontend/src/lib/components/canister-segment/CanisterSettings.svelte new file mode 100644 index 000000000..b9ed9ac8f --- /dev/null +++ b/src/frontend/src/lib/components/canister-segment/CanisterSettings.svelte @@ -0,0 +1,27 @@ + + + + + diff --git a/src/frontend/src/lib/components/core/Navmenu.svelte b/src/frontend/src/lib/components/core/Navmenu.svelte index 40648d301..94bfab89c 100644 --- a/src/frontend/src/lib/components/core/Navmenu.svelte +++ b/src/frontend/src/lib/components/core/Navmenu.svelte @@ -1,11 +1,12 @@