diff --git a/Makefile b/Makefile index d3f304abf..80d3c65b5 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ COMPAT_SRC := $(shell git ls-files system-images/compat/) UTILS_SRC := $(shell git ls-files system-images/utils/) BINFMT_SRC := $(shell git ls-files system-images/binfmt/) CORE_SRC := $(shell git ls-files core) $(shell git ls-files --recurse-submodules patch-db) $(GIT_HASH_FILE) -WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist/index.js sdk/baseDist/package.json web/patchdb-ui-seed.json sdk/dist/package.json +WEB_SHARED_SRC := $(shell git ls-files web/projects/shared) $(shell git ls-files web/projects/marketplace) $(shell ls -p web/ | grep -v / | sed 's/^/web\//g') web/node_modules/.package-lock.json web/config.json patch-db/client/dist/index.js sdk/baseDist/package.json web/patchdb-ui-seed.json sdk/dist/package.json WEB_UI_SRC := $(shell git ls-files web/projects/ui) WEB_SETUP_WIZARD_SRC := $(shell git ls-files web/projects/setup-wizard) WEB_INSTALL_WIZARD_SRC := $(shell git ls-files web/projects/install-wizard) @@ -302,17 +302,7 @@ $(COMPRESSED_WEB_UIS): $(WEB_UIS) $(ENVIRONMENT_FILE) ./compress-uis.sh web/config.json: $(GIT_HASH_FILE) web/config-sample.json - @ver=$$(cat VERSION.txt); \ - case $$ver in \ - *alpha*) osUrl="https://alpha-registry-x.start9.com/" ;; \ - *beta*) osUrl="https://beta-registry.start9.com/" ;; \ - *) osUrl="https://registry.start9.com/" ;; \ - esac; \ - gitHash=$$(cat GIT_HASH.txt); \ - jq '.useMocks = false' web/config-sample.json \ - | jq --arg gitHash "$$gitHash" '.gitHash = $$gitHash' \ - | jq --arg osUrl "$$osUrl" '.ui.startosRegistry = $$osUrl' \ - > web/config.json; + jq '.useMocks = false' web/config-sample.json | jq '.gitHash = "$(shell cat GIT_HASH.txt)"' > web/config.json patch-db/client/node_modules/.package-lock.json: patch-db/client/package.json npm --prefix patch-db/client ci diff --git a/core/Cargo.lock b/core/Cargo.lock index a0d511d5b..02b74c15b 100644 --- a/core/Cargo.lock +++ b/core/Cargo.lock @@ -6001,7 +6001,7 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "start-os" -version = "0.4.0-alpha.0" +version = "0.4.0-alpha.1" dependencies = [ "aes 0.7.5", "async-acme", diff --git a/core/startos/Cargo.toml b/core/startos/Cargo.toml index 55d6a8622..bead9781f 100644 --- a/core/startos/Cargo.toml +++ b/core/startos/Cargo.toml @@ -14,7 +14,7 @@ keywords = [ name = "start-os" readme = "README.md" repository = "https://github.com/Start9Labs/start-os" -version = "0.4.0-alpha.0" # VERSION_BUMP +version = "0.4.0-alpha.1" # VERSION_BUMP license = "MIT" [lib] diff --git a/core/startos/src/registry/admin.rs b/core/startos/src/registry/admin.rs index ffc9c3b35..02a852dd1 100644 --- a/core/startos/src/registry/admin.rs +++ b/core/startos/src/registry/admin.rs @@ -22,16 +22,30 @@ pub fn admin_api() -> ParentHandler { "signer", signers_api::().with_about("Commands to add or list signers"), ) - .subcommand("add", from_fn_async(add_admin).no_cli()) + .subcommand( + "add", + from_fn_async(add_admin) + .with_metadata("admin", Value::Bool(true)) + .no_cli(), + ) .subcommand( "add", from_fn_async(cli_add_admin) .no_display() .with_about("Add admin signer"), ) + .subcommand( + "remove", + from_fn_async(remove_admin) + .with_metadata("admin", Value::Bool(true)) + .no_display() + .with_about("Remove an admin signer") + .with_call_remote::(), + ) .subcommand( "list", from_fn_async(list_admins) + .with_metadata("admin", Value::Bool(true)) .with_display_serializable() .with_custom_display_fn(|handle, result| Ok(display_signers(handle.params, result))) .with_about("List admin signers") @@ -285,6 +299,27 @@ pub async fn add_admin( .result } +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct RemoveAdminParams { + pub signer: Guid, +} + +// TODO: don't allow removing self? +pub async fn remove_admin( + ctx: RegistryContext, + RemoveAdminParams { signer }: RemoveAdminParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + db.as_admins_mut().mutate(|a| Ok(a.remove(&signer)))?; + Ok(()) + }) + .await + .result +} + #[derive(Debug, Deserialize, Serialize, Parser)] #[command(rename_all = "kebab-case")] #[serde(rename_all = "camelCase")] diff --git a/core/startos/src/registry/os/asset/add.rs b/core/startos/src/registry/os/asset/add.rs index 09a6ad164..89a1ec9b1 100644 --- a/core/startos/src/registry/os/asset/add.rs +++ b/core/startos/src/registry/os/asset/add.rs @@ -51,6 +51,28 @@ pub fn add_api() -> ParentHandler { ) } +pub fn remove_api() -> ParentHandler { + ParentHandler::new() + .subcommand( + "iso", + from_fn_async(remove_iso) + .with_metadata("get_signer", Value::Bool(true)) + .no_cli(), + ) + .subcommand( + "img", + from_fn_async(remove_img) + .with_metadata("get_signer", Value::Bool(true)) + .no_cli(), + ) + .subcommand( + "squashfs", + from_fn_async(remove_squashfs) + .with_metadata("get_signer", Value::Bool(true)) + .no_cli(), + ) +} + #[derive(Debug, Deserialize, Serialize, TS)] #[serde(rename_all = "camelCase")] #[ts(export)] @@ -99,6 +121,7 @@ async fn add_asset( .as_authorized() .de()? .contains(&signer_guid) + || db.as_admins().de()?.contains(&signer_guid) { accessor( db.as_index_mut() @@ -256,3 +279,74 @@ pub async fn cli_add_asset( Ok(()) } + +#[derive(Debug, Deserialize, Serialize, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct RemoveAssetParams { + #[ts(type = "string")] + pub version: Version, + #[ts(type = "string")] + pub platform: InternedString, + #[serde(rename = "__auth_signer")] + #[ts(skip)] + pub signer: AnyVerifyingKey, +} + +async fn remove_asset( + ctx: RegistryContext, + RemoveAssetParams { + version, + platform, + signer, + }: RemoveAssetParams, + accessor: impl FnOnce( + &mut Model, + ) -> &mut Model>> + + UnwindSafe + + Send, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + let signer_guid = db.as_index().as_signers().get_signer(&signer)?; + if db + .as_index() + .as_os() + .as_versions() + .as_idx(&version) + .or_not_found(&version)? + .as_authorized() + .de()? + .contains(&signer_guid) + || db.as_admins().de()?.contains(&signer_guid) + { + accessor( + db.as_index_mut() + .as_os_mut() + .as_versions_mut() + .as_idx_mut(&version) + .or_not_found(&version)?, + ) + .remove(&platform)?; + Ok(()) + } else { + Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization)) + } + }) + .await + .result?; + + Ok(()) +} + +pub async fn remove_iso(ctx: RegistryContext, params: RemoveAssetParams) -> Result<(), Error> { + remove_asset(ctx, params, |m| m.as_iso_mut()).await +} + +pub async fn remove_img(ctx: RegistryContext, params: RemoveAssetParams) -> Result<(), Error> { + remove_asset(ctx, params, |m| m.as_img_mut()).await +} + +pub async fn remove_squashfs(ctx: RegistryContext, params: RemoveAssetParams) -> Result<(), Error> { + remove_asset(ctx, params, |m| m.as_squashfs_mut()).await +} diff --git a/core/startos/src/registry/os/asset/mod.rs b/core/startos/src/registry/os/asset/mod.rs index 52c12341a..9cafa14a6 100644 --- a/core/startos/src/registry/os/asset/mod.rs +++ b/core/startos/src/registry/os/asset/mod.rs @@ -13,6 +13,7 @@ pub fn asset_api() -> ParentHandler { .no_display() .with_about("Add asset to registry"), ) + .subcommand("remove", add::remove_api::()) .subcommand("sign", sign::sign_api::()) .subcommand( "sign", @@ -20,6 +21,7 @@ pub fn asset_api() -> ParentHandler { .no_display() .with_about("Sign file and add to registry index"), ) + // TODO: remove signature api .subcommand( "get", get::get_api::().with_about("Commands to download image, iso, or squashfs files"), diff --git a/core/startos/src/registry/os/version/mod.rs b/core/startos/src/registry/os/version/mod.rs index 12661e4e1..ddbf92b24 100644 --- a/core/startos/src/registry/os/version/mod.rs +++ b/core/startos/src/registry/os/version/mod.rs @@ -3,6 +3,7 @@ use std::collections::BTreeMap; use chrono::Utc; use clap::Parser; use exver::{Version, VersionRange}; +use imbl_value::InternedString; use itertools::Itertools; use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; @@ -12,6 +13,7 @@ use ts_rs::TS; use crate::context::CliContext; use crate::prelude::*; use crate::registry::context::RegistryContext; +use crate::registry::device_info::DeviceInfo; use crate::registry::os::index::OsVersionInfo; use crate::registry::signer::sign::AnyVerifyingKey; use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; @@ -44,6 +46,7 @@ pub fn version_api() -> ParentHandler { .subcommand( "get", from_fn_async(get_version) + .with_metadata("get_device_info", Value::Bool(true)) .with_display_serializable() .with_custom_display_fn(|handle, result| { Ok(display_version_info(handle.params, result)) @@ -133,35 +136,47 @@ pub async fn remove_version( pub struct GetOsVersionParams { #[ts(type = "string | null")] #[arg(long = "src")] - pub source: Option, - #[ts(type = "string | null")] - #[arg(long = "target")] - pub target: Option, + pub source_version: Option, #[ts(type = "string | null")] + #[arg(long)] + pub target_version: Option, + #[arg(long)] + pub include_prerelease: Option, #[arg(long = "id")] server_id: Option, #[ts(type = "string | null")] - #[arg(long = "arch")] - arch: Option, + #[arg(long)] + platform: Option, + #[ts(skip)] + #[arg(skip)] + #[serde(rename = "__device_info")] + pub device_info: Option, } pub async fn get_version( ctx: RegistryContext, GetOsVersionParams { - source, - target, + source_version: source, + target_version: target, + include_prerelease, server_id, - arch, + platform, + device_info, }: GetOsVersionParams, ) -> Result, Error> { - if let (Some(pool), Some(server_id), Some(arch)) = (&ctx.pool, server_id, arch) { + let source = source.or_else(|| device_info.as_ref().map(|d| d.os.version.clone())); + let platform = platform.or_else(|| device_info.as_ref().map(|d| d.os.platform.clone())); + let include_prerelease = include_prerelease + .or_else(|| source.as_ref().map(|s| !s.prerelease().is_empty())) + .unwrap_or(cfg!(feature = "dev")); + if let (Some(pool), Some(server_id), Some(arch)) = (&ctx.pool, server_id, &platform) { let created_at = Utc::now(); query!( "INSERT INTO user_activity (created_at, server_id, arch) VALUES ($1, $2, $3)", created_at, server_id, - arch + &**arch ) .execute(pool) .await?; @@ -177,7 +192,11 @@ pub async fn get_version( .into_iter() .map(|(v, i)| i.de().map(|i| (v, i))) .filter_ok(|(version, info)| { - version.satisfies(&target) + (version.prerelease().is_empty() || include_prerelease) + && platform + .as_ref() + .map_or(true, |p| info.squashfs.contains_key(p)) + && version.satisfies(&target) && source .as_ref() .map_or(true, |s| s.satisfies(&info.source_version)) diff --git a/core/startos/src/registry/package/add.rs b/core/startos/src/registry/package/add.rs index bf3d36fd1..146f96334 100644 --- a/core/startos/src/registry/package/add.rs +++ b/core/startos/src/registry/package/add.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use clap::Parser; use imbl_value::InternedString; use itertools::Itertools; +use models::{PackageId, VersionString}; use rpc_toolkit::HandlerArgs; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -158,3 +159,55 @@ pub async fn cli_add_package( Ok(()) } + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct RemovePackageParams { + pub id: PackageId, + pub version: VersionString, + #[ts(skip)] + #[serde(rename = "__auth_signer")] + pub signer: AnyVerifyingKey, +} + +pub async fn remove_package( + ctx: RegistryContext, + RemovePackageParams { + id, + version, + signer, + }: RemovePackageParams, +) -> Result<(), Error> { + let peek = ctx.db.peek().await; + let signer_guid = peek.as_index().as_signers().get_signer(&signer)?; + + ctx.db + .mutate(|db| { + if db.as_admins().de()?.contains(&signer_guid) + || db + .as_index() + .as_package() + .as_packages() + .as_idx(&id) + .or_not_found(&id)? + .as_authorized() + .de()? + .contains(&signer_guid) + { + if let Some(package) = db + .as_index_mut() + .as_package_mut() + .as_packages_mut() + .as_idx_mut(&id) + { + package.as_versions_mut().remove(&version)?; + } + Ok(()) + } else { + Err(Error::new(eyre!("UNAUTHORIZED"), ErrorKind::Authorization)) + } + }) + .await + .result +} diff --git a/core/startos/src/registry/package/category.rs b/core/startos/src/registry/package/category.rs index 7afb8906b..7c3aafad8 100644 --- a/core/startos/src/registry/package/category.rs +++ b/core/startos/src/registry/package/category.rs @@ -2,6 +2,7 @@ use std::collections::BTreeMap; use clap::Parser; use imbl_value::InternedString; +use models::PackageId; use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler}; use serde::{Deserialize, Serialize}; use ts_rs::TS; @@ -10,7 +11,6 @@ use crate::context::CliContext; use crate::prelude::*; use crate::registry::context::RegistryContext; use crate::registry::package::index::Category; -use crate::s9pk::manifest::Description; use crate::util::serde::{display_serializable, HandlerExtSerde, WithIoFormat}; pub fn category_api() -> ParentHandler { @@ -31,6 +31,22 @@ pub fn category_api() -> ParentHandler { .with_about("Remove a category from the registry") .with_call_remote::(), ) + .subcommand( + "add-package", + from_fn_async(add_package) + .with_metadata("admin", Value::Bool(true)) + .no_display() + .with_about("Add a package to a category") + .with_call_remote::(), + ) + .subcommand( + "remove-package", + from_fn_async(remove_package) + .with_metadata("admin", Value::Bool(true)) + .no_display() + .with_about("Remove a package from a category") + .with_call_remote::(), + ) .subcommand( "list", from_fn_async(list_categories) @@ -50,33 +66,18 @@ pub struct AddCategoryParams { #[ts(type = "string")] pub id: InternedString, pub name: String, - #[arg(short, long, help = "Short description for the category")] - pub short: String, - #[arg(short, long, help = "Long description for the category")] - pub long: String, } pub async fn add_category( ctx: RegistryContext, - AddCategoryParams { - id, - name, - short, - long, - }: AddCategoryParams, + AddCategoryParams { id, name }: AddCategoryParams, ) -> Result<(), Error> { ctx.db .mutate(|db| { db.as_index_mut() .as_package_mut() .as_categories_mut() - .insert( - &id, - &Category { - name, - description: Description { short, long }, - }, - ) + .insert(&id, &Category { name }) }) .await .result?; @@ -108,6 +109,64 @@ pub async fn remove_category( Ok(()) } +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct AddPackageToCategoryParams { + #[ts(type = "string")] + pub id: InternedString, + pub package: PackageId, +} + +pub async fn add_package( + ctx: RegistryContext, + AddPackageToCategoryParams { id, package }: AddPackageToCategoryParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + db.as_index_mut() + .as_package_mut() + .as_packages_mut() + .as_idx_mut(&package) + .or_not_found(&package)? + .as_categories_mut() + .mutate(|c| Ok(c.insert(id))) + }) + .await + .result?; + Ok(()) +} + +#[derive(Debug, Deserialize, Serialize, Parser, TS)] +#[command(rename_all = "kebab-case")] +#[serde(rename_all = "camelCase")] +#[ts(export)] +pub struct RemovePackageFromCategoryParams { + #[ts(type = "string")] + pub id: InternedString, + pub package: PackageId, +} + +pub async fn remove_package( + ctx: RegistryContext, + RemovePackageFromCategoryParams { id, package }: RemovePackageFromCategoryParams, +) -> Result<(), Error> { + ctx.db + .mutate(|db| { + db.as_index_mut() + .as_package_mut() + .as_packages_mut() + .as_idx_mut(&package) + .or_not_found(&package)? + .as_categories_mut() + .mutate(|c| Ok(c.remove(&id))) + }) + .await + .result?; + Ok(()) +} + pub async fn list_categories( ctx: RegistryContext, ) -> Result, Error> { @@ -134,16 +193,9 @@ pub fn display_categories( table.add_row(row![bc => "ID", "NAME", - "SHORT DESCRIPTION", - "LONG DESCRIPTION", ]); for (id, info) in categories { - table.add_row(row![ - &*id, - &info.name, - &info.description.short, - &info.description.long, - ]); + table.add_row(row![&*id, &info.name]); } table.print_tty(false).unwrap(); } diff --git a/core/startos/src/registry/package/get.rs b/core/startos/src/registry/package/get.rs index cae1289a9..32a279fde 100644 --- a/core/startos/src/registry/package/get.rs +++ b/core/startos/src/registry/package/get.rs @@ -45,7 +45,9 @@ pub struct PackageInfoShort { pub struct GetPackageParams { pub id: Option, #[ts(type = "string | null")] - pub version: Option, + #[arg(long, short = 'v')] + pub target_version: Option, + #[arg(long)] pub source_version: Option, #[ts(skip)] #[arg(skip)] @@ -188,7 +190,7 @@ pub async fn get_package(ctx: RegistryContext, params: GetPackageParams) -> Resu let package_best = best.entry(id.clone()).or_default(); let package_other = other.entry(id.clone()).or_default(); if params - .version + .target_version .as_ref() .map_or(true, |v| version.satisfies(v)) && package_best.keys().all(|k| !(**k > version)) diff --git a/core/startos/src/registry/package/index.rs b/core/startos/src/registry/package/index.rs index d48963267..2081d4771 100644 --- a/core/startos/src/registry/package/index.rs +++ b/core/startos/src/registry/package/index.rs @@ -47,7 +47,6 @@ pub struct PackageInfo { #[ts(export)] pub struct Category { pub name: String, - pub description: Description, } #[derive(Debug, Deserialize, Serialize, HasModel, TS)] diff --git a/core/startos/src/registry/package/mod.rs b/core/startos/src/registry/package/mod.rs index 74d244deb..fa395a479 100644 --- a/core/startos/src/registry/package/mod.rs +++ b/core/startos/src/registry/package/mod.rs @@ -31,6 +31,13 @@ pub fn package_api() -> ParentHandler { .no_display() .with_about("Add package to registry index"), ) + .subcommand( + "remove", + from_fn_async(add::remove_package) + .no_display() + .with_about("Add package to registry index") + .with_call_remote::(), + ) .subcommand( "signer", signer::signer_api::().with_about("Add, remove, and list package signers"), diff --git a/core/startos/src/version/mod.rs b/core/startos/src/version/mod.rs index 3e8523d33..1cf615005 100644 --- a/core/startos/src/version/mod.rs +++ b/core/startos/src/version/mod.rs @@ -40,8 +40,9 @@ mod v0_3_6_alpha_17; mod v0_3_6_alpha_18; mod v0_4_0_alpha_0; +mod v0_4_0_alpha_1; -pub type Current = v0_4_0_alpha_0::Version; // VERSION_BUMP +pub type Current = v0_4_0_alpha_1::Version; // VERSION_BUMP impl Current { #[instrument(skip(self, db))] @@ -145,7 +146,8 @@ enum Version { V0_3_6_alpha_16(Wrapper), V0_3_6_alpha_17(Wrapper), V0_3_6_alpha_18(Wrapper), - V0_4_0_alpha_0(Wrapper), // VERSION_BUMP + V0_4_0_alpha_0(Wrapper), + V0_4_0_alpha_1(Wrapper), // VERSION_BUMP Other(exver::Version), } @@ -187,8 +189,9 @@ impl Version { Self::V0_3_6_alpha_15(v) => DynVersion(Box::new(v.0)), Self::V0_3_6_alpha_16(v) => DynVersion(Box::new(v.0)), Self::V0_3_6_alpha_17(v) => DynVersion(Box::new(v.0)), - Self::V0_3_6_alpha_18(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP - Self::V0_4_0_alpha_0(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP + Self::V0_3_6_alpha_18(v) => DynVersion(Box::new(v.0)), + Self::V0_4_0_alpha_0(v) => DynVersion(Box::new(v.0)), + Self::V0_4_0_alpha_1(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP Self::Other(v) => { return Err(Error::new( eyre!("unknown version {v}"), @@ -223,7 +226,8 @@ impl Version { Version::V0_3_6_alpha_16(Wrapper(x)) => x.semver(), Version::V0_3_6_alpha_17(Wrapper(x)) => x.semver(), Version::V0_3_6_alpha_18(Wrapper(x)) => x.semver(), - Version::V0_4_0_alpha_0(Wrapper(x)) => x.semver(), // VERSION_BUMP + Version::V0_4_0_alpha_0(Wrapper(x)) => x.semver(), + Version::V0_4_0_alpha_1(Wrapper(x)) => x.semver(), // VERSION_BUMP Version::Other(x) => x.clone(), } } diff --git a/core/startos/src/version/v0_4_0_alpha_1.rs b/core/startos/src/version/v0_4_0_alpha_1.rs new file mode 100644 index 000000000..f44a5ef51 --- /dev/null +++ b/core/startos/src/version/v0_4_0_alpha_1.rs @@ -0,0 +1,72 @@ +use exver::{PreReleaseSegment, VersionRange}; +use imbl_value::json; + +use super::v0_3_5::V0_3_0_COMPAT; +use super::{v0_4_0_alpha_0, VersionT}; +use crate::prelude::*; + +lazy_static::lazy_static! { + static ref V0_4_0_alpha_1: exver::Version = exver::Version::new( + [0, 4, 0], + [PreReleaseSegment::String("alpha".into()), 1.into()] + ); +} + +#[derive(Clone, Copy, Debug, Default)] +pub struct Version; + +impl VersionT for Version { + type Previous = v0_4_0_alpha_0::Version; + type PreUpRes = (); + + async fn pre_up(self) -> Result { + Ok(()) + } + fn semver(self) -> exver::Version { + V0_4_0_alpha_1.clone() + } + fn compat(self) -> &'static VersionRange { + &V0_3_0_COMPAT + } + fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<(), Error> { + let Some(ui) = db["public"]["ui"].as_object_mut() else { + return Err(Error::new( + eyre!("db.public.ui is not an object"), + ErrorKind::Database, + )); + }; + ui.insert( + "registries".into(), + Value::Object( + ui.get("marketplace") + .and_then(|m| m.get("knownHosts")) + .and_then(|kh| kh.as_object()) + .into_iter() + .flatten() + .map(|(k, v)| (k.clone(), v["name"].clone())) + .collect(), + ), + ); + if let Some(highscore) = ui + .get_mut("gaming") + .and_then(|g| g.get_mut("snake")) + .and_then(|s| s.get_mut("highScore")) + .map(|hs| hs.take()) + .filter(|s| s.is_number()) + { + ui.insert("snakeHighScore".into(), highscore); + } + ui.insert( + "startosRegistry".into(), + json!("https://registry.start9.com/"), + ); + ui.remove("marketplace"); + ui.remove("gaming"); + ui.remove("theme"); + + Ok(()) + } + fn down(self, _db: &mut Value) -> Result<(), Error> { + Ok(()) + } +} diff --git a/sdk/base/lib/osBindings/AddCategoryParams.ts b/sdk/base/lib/osBindings/AddCategoryParams.ts index 799f2d4d2..62b04e6e2 100644 --- a/sdk/base/lib/osBindings/AddCategoryParams.ts +++ b/sdk/base/lib/osBindings/AddCategoryParams.ts @@ -1,8 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -export type AddCategoryParams = { - id: string - name: string - short: string - long: string -} +export type AddCategoryParams = { id: string; name: string } diff --git a/sdk/base/lib/osBindings/AddPackageToCategoryParams.ts b/sdk/base/lib/osBindings/AddPackageToCategoryParams.ts new file mode 100644 index 000000000..2ac9c2ff3 --- /dev/null +++ b/sdk/base/lib/osBindings/AddPackageToCategoryParams.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PackageId } from "./PackageId" + +export type AddPackageToCategoryParams = { id: string; package: PackageId } diff --git a/sdk/base/lib/osBindings/Category.ts b/sdk/base/lib/osBindings/Category.ts index 6e0815675..615094527 100644 --- a/sdk/base/lib/osBindings/Category.ts +++ b/sdk/base/lib/osBindings/Category.ts @@ -1,4 +1,3 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. -import type { Description } from "./Description" -export type Category = { name: string; description: Description } +export type Category = { name: string } diff --git a/sdk/base/lib/osBindings/GetOsVersionParams.ts b/sdk/base/lib/osBindings/GetOsVersionParams.ts index de0458645..155819758 100644 --- a/sdk/base/lib/osBindings/GetOsVersionParams.ts +++ b/sdk/base/lib/osBindings/GetOsVersionParams.ts @@ -1,8 +1,9 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. export type GetOsVersionParams = { - source: string | null - target: string | null + sourceVersion: string | null + targetVersion: string | null + includePrerelease: boolean | null serverId: string | null - arch: string | null + platform: string | null } diff --git a/sdk/base/lib/osBindings/GetPackageParams.ts b/sdk/base/lib/osBindings/GetPackageParams.ts index 3dde55b28..22046d8e9 100644 --- a/sdk/base/lib/osBindings/GetPackageParams.ts +++ b/sdk/base/lib/osBindings/GetPackageParams.ts @@ -5,7 +5,7 @@ import type { Version } from "./Version" export type GetPackageParams = { id: PackageId | null - version: string | null + targetVersion: string | null sourceVersion: Version | null otherVersions: PackageDetailLevel } diff --git a/sdk/base/lib/osBindings/RemoveAdminParams.ts b/sdk/base/lib/osBindings/RemoveAdminParams.ts new file mode 100644 index 000000000..80ca0e823 --- /dev/null +++ b/sdk/base/lib/osBindings/RemoveAdminParams.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { Guid } from "./Guid" + +export type RemoveAdminParams = { signer: Guid } diff --git a/sdk/base/lib/osBindings/RemoveAssetParams.ts b/sdk/base/lib/osBindings/RemoveAssetParams.ts new file mode 100644 index 000000000..220c2f3b0 --- /dev/null +++ b/sdk/base/lib/osBindings/RemoveAssetParams.ts @@ -0,0 +1,3 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RemoveAssetParams = { version: string; platform: string } diff --git a/sdk/base/lib/osBindings/RemovePackageFromCategoryParams.ts b/sdk/base/lib/osBindings/RemovePackageFromCategoryParams.ts new file mode 100644 index 000000000..c12dc6002 --- /dev/null +++ b/sdk/base/lib/osBindings/RemovePackageFromCategoryParams.ts @@ -0,0 +1,4 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PackageId } from "./PackageId" + +export type RemovePackageFromCategoryParams = { id: string; package: PackageId } diff --git a/sdk/base/lib/osBindings/RemovePackageParams.ts b/sdk/base/lib/osBindings/RemovePackageParams.ts new file mode 100644 index 000000000..aee8d50f2 --- /dev/null +++ b/sdk/base/lib/osBindings/RemovePackageParams.ts @@ -0,0 +1,5 @@ +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { PackageId } from "./PackageId" +import type { Version } from "./Version" + +export type RemovePackageParams = { id: PackageId; version: Version } diff --git a/sdk/base/lib/osBindings/index.ts b/sdk/base/lib/osBindings/index.ts index a1aacb61b..9162d46d0 100644 --- a/sdk/base/lib/osBindings/index.ts +++ b/sdk/base/lib/osBindings/index.ts @@ -4,13 +4,13 @@ export { AcmeSettings } from "./AcmeSettings" export { ActionId } from "./ActionId" export { ActionInput } from "./ActionInput" export { ActionMetadata } from "./ActionMetadata" -export { ActionRequest } from "./ActionRequest" export { ActionRequestCondition } from "./ActionRequestCondition" export { ActionRequestEntry } from "./ActionRequestEntry" export { ActionRequestInput } from "./ActionRequestInput" export { ActionRequestTrigger } from "./ActionRequestTrigger" -export { ActionResult } from "./ActionResult" +export { ActionRequest } from "./ActionRequest" export { ActionResultMember } from "./ActionResultMember" +export { ActionResult } from "./ActionResult" export { ActionResultV0 } from "./ActionResultV0" export { ActionResultV1 } from "./ActionResultV1" export { ActionResultValue } from "./ActionResultValue" @@ -20,13 +20,14 @@ export { AddAdminParams } from "./AddAdminParams" export { AddAssetParams } from "./AddAssetParams" export { AddCategoryParams } from "./AddCategoryParams" export { AddPackageParams } from "./AddPackageParams" +export { AddPackageToCategoryParams } from "./AddPackageToCategoryParams" +export { AddressInfo } from "./AddressInfo" export { AddSslOptions } from "./AddSslOptions" export { AddVersionParams } from "./AddVersionParams" -export { AddressInfo } from "./AddressInfo" export { Alerts } from "./Alerts" export { Algorithm } from "./Algorithm" -export { AllPackageData } from "./AllPackageData" export { AllowedStatuses } from "./AllowedStatuses" +export { AllPackageData } from "./AllPackageData" export { AlpnInfo } from "./AlpnInfo" export { AnySignature } from "./AnySignature" export { AnySigningKey } from "./AnySigningKey" @@ -38,9 +39,9 @@ export { BackupTargetFS } from "./BackupTargetFS" export { Base64 } from "./Base64" export { BindId } from "./BindId" export { BindInfo } from "./BindInfo" +export { BindingSetPublicParams } from "./BindingSetPublicParams" export { BindOptions } from "./BindOptions" export { BindParams } from "./BindParams" -export { BindingSetPublicParams } from "./BindingSetPublicParams" export { Blake3Commitment } from "./Blake3Commitment" export { BlockDev } from "./BlockDev" export { BuildArg } from "./BuildArg" @@ -61,11 +62,11 @@ export { CreateSubcontainerFsParams } from "./CreateSubcontainerFsParams" export { CurrentDependencies } from "./CurrentDependencies" export { CurrentDependencyInfo } from "./CurrentDependencyInfo" export { DataUrl } from "./DataUrl" -export { DepInfo } from "./DepInfo" export { Dependencies } from "./Dependencies" export { DependencyKind } from "./DependencyKind" export { DependencyMetadata } from "./DependencyMetadata" export { DependencyRequirement } from "./DependencyRequirement" +export { DepInfo } from "./DepInfo" export { Description } from "./Description" export { DestroySubcontainerFsParams } from "./DestroySubcontainerFsParams" export { DeviceFilter } from "./DeviceFilter" @@ -86,8 +87,8 @@ export { GetHostInfoParams } from "./GetHostInfoParams" export { GetOsAssetParams } from "./GetOsAssetParams" export { GetOsVersionParams } from "./GetOsVersionParams" export { GetPackageParams } from "./GetPackageParams" -export { GetPackageResponse } from "./GetPackageResponse" export { GetPackageResponseFull } from "./GetPackageResponseFull" +export { GetPackageResponse } from "./GetPackageResponse" export { GetServiceInterfaceParams } from "./GetServiceInterfaceParams" export { GetServicePortForwardParams } from "./GetServicePortForwardParams" export { GetSslCertificateParams } from "./GetSslCertificateParams" @@ -101,21 +102,21 @@ export { Governor } from "./Governor" export { Guid } from "./Guid" export { HardwareRequirements } from "./HardwareRequirements" export { HealthCheckId } from "./HealthCheckId" -export { Host } from "./Host" export { HostAddress } from "./HostAddress" export { HostId } from "./HostId" export { HostnameInfo } from "./HostnameInfo" export { Hosts } from "./Hosts" +export { Host } from "./Host" export { ImageConfig } from "./ImageConfig" export { ImageId } from "./ImageId" export { ImageMetadata } from "./ImageMetadata" export { ImageSource } from "./ImageSource" export { InitProgressRes } from "./InitProgressRes" -export { InstallParams } from "./InstallParams" export { InstalledState } from "./InstalledState" export { InstalledVersionParams } from "./InstalledVersionParams" export { InstallingInfo } from "./InstallingInfo" export { InstallingState } from "./InstallingState" +export { InstallParams } from "./InstallParams" export { IpHostname } from "./IpHostname" export { IpInfo } from "./IpInfo" export { ListPackageSignersParams } from "./ListPackageSignersParams" @@ -130,11 +131,11 @@ export { Manifest } from "./Manifest" export { MaybeUtf8String } from "./MaybeUtf8String" export { MebiBytes } from "./MebiBytes" export { MerkleArchiveCommitment } from "./MerkleArchiveCommitment" -export { Metrics } from "./Metrics" export { MetricsCpu } from "./MetricsCpu" export { MetricsDisk } from "./MetricsDisk" export { MetricsGeneral } from "./MetricsGeneral" export { MetricsMemory } from "./MetricsMemory" +export { Metrics } from "./Metrics" export { MountParams } from "./MountParams" export { MountTarget } from "./MountTarget" export { NamedHealthCheckResult } from "./NamedHealthCheckResult" @@ -146,14 +147,14 @@ export { NetworkInterfaceSetInboundParams } from "./NetworkInterfaceSetInboundPa export { NetworkInterfaceType } from "./NetworkInterfaceType" export { OnionHostname } from "./OnionHostname" export { OsIndex } from "./OsIndex" -export { OsVersionInfo } from "./OsVersionInfo" export { OsVersionInfoMap } from "./OsVersionInfoMap" +export { OsVersionInfo } from "./OsVersionInfo" export { PackageDataEntry } from "./PackageDataEntry" export { PackageDetailLevel } from "./PackageDetailLevel" export { PackageId } from "./PackageId" export { PackageIndex } from "./PackageIndex" -export { PackageInfo } from "./PackageInfo" export { PackageInfoShort } from "./PackageInfoShort" +export { PackageInfo } from "./PackageInfo" export { PackageSignerParams } from "./PackageSignerParams" export { PackageState } from "./PackageState" export { PackageVersionInfo } from "./PackageVersionInfo" @@ -166,7 +167,11 @@ export { Public } from "./Public" export { RecoverySource } from "./RecoverySource" export { RegistryAsset } from "./RegistryAsset" export { RegistryInfo } from "./RegistryInfo" +export { RemoveAdminParams } from "./RemoveAdminParams" +export { RemoveAssetParams } from "./RemoveAssetParams" export { RemoveCategoryParams } from "./RemoveCategoryParams" +export { RemovePackageFromCategoryParams } from "./RemovePackageFromCategoryParams" +export { RemovePackageParams } from "./RemovePackageParams" export { RemoveVersionParams } from "./RemoveVersionParams" export { ReplayId } from "./ReplayId" export { RequestActionParams } from "./RequestActionParams" @@ -176,18 +181,18 @@ export { Security } from "./Security" export { ServerInfo } from "./ServerInfo" export { ServerSpecs } from "./ServerSpecs" export { ServerStatus } from "./ServerStatus" -export { ServiceInterface } from "./ServiceInterface" export { ServiceInterfaceId } from "./ServiceInterfaceId" +export { ServiceInterface } from "./ServiceInterface" export { ServiceInterfaceType } from "./ServiceInterfaceType" -export { Session } from "./Session" export { SessionList } from "./SessionList" export { Sessions } from "./Sessions" +export { Session } from "./Session" export { SetDataVersionParams } from "./SetDataVersionParams" export { SetDependenciesParams } from "./SetDependenciesParams" export { SetHealth } from "./SetHealth" export { SetIconParams } from "./SetIconParams" -export { SetMainStatus } from "./SetMainStatus" export { SetMainStatusStatus } from "./SetMainStatusStatus" +export { SetMainStatus } from "./SetMainStatus" export { SetNameParams } from "./SetNameParams" export { SetStoreParams } from "./SetStoreParams" export { SetupExecuteParams } from "./SetupExecuteParams" @@ -202,7 +207,7 @@ export { TestSmtpParams } from "./TestSmtpParams" export { UnsetInboundParams } from "./UnsetInboundParams" export { UpdatingState } from "./UpdatingState" export { VerifyCifsParams } from "./VerifyCifsParams" -export { Version } from "./Version" export { VersionSignerParams } from "./VersionSignerParams" +export { Version } from "./Version" export { VolumeId } from "./VolumeId" export { WifiInfo } from "./WifiInfo" diff --git a/sdk/package/lib/StartSdk.ts b/sdk/package/lib/StartSdk.ts index 65dcb33f1..ed1f7e722 100644 --- a/sdk/package/lib/StartSdk.ts +++ b/sdk/package/lib/StartSdk.ts @@ -82,7 +82,7 @@ import * as actions from "../../base/lib/actions" import { setupInit } from "./inits/setupInit" import * as fs from "node:fs/promises" -export const OSVersion = testTypeVersion("0.4.0-alpha.0") +export const OSVersion = testTypeVersion("0.4.0-alpha.1") // prettier-ignore type AnyNeverCond = diff --git a/web/config-sample.json b/web/config-sample.json index 9dc0e2bf5..244f533b6 100644 --- a/web/config-sample.json +++ b/web/config-sample.json @@ -5,11 +5,6 @@ "url": "rpc", "version": "v1" }, - "marketplace": { - "start9": "https://registry.start9.com/", - "community": "https://community-registry.start9.com/" - }, - "startosRegistry": "https://registry.start9.com/", "mocks": { "maskAs": "tor", "maskAsHttps": true, diff --git a/web/package-lock.json b/web/package-lock.json index e13fb42bd..f8e01984c 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1,12 +1,12 @@ { "name": "startos-ui", - "version": "0.4.0-alpha.0", + "version": "0.4.0-alpha.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "startos-ui", - "version": "0.4.0-alpha.0", + "version": "0.4.0-alpha.1", "license": "MIT", "dependencies": { "@angular/animations": "^17.3.1", diff --git a/web/package.json b/web/package.json index 1df95e03b..d28baa1d9 100644 --- a/web/package.json +++ b/web/package.json @@ -1,6 +1,6 @@ { "name": "startos-ui", - "version": "0.4.0-alpha.0", + "version": "0.4.0-alpha.1", "author": "Start9 Labs, Inc", "homepage": "https://start9.com/", "license": "MIT", diff --git a/web/patchdb-ui-seed.json b/web/patchdb-ui-seed.json index 6e197c7a4..5ecca58d5 100644 --- a/web/patchdb-ui-seed.json +++ b/web/patchdb-ui-seed.json @@ -1,21 +1,10 @@ { "name": null, - "marketplace": { - "selectedUrl": "https://registry.start9.com/", - "knownHosts": { - "https://registry.start9.com/": { - "name": "Start9 Registry" - }, - "https://community-registry.start9.com/": { - "name": "Community Registry" - } - } + "registries": { + "https://registry.start9.com/": "Start9 Registry", + "https://community-registry.start9.com/": "Community Registry" }, - "gaming": { - "snake": { - "highScore": 0 - } - }, - "ackInstructions": {}, - "theme": "Dark" + "startosRegisrty": "https://registry.start9.com/", + "snakeHighScore": 0, + "ackInstructions": {} } diff --git a/web/projects/marketplace/src/components/menu/menu.component.html b/web/projects/marketplace/src/components/menu/menu.component.html index 47fe4ffd0..2b4831060 100644 --- a/web/projects/marketplace/src/components/menu/menu.component.html +++ b/web/projects/marketplace/src/components/menu/menu.component.html @@ -5,7 +5,6 @@ [style.border-radius.%]="!registry ? 100 : null" size="60px" [url]="registry?.url || ''" - [marketplace]="iconConfig" />

{{ registry?.info?.name || 'Unnamed Registry' }} @@ -27,7 +26,6 @@ [style.height.px]="42" [style.border-radius.%]="100" [url]="registry?.url || ''" - [marketplace]="iconConfig" [tuiSkeleton]="!registry" /> +
{{ registry.name }}
{{ registry.url }}
@@ -24,8 +23,5 @@ import { MarketplaceConfig } from '@start9labs/shared' }) export class MarketplaceRegistryComponent { @Input() - marketplace!: MarketplaceConfig - - @Input() - registry!: { url: string; selected: boolean; name?: string } + registry!: { url: string; selected: boolean; name: string } } diff --git a/web/projects/marketplace/src/components/store-icon/store-icon.component.ts b/web/projects/marketplace/src/components/store-icon/store-icon.component.ts index 963d21ffc..de6436360 100644 --- a/web/projects/marketplace/src/components/store-icon/store-icon.component.ts +++ b/web/projects/marketplace/src/components/store-icon/store-icon.component.ts @@ -1,5 +1,5 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core' -import { MarketplaceConfig, sameUrl } from '@start9labs/shared' +import { knownRegistries, sameUrl } from '@start9labs/shared' @Component({ selector: 'store-icon', @@ -9,13 +9,13 @@ import { MarketplaceConfig, sameUrl } from '@start9labs/shared' [style.border-radius.%]="100" [style.max-width]="size || '100%'" [src]="icon" - alt="Marketplace Icon" + alt="Registry Icon" /> Marketplace Icon `, @@ -27,17 +27,20 @@ export class StoreIconComponent { url = '' @Input() size?: string - @Input({ required: true }) - marketplace!: MarketplaceConfig get icon() { - const { start9, community } = this.marketplace + const { start9Alpha, start9Beta, start9, community } = knownRegistries - if (sameUrl(this.url, start9)) { + if (sameUrl(this.url, start9Alpha)) { + return 'assets/img/icon_alpha.png' + } else if (sameUrl(this.url, start9Beta)) { + return 'assets/img/icon_beta.png' + } else if (sameUrl(this.url, start9)) { return 'assets/img/icon_transparent.png' } else if (sameUrl(this.url, community)) { - return 'assets/img/community-store.png' + return 'assets/img/community-icon.png' + } else { + return 'assets/img/storefront-outline.png' } - return null } } diff --git a/web/projects/marketplace/src/pages/list/categories/categories.component.ts b/web/projects/marketplace/src/pages/list/categories/categories.component.ts index e65621f0b..735da7740 100644 --- a/web/projects/marketplace/src/pages/list/categories/categories.component.ts +++ b/web/projects/marketplace/src/pages/list/categories/categories.component.ts @@ -39,11 +39,11 @@ export class CategoriesComponent { readonly categoryChange = new EventEmitter() readonly fallback: Record = { - a: { name: '', description: { short: 'a', long: 'a' } }, - b: { name: '', description: { short: 'a', long: 'a' } }, - c: { name: '', description: { short: 'a', long: 'a' } }, - d: { name: '', description: { short: 'a', long: 'a' } }, - e: { name: '', description: { short: 'a', long: 'a' } }, + a: { name: '' }, + b: { name: '' }, + c: { name: '' }, + d: { name: '' }, + e: { name: '' }, } switchCategory(category: string): void { diff --git a/web/projects/marketplace/src/types.ts b/web/projects/marketplace/src/types.ts index 6f24fcea3..9c7f375d9 100644 --- a/web/projects/marketplace/src/types.ts +++ b/web/projects/marketplace/src/types.ts @@ -3,7 +3,7 @@ import { T } from '@start9labs/start-sdk' export type GetPackageReq = { id: string - version: string | null + targetVersion: string | null otherVersions: 'short' } export type GetPackageRes = T.GetPackageResponse & { @@ -12,7 +12,7 @@ export type GetPackageRes = T.GetPackageResponse & { export type GetPackagesReq = { id: null - version: null + targetVersion: null otherVersions: 'short' } @@ -22,7 +22,7 @@ export type GetPackagesRes = { export type StoreIdentity = { url: string - name?: string + name: string } export type Marketplace = Record diff --git a/web/projects/shared/assets/img/community-store.png b/web/projects/shared/assets/img/community-icon.png similarity index 100% rename from web/projects/shared/assets/img/community-store.png rename to web/projects/shared/assets/img/community-icon.png diff --git a/web/projects/shared/assets/img/icon_alpha.png b/web/projects/shared/assets/img/icon_alpha.png new file mode 100644 index 000000000..cf8dc42c4 Binary files /dev/null and b/web/projects/shared/assets/img/icon_alpha.png differ diff --git a/web/projects/shared/assets/img/icon_beta.png b/web/projects/shared/assets/img/icon_beta.png new file mode 100644 index 000000000..fa1fa07ac Binary files /dev/null and b/web/projects/shared/assets/img/icon_beta.png differ diff --git a/web/projects/shared/assets/img/storefront-outline.png b/web/projects/shared/assets/img/storefront-outline.png index aa7bd4404..3de3f1c86 100644 Binary files a/web/projects/shared/assets/img/storefront-outline.png and b/web/projects/shared/assets/img/storefront-outline.png differ diff --git a/web/projects/shared/src/types/workspace-config.ts b/web/projects/shared/src/types/workspace-config.ts index eced51db1..cf4cf0578 100644 --- a/web/projects/shared/src/types/workspace-config.ts +++ b/web/projects/shared/src/types/workspace-config.ts @@ -7,11 +7,6 @@ export type WorkspaceConfig = { url: string version: string } - marketplace: MarketplaceConfig - startosRegistry: - | 'https://alpha-registry-x.start9.com/' - | 'https://beta-registry.start9.com/' - | 'https://registry.start9.com/' mocks: { maskAs: 'tor' | 'local' | 'localhost' | 'ipv4' | 'ipv6' | 'clearnet' maskAsHttps: boolean @@ -20,7 +15,13 @@ export type WorkspaceConfig = { } } -export interface MarketplaceConfig { - start9: 'https://registry.start9.com/' - community: 'https://community-registry.start9.com/' -} +export const defaultRegistries = { + start9: 'https://registry.start9.com/', + community: 'https://community-registry.start9.com/', +} as const + +export const knownRegistries = { + ...defaultRegistries, + start9Alpha: 'https://alpha-registry-x.start9.com/', + start9Beta: 'https://beta-registry.start9.com/', +} as const diff --git a/web/projects/ui/src/app/routes/portal/routes/marketplace/components/controls.component.ts b/web/projects/ui/src/app/routes/portal/routes/marketplace/components/controls.component.ts index 0abef3b10..f8b651001 100644 --- a/web/projects/ui/src/app/routes/portal/routes/marketplace/components/controls.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/marketplace/components/controls.component.ts @@ -127,8 +127,8 @@ export class MarketplaceControlsComponent { async tryInstall() { const currentUrl = this.file ? null - : await firstValueFrom(this.marketplaceService.getRegistryUrl$()) - const originalUrl = this.localPkg?.registry || '' + : await firstValueFrom(this.marketplaceService.getCurrentRegistryUrl$()) + const originalUrl = this.localPkg?.registry || null if (!this.localPkg) { if (await this.alerts.alertInstall(this.pkg)) { diff --git a/web/projects/ui/src/app/routes/portal/routes/marketplace/components/menu.component.ts b/web/projects/ui/src/app/routes/portal/routes/marketplace/components/menu.component.ts index 1efb24bc2..ccb3a666b 100644 --- a/web/projects/ui/src/app/routes/portal/routes/marketplace/components/menu.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/marketplace/components/menu.component.ts @@ -2,7 +2,6 @@ import { ChangeDetectionStrategy, Component, inject } from '@angular/core' import { CommonModule } from '@angular/common' import { MenuModule } from '@start9labs/marketplace' import { TuiIcon, TuiButton, TuiAppearance } from '@taiga-ui/core' -import { ConfigService } from 'src/app/services/config.service' import { MARKETPLACE_REGISTRY } from '../modals/registry.component' import { MarketplaceService } from 'src/app/services/marketplace.service' import { DialogService, i18nPipe } from '@start9labs/shared' @@ -11,7 +10,7 @@ import { DialogService, i18nPipe } from '@start9labs/shared' standalone: true, selector: 'marketplace-menu', template: ` - + @@ -53,19 +45,18 @@ import { IST, utils } from '@start9labs/start-sdk'
{{ 'Add custom registry' | i18n }}
- @for (registry of stores.alt; track $index) { + @for (registry of registries.alt; track $index) {
@@ -103,26 +94,28 @@ export class MarketplaceRegistryModal { private readonly marketplaceService = inject(MarketplaceService) private readonly context = injectContext() private readonly router = inject(Router) - private readonly hosts$ = inject>(PatchDB).watch$( + private readonly rawRegistries$ = inject>(PatchDB).watch$( 'ui', - 'marketplace', - 'knownHosts', + 'registries', ) private readonly i18n = inject(i18nPipe) - readonly marketplaceConfig = inject(ConfigService).marketplace + private readonly storage = inject(StorageService) - readonly stores$ = combineLatest([ - this.marketplaceService.getKnownHosts$(), - this.marketplaceService.getRegistryUrl$(), + readonly registries$ = combineLatest([ + this.marketplaceService.getRegistries$(), + this.marketplaceService.getCurrentRegistryUrl$(), ]).pipe( - map(([stores, selectedUrl]) => - stores.map(s => ({ + map(([registries, currentUrl]) => + registries.map(s => ({ ...s, - selected: sameUrl(s.url, selectedUrl), + selected: sameUrl(s.url, currentUrl), })), ), // 0 and 1 are prod and community, 2 and beyond are alts - map(stores => ({ standard: stores.slice(0, 2), alt: stores.slice(2) })), + map(registries => ({ + standard: registries.slice(0, 2), + alt: registries.slice(2), + })), ) add() { @@ -147,7 +140,7 @@ export class MarketplaceRegistryModal { }) } - delete(url: string, name: string = '') { + delete(url: string) { this.dialog .openConfirm({ label: 'Confirm', @@ -161,19 +154,21 @@ export class MarketplaceRegistryModal { .pipe(filter(Boolean)) .subscribe(async () => { const loader = this.loader.open('Deleting').subscribe() - const hosts = await firstValueFrom(this.hosts$) - const filtered: { [url: string]: UIStore } = Object.keys(hosts) + const rawRegistries = await firstValueFrom(this.rawRegistries$) + const filtered: { [url: string]: string | null } = Object.keys( + rawRegistries, + ) .filter(key => !sameUrl(key, url)) .reduce( (prev, curr) => ({ ...prev, - [curr]: hosts[curr], + [curr]: rawRegistries[curr], }), {}, ) try { - await this.api.setDbValue(['marketplace', 'knownHosts'], filtered) + await this.api.setDbValue(['registries'], filtered) } catch (e: any) { this.errorService.handleError(e) } finally { @@ -195,7 +190,7 @@ export class MarketplaceRegistryModal { queryParams: { registry: url }, queryParamsHandling: 'merge', }) - this.api.setDbValue(['marketplace', 'selectedUrl'], url) + this.storage.set('selectedRegistry', url) this.context.$implicit.complete() } catch (e: any) { this.errorService.handleError(e) @@ -255,8 +250,8 @@ export class MarketplaceRegistryModal { loader: Subscription, ): Promise { // Error on duplicates - const hosts = await firstValueFrom(this.hosts$) - const currentUrls = Object.keys(hosts).map(toUrl) + const rawRegistries = await firstValueFrom(this.rawRegistries$) + const currentUrls = Object.keys(rawRegistries).map(toUrl) if (currentUrls.includes(url)) throw new Error(this.i18n.transform('Registry already added')) @@ -274,7 +269,7 @@ export class MarketplaceRegistryModal { loader.closed = false loader.add(this.loader.open('Saving').subscribe()) - await this.api.setDbValue(['marketplace', 'knownHosts', url], { name }) + await this.api.setDbValue(['altRegistries', url], name) } } diff --git a/web/projects/ui/src/app/routes/portal/routes/marketplace/services/alerts.service.ts b/web/projects/ui/src/app/routes/portal/routes/marketplace/services/alerts.service.ts index a108442c2..3f4f8a14e 100644 --- a/web/projects/ui/src/app/routes/portal/routes/marketplace/services/alerts.service.ts +++ b/web/projects/ui/src/app/routes/portal/routes/marketplace/services/alerts.service.ts @@ -1,29 +1,30 @@ import { inject, Injectable } from '@angular/core' import { MarketplacePkgBase } from '@start9labs/marketplace' -import { PatchDB } from 'patch-db-client' import { defaultIfEmpty, firstValueFrom } from 'rxjs' -import { DataModel } from 'src/app/services/patch-db/data-model' import { DialogService, i18nKey, i18nPipe } from '@start9labs/shared' +import { MarketplaceService } from 'src/app/services/marketplace.service' @Injectable({ providedIn: 'root', }) export class MarketplaceAlertsService { private readonly dialog = inject(DialogService) - private readonly marketplace$ = inject>(PatchDB).watch$( - 'ui', - 'marketplace', - ) + private readonly marketplaceService = inject(MarketplaceService) private readonly i18n = inject(i18nPipe) - async alertMarketplace(url: string, originalUrl: string): Promise { - const marketplaces = await firstValueFrom(this.marketplace$) - const name = marketplaces.knownHosts[url]?.name || url - const source = marketplaces.knownHosts[originalUrl]?.name || originalUrl - const message = source - ? `${this.i18n.transform('installed from')} ${source}` + async alertMarketplace( + url: string, + originalUrl: string | null, + ): Promise { + const registries = await firstValueFrom( + this.marketplaceService.getRegistries$(), + ) + const message = originalUrl + ? `${this.i18n.transform('installed from')} ${registries.find(h => h.url === originalUrl) || originalUrl}` : this.i18n.transform('sideloaded') + const currentName = registries.find(h => h.url === url) || url + return new Promise(async resolve => { this.dialog .openConfirm({ @@ -31,7 +32,7 @@ export class MarketplaceAlertsService { size: 's', data: { content: - `${this.i18n.transform('This service was originally')} ${message}, ${this.i18n.transform('but you are currently connected to')} ${name}. ${this.i18n.transform('To install from')} ${name} ${this.i18n.transform('anyway, click "Continue".')}` as i18nKey, + `${this.i18n.transform('This service was originally')} ${message}, ${this.i18n.transform('but you are currently connected to')} ${currentName}. ${this.i18n.transform('To install from')} ${currentName} ${this.i18n.transform('anyway, click "Continue".')}` as i18nKey, yes: 'Continue', no: 'Cancel', }, diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/acme/acme.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/acme/acme.component.ts index 905bad0fb..c295ac9c7 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/acme/acme.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/acme/acme.component.ts @@ -205,7 +205,6 @@ export default class SystemAcmeComponent { } private async saveAcme(providerUrl: string, contact: string[]) { - console.log(providerUrl, contact) const loader = this.loader.open('Saving').subscribe() try { diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/general/general.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/general/general.component.ts index 7bb817ad4..d78a0e8da 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/general/general.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/general/general.component.ts @@ -242,10 +242,9 @@ export default class SystemGeneralComponent { readonly translation: TuiStringHandler> = ({ $implicit, }) => this.i18n.transform($implicit)! - readonly score = toSignal( - this.patch.watch$('ui', 'gaming', 'snake', 'highScore'), - { initialValue: 0 }, - ) + readonly score = toSignal(this.patch.watch$('ui', 'snakeHighScore'), { + initialValue: 0, + }) get language(): Languages | undefined { return this.languages.find(lang => lang === this.i18nService.language) diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/general/snek.directive.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/general/snek.directive.ts index 120970a80..3a02c16cb 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/general/snek.directive.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/general/snek.directive.ts @@ -37,10 +37,7 @@ export class SnekDirective { const loader = this.loader.open('Saving high score').subscribe() try { - await this.api.setDbValue( - ['gaming', 'snake', 'highScore'], - score, - ) + await this.api.setDbValue(['snakeHighScore'], score) } catch (e: any) { this.errorService.handleError(e) } finally { diff --git a/web/projects/ui/src/app/routes/portal/routes/system/routes/general/update.component.ts b/web/projects/ui/src/app/routes/portal/routes/system/routes/general/update.component.ts index 719ff0969..3226b6e28 100644 --- a/web/projects/ui/src/app/routes/portal/routes/system/routes/general/update.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/system/routes/general/update.component.ts @@ -16,6 +16,9 @@ import { TuiDialogContext, TuiScrollbar, TuiButton } from '@taiga-ui/core' import { NgDompurifyModule } from '@tinkoff/ng-dompurify' import { ApiService } from 'src/app/services/api/embassy-api.service' import { OSService } from 'src/app/services/os.service' +import { PatchDB } from 'patch-db-client' +import { DataModel } from 'src/app/services/patch-db/data-model' +import { firstValueFrom } from 'rxjs' @Component({ template: ` @@ -61,13 +64,19 @@ export class SystemUpdateModal { private readonly errorService: ErrorService, private readonly embassyApi: ApiService, private readonly os: OSService, + private readonly patch: PatchDB, ) {} async update() { const loader = this.loader.open('Beginning update').subscribe() + const { startosRegistry } = await firstValueFrom(this.patch.watch$('ui')) + try { - await this.embassyApi.updateServer() + await this.embassyApi.updateServer({ + targetVersion: `=${this.versions[0]!.version}`, + registry: startosRegistry, + }) this.context.$implicit.complete() } catch (e: any) { this.errorService.handleError(e) diff --git a/web/projects/ui/src/app/routes/portal/routes/updates/updates.component.ts b/web/projects/ui/src/app/routes/portal/routes/updates/updates.component.ts index 5a3f513de..33ea5f289 100644 --- a/web/projects/ui/src/app/routes/portal/routes/updates/updates.component.ts +++ b/web/projects/ui/src/app/routes/portal/routes/updates/updates.component.ts @@ -22,7 +22,6 @@ import { TuiCell } from '@taiga-ui/layout' import { PatchDB } from 'patch-db-client' import { combineLatest, map, tap } from 'rxjs' import { TableComponent } from 'src/app/routes/portal/components/table.component' -import { ConfigService } from 'src/app/services/config.service' import { MarketplaceService } from 'src/app/services/marketplace.service' import { DataModel, @@ -66,7 +65,7 @@ interface UpdatesData { (click)="current.set(registry)" > - + {{ registry.name }} @@ -221,15 +220,17 @@ export default class UpdatesComponent { private readonly isMobile = inject(TUI_IS_MOBILE) private readonly marketplaceService = inject(MarketplaceService) - readonly mp = inject(ConfigService).marketplace readonly current = signal(null) readonly data = toSignal( combineLatest({ hosts: this.marketplaceService - .getKnownHosts$(true) + .getRegistries$(true) .pipe( - tap(([store]) => !this.isMobile && store && this.current.set(store)), + tap( + ([registry]) => + !this.isMobile && registry && this.current.set(registry), + ), ), marketplace: this.marketplaceService.marketplace$, localPkgs: inject>(PatchDB) diff --git a/web/projects/ui/src/app/services/api/api.fixures.ts b/web/projects/ui/src/app/services/api/api.fixures.ts index e77dab33b..fac8d0be6 100644 --- a/web/projects/ui/src/app/services/api/api.fixures.ts +++ b/web/projects/ui/src/app/services/api/api.fixures.ts @@ -30,7 +30,7 @@ export namespace Mock { shuttingDown: false, } - export const RegistryOSUpdate: RR.GetRegistryOsUpdateRes = { + export const RegistryOSUpdate: RR.CheckOsUpdateRes = { '0.3.6-alpha.17': { headline: 'v0.3.6-alpha.17', releaseNotes: '', @@ -101,16 +101,16 @@ export namespace Mock { }, img: {}, }, - '0.4.0-alpha.0': { - headline: 'v0.4.0-alpha.0', + '0.4.0-alpha.1': { + headline: 'v0.4.0-alpha.1', releaseNotes: '', - sourceVersion: '>=0.3.5:0 <=0.4.0-alpha.0:0', + sourceVersion: '>=0.3.5:0 <=0.4.0-alpha.1:0', authorized: ['G24CSA5HNYEPIXJNMK7ZM4KD5SX5N6X4'], iso: {}, squashfs: { aarch64: { publishedAt: '2025-04-21T20:58:48.140749883Z', - url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.0/startos-0.4.0-alpha.0-33ae46f~dev_aarch64.squashfs', + url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.1/startos-0.4.0-alpha.1-33ae46f~dev_aarch64.squashfs', commitment: { hash: '4elBFVkd/r8hNadKmKtLIs42CoPltMvKe2z3LRqkphk=', size: 1343500288, @@ -122,7 +122,7 @@ export namespace Mock { }, 'aarch64-nonfree': { publishedAt: '2025-04-21T21:07:00.249285116Z', - url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.0/startos-0.4.0-alpha.0-33ae46f~dev_aarch64-nonfree.squashfs', + url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.1/startos-0.4.0-alpha.1-33ae46f~dev_aarch64-nonfree.squashfs', commitment: { hash: 'MrCEi4jxbmPS7zAiGk/JSKlMsiuKqQy6RbYOxlGHOIQ=', size: 1653075968, @@ -134,7 +134,7 @@ export namespace Mock { }, raspberrypi: { publishedAt: '2025-04-21T21:16:12.933319237Z', - url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.0/startos-0.4.0-alpha.0-33ae46f~dev_raspberrypi.squashfs', + url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.1/startos-0.4.0-alpha.1-33ae46f~dev_raspberrypi.squashfs', commitment: { hash: '/XTVQRCqY3RK544PgitlKu7UplXjkmzWoXUh2E4HCw0=', size: 1490731008, @@ -146,7 +146,7 @@ export namespace Mock { }, x86_64: { publishedAt: '2025-04-21T21:14:20.246908903Z', - url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.0/startos-0.4.0-alpha.0-33ae46f~dev_x86_64.squashfs', + url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.1/startos-0.4.0-alpha.1-33ae46f~dev_x86_64.squashfs', commitment: { hash: '/6romKTVQGSaOU7FqSZdw0kFyd7P+NBSYNwM3q7Fe44=', size: 1411657728, @@ -158,7 +158,7 @@ export namespace Mock { }, 'x86_64-nonfree': { publishedAt: '2025-04-21T21:15:17.955265284Z', - url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.0/startos-0.4.0-alpha.0-33ae46f~dev_x86_64-nonfree.squashfs', + url: 'https://alpha-registry-x.start9.com/startos/v0.4.0-alpha.1/startos-0.4.0-alpha.1-33ae46f~dev_x86_64-nonfree.squashfs', commitment: { hash: 'HCRq9sr/0t85pMdrEgNBeM4x11zVKHszGnD1GDyZbSE=', size: 1731035136, @@ -179,27 +179,21 @@ export namespace Mock { categories: { bitcoin: { name: 'Bitcoin', - description: mockDescription, }, featured: { name: 'Featured', - description: mockDescription, }, lightning: { name: 'Lightning', - description: mockDescription, }, communications: { name: 'Communications', - description: mockDescription, }, data: { name: 'Data', - description: mockDescription, }, ai: { name: 'AI', - description: mockDescription, }, }, } diff --git a/web/projects/ui/src/app/services/api/api.types.ts b/web/projects/ui/src/app/services/api/api.types.ts index 8c696b1f4..2cda22b7c 100644 --- a/web/projects/ui/src/app/services/api/api.types.ts +++ b/web/projects/ui/src/app/services/api/api.types.ts @@ -3,6 +3,12 @@ import { DataModel } from 'src/app/services/patch-db/data-model' import { StartOSDiskInfo, FetchLogsReq, FetchLogsRes } from '@start9labs/shared' import { IST, T } from '@start9labs/start-sdk' import { WebSocketSubjectConfig } from 'rxjs/webSocket' +import { + GetPackageReq, + GetPackageRes, + GetPackagesReq, + GetPackagesRes, +} from '@start9labs/marketplace' export namespace RR { // websocket @@ -86,7 +92,7 @@ export namespace RR { metrics: ServerMetrics } - export type UpdateServerReq = { registry: string } // server.update + export type UpdateServerReq = { registry: string; targetVersion: string } // server.update export type UpdateServerRes = 'updating' | 'no-updates' export type RestartServerReq = {} // server.restart @@ -362,8 +368,17 @@ export namespace RR { // registry /** these are returned in ASCENDING order. the newest available version will be the LAST in the object */ - export type CheckOSUpdateReq = { serverId: string } - export type GetRegistryOsUpdateRes = { [version: string]: T.OsVersionInfo } + export type CheckOsUpdateReq = { registry: string; serverId: string } + export type CheckOsUpdateRes = { [version: string]: T.OsVersionInfo } + + export type GetRegistryInfoReq = { registry: string } + export type GetRegistryInfoRes = T.RegistryInfo + + export type GetRegistryPackageReq = GetPackageReq & { registry: string } + export type GetRegistryPackageRes = GetPackageRes + + export type GetRegistryPackagesReq = GetPackagesReq & { registry: string } + export type GetRegistryPackagesRes = GetPackagesRes } export type Breakages = { diff --git a/web/projects/ui/src/app/services/api/embassy-api.service.ts b/web/projects/ui/src/app/services/api/embassy-api.service.ts index 2aec7d40d..fe309ac65 100644 --- a/web/projects/ui/src/app/services/api/embassy-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-api.service.ts @@ -1,9 +1,4 @@ -import { - GetPackageRes, - GetPackagesRes, - MarketplacePkg, -} from '@start9labs/marketplace' -import { RPCOptions } from '@start9labs/shared' +import { MarketplacePkg } from '@start9labs/marketplace' import { T } from '@start9labs/start-sdk' import { RR } from './api.types' import { WebSocketSubject } from 'rxjs/webSocket' @@ -113,7 +108,7 @@ export abstract class ApiService { params: RR.FollowServerMetricsReq, ): Promise - abstract updateServer(url?: string): Promise + abstract updateServer(params: RR.UpdateServerReq): Promise abstract restartServer( params: RR.RestartServerReq, @@ -145,24 +140,21 @@ export abstract class ApiService { // marketplace URLs - abstract registryRequest( - registryUrl: string, - options: RPCOptions, - ): Promise - abstract checkOSUpdate( - qp: RR.CheckOSUpdateReq, - ): Promise + params: RR.CheckOsUpdateReq, + ): Promise - abstract getRegistryInfo(registryUrl: string): Promise + abstract getRegistryInfo( + params: RR.GetRegistryInfoReq, + ): Promise abstract getRegistryPackage( - url: string, - id: string, - versionRange: string | null, - ): Promise + params: RR.GetRegistryPackageReq, + ): Promise - abstract getRegistryPackages(registryUrl: string): Promise + abstract getRegistryPackages( + params: RR.GetRegistryPackagesReq, + ): Promise // notification diff --git a/web/projects/ui/src/app/services/api/embassy-live-api.service.ts b/web/projects/ui/src/app/services/api/embassy-live-api.service.ts index 2fc56270c..88e8e428c 100644 --- a/web/projects/ui/src/app/services/api/embassy-live-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-live-api.service.ts @@ -10,7 +10,6 @@ import { import { PATCH_CACHE } from 'src/app/services/patch-db/patch-db-source' import { ApiService } from './embassy-api.service' import { RR } from './api.types' -import { ConfigService } from '../config.service' import { webSocket, WebSocketSubject } from 'rxjs/webSocket' import { Observable, filter, firstValueFrom } from 'rxjs' import { AuthService } from '../auth.service' @@ -18,13 +17,7 @@ import { DOCUMENT } from '@angular/common' import { DataModel } from '../patch-db/data-model' import { Dump, pathFromArray } from 'patch-db-client' import { T } from '@start9labs/start-sdk' -import { - GetPackageReq, - GetPackageRes, - GetPackagesReq, - GetPackagesRes, - MarketplacePkg, -} from '@start9labs/marketplace' +import { MarketplacePkg } from '@start9labs/marketplace' import { blake3 } from '@noble/hashes/blake3' @Injectable() @@ -32,7 +25,6 @@ export class LiveApiService extends ApiService { constructor( @Inject(DOCUMENT) private readonly document: Document, private readonly http: HttpService, - private readonly config: ConfigService, private readonly auth: AuthService, @Inject(PATCH_CACHE) private readonly cache$: Observable>, ) { @@ -248,10 +240,7 @@ export class LiveApiService extends ApiService { return this.rpcRequest({ method: 'server.metrics.follow', params }) } - async updateServer(url?: string): Promise { - const params = { - registry: url || this.config.startosRegistry, - } + async updateServer(params: RR.UpdateServerReq): Promise { return this.rpcRequest({ method: 'server.update', params }) } @@ -283,61 +272,38 @@ export class LiveApiService extends ApiService { // marketplace URLs - async registryRequest( - registryUrl: string, - options: RPCOptions, - ): Promise { - return this.rpcRequest({ - ...options, - method: `registry.${options.method}`, - params: { registry: registryUrl, ...options.params }, - }) - } - async checkOSUpdate( - qp: RR.CheckOSUpdateReq, - ): Promise { - const { serverId } = qp - - return this.registryRequest(this.config.startosRegistry, { - method: 'os.version.get', - params: { serverId }, - }) - } - - async getRegistryInfo(registryUrl: string): Promise { - return this.registryRequest(registryUrl, { - method: 'info', - params: {}, - }) - } - - async getRegistryPackage( - registryUrl: string, - id: string, - versionRange: string | null, - ): Promise { - const params: GetPackageReq = { - id, - version: versionRange, - otherVersions: 'short', - } - - return this.registryRequest(registryUrl, { - method: 'package.get', + params: RR.CheckOsUpdateReq, + ): Promise { + return this.rpcRequest({ + method: 'registry.os.version.get', params, }) } - async getRegistryPackages(registryUrl: string): Promise { - const params: GetPackagesReq = { - id: null, - version: null, - otherVersions: 'short', - } + async getRegistryInfo( + params: RR.GetRegistryInfoReq, + ): Promise { + return this.rpcRequest({ + method: 'registry.info', + params, + }) + } - return this.registryRequest(registryUrl, { - method: 'package.get', + async getRegistryPackage( + params: RR.GetRegistryPackageReq, + ): Promise { + return this.rpcRequest({ + method: 'registry.package.get', + params, + }) + } + + async getRegistryPackages( + params: RR.GetRegistryPackagesReq, + ): Promise { + return this.rpcRequest({ + method: 'registry.package.get', params, }) } diff --git a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts index 458cd309e..9014c48d5 100644 --- a/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts +++ b/web/projects/ui/src/app/services/api/embassy-mock-api.service.ts @@ -169,6 +169,7 @@ export class MockApiService extends ApiService { pathArr: Array, value: T, ): Promise { + console.warn(pathArr, value) const pointer = pathFromArray(pathArr) const params: RR.SetDBValueReq = { pointer, value } await pauseFor(2000) @@ -367,7 +368,7 @@ export class MockApiService extends ApiService { } } - async updateServer(url?: string): Promise { + async updateServer(params?: RR.UpdateServerReq): Promise { await pauseFor(2000) const initialProgress = { size: null, @@ -475,41 +476,37 @@ export class MockApiService extends ApiService { // marketplace URLs - async registryRequest( - registryUrl: string, - options: RPCOptions, - ): Promise { - await pauseFor(2000) - - return Error('do not call directly') - } - async checkOSUpdate( - qp: RR.CheckOSUpdateReq, - ): Promise { + params: RR.CheckOsUpdateReq, + ): Promise { await pauseFor(2000) return Mock.RegistryOSUpdate } - async getRegistryInfo(registryUrl: string): Promise { + async getRegistryInfo( + params: RR.GetRegistryInfoReq, + ): Promise { await pauseFor(2000) return Mock.RegistryInfo } async getRegistryPackage( - url: string, - id: string, - versionRange: string, - ): Promise { + params: RR.GetRegistryPackageReq, + ): Promise { await pauseFor(2000) - if (!versionRange || versionRange === '=*') { + + const { targetVersion, id } = params + + if (!targetVersion || targetVersion === '=*') { return Mock.RegistryPackages[id]! } else { - return Mock.OtherPackageVersions[id]![versionRange]! + return Mock.OtherPackageVersions[id]![targetVersion]! } } - async getRegistryPackages(registryUrl: string): Promise { + async getRegistryPackages( + params: RR.GetRegistryPackagesReq, + ): Promise { await pauseFor(2000) return Mock.RegistryPackages } diff --git a/web/projects/ui/src/app/services/api/mock-patch.ts b/web/projects/ui/src/app/services/api/mock-patch.ts index 005eb62b7..3e3fe7beb 100644 --- a/web/projects/ui/src/app/services/api/mock-patch.ts +++ b/web/projects/ui/src/app/services/api/mock-patch.ts @@ -6,26 +6,14 @@ const version = require('../../../../../../package.json').version export const mockPatchData: DataModel = { ui: { name: `Matt's Server`, - theme: 'Dark', - marketplace: { - selectedUrl: 'https://registry.start9.com/', - knownHosts: { - 'https://registry.start9.com/': { - name: 'Start9 Registry', - }, - 'https://community-registry.start9.com/': {}, - 'https://beta-registry.start9.com/': { - name: 'Dark9', - }, - }, + registries: { + 'https://registry.start9.com/': 'Start9 Registry', + 'https://community-registry.start9.com/': 'Community Registry', }, - gaming: { - snake: { - highScore: 0, - }, - }, - language: 'english', + startosRegistry: 'https://registry.start9.com/', + snakeHighScore: 0, ackInstructions: {}, + language: 'english', }, serverInfo: { arch: 'x86_64', diff --git a/web/projects/ui/src/app/services/config.service.ts b/web/projects/ui/src/app/services/config.service.ts index 2ed5b8eaf..e11bf78f1 100644 --- a/web/projects/ui/src/app/services/config.service.ts +++ b/web/projects/ui/src/app/services/config.service.ts @@ -7,7 +7,7 @@ import { PackageDataEntry } from './patch-db/data-model' const { gitHash, useMocks, - ui: { api, marketplace, mocks, startosRegistry }, + ui: { api, mocks }, } = require('../../../../../config.json') as WorkspaceConfig @Injectable({ @@ -26,8 +26,6 @@ export class ConfigService { mocks = mocks gitHash = gitHash api = api - marketplace = marketplace - startosRegistry = startosRegistry skipStartupAlerts = useMocks && mocks.skipStartupAlerts supportsWebSockets = !!window.WebSocket diff --git a/web/projects/ui/src/app/services/marketplace.service.ts b/web/projects/ui/src/app/services/marketplace.service.ts index b879c1ae8..6fcc25f5b 100644 --- a/web/projects/ui/src/app/services/marketplace.service.ts +++ b/web/projects/ui/src/app/services/marketplace.service.ts @@ -7,7 +7,7 @@ import { StoreDataWithUrl, StoreIdentity, } from '@start9labs/marketplace' -import { Exver, sameUrl } from '@start9labs/shared' +import { Exver, defaultRegistries, sameUrl } from '@start9labs/shared' import { T } from '@start9labs/start-sdk' import { PatchDB } from 'patch-db-client' import { @@ -31,32 +31,28 @@ import { } from 'rxjs' import { RR } from 'src/app/services/api/api.types' import { ApiService } from 'src/app/services/api/embassy-api.service' -import { DataModel, UIStore } from 'src/app/services/patch-db/data-model' +import { DataModel } from 'src/app/services/patch-db/data-model' import { ClientStorageService } from './client-storage.service' -import { ConfigService } from './config.service' + +const { start9, community } = defaultRegistries @Injectable({ providedIn: 'root', }) export class MarketplaceService { - private readonly registryUrlSubject$ = new ReplaySubject(1) - private readonly registryUrl$ = this.registryUrlSubject$.pipe( + private readonly currentRegistryUrlSubject$ = new ReplaySubject(1) + private readonly currentRegistryUrl$ = this.currentRegistryUrlSubject$.pipe( distinctUntilChanged(), ) - private readonly registry$: Observable = - this.registryUrl$.pipe( + private readonly currentRegistry$: Observable = + this.currentRegistryUrl$.pipe( switchMap(url => this.fetchRegistry$(url)), filter(Boolean), map(registry => { - // @TODO Aiden let's drop description. We do not use it. categories should just be Record registry.info.categories = { all: { name: 'All', - description: { - short: 'All registry packages', - long: 'An unfiltered list of all packages available on this registry.', - }, }, ...registry.info.categories, } @@ -66,36 +62,27 @@ export class MarketplaceService { shareReplay(1), ) - private readonly knownHosts$: Observable = this.patch - .watch$('ui', 'marketplace', 'knownHosts') + private readonly registries$: Observable = this.patch + .watch$('ui', 'registries') .pipe( - map(hosts => { - const { start9, community } = this.config.marketplace - let arr = [ - toStoreIdentity(start9, hosts[start9]), - toStoreIdentity(community, { - ...hosts[community], - name: 'Community Registry', - }), - ] - - return arr.concat( - Object.entries(hosts) - .filter(([url, _]) => ![start9, community].includes(url as any)) - .map(([url, store]) => toStoreIdentity(url, store)), - ) - }), + map(registries => [ + toStoreIdentity(start9, registries[start9]), + toStoreIdentity(community, registries[community]), + ...Object.entries(registries) + .filter(([url, _]) => ![start9, community].includes(url as any)) + .map(([url, name]) => toStoreIdentity(url, name)), + ]), ) - private readonly filteredKnownHosts$: Observable = + private readonly filteredRegistries$: Observable = combineLatest([ this.clientStorageService.showDevTools$, - this.knownHosts$, + this.registries$, ]).pipe( - map(([devMode, knownHosts]) => + map(([devMode, registries]) => devMode - ? knownHosts - : knownHosts.filter( + ? registries + : registries.filter( ({ url }) => !url.includes('alpha') && !url.includes('beta'), ), ), @@ -103,7 +90,7 @@ export class MarketplaceService { private readonly requestErrors$ = new BehaviorSubject([]) - readonly marketplace$: Observable = this.knownHosts$.pipe( + readonly marketplace$: Observable = this.registries$.pipe( startWith([]), pairwise(), mergeMap(([prev, curr]) => @@ -112,7 +99,8 @@ export class MarketplaceService { mergeMap(({ url, name }) => this.fetchRegistry$(url).pipe( tap(data => { - if (data?.info.name) this.updateStoreName(url, name, data.info.name) + if (data?.info.name) + this.updateRegistryName(url, name, data.info.name) }), map(data => [url, data]), startWith<[string, StoreData | null]>([url, null]), @@ -132,27 +120,25 @@ export class MarketplaceService { constructor( private readonly api: ApiService, private readonly patch: PatchDB, - private readonly config: ConfigService, private readonly clientStorageService: ClientStorageService, private readonly exver: Exver, ) {} - getKnownHosts$(filtered = false): Observable { + getRegistries$(filtered = false): Observable { // option to filter out hosts containing 'alpha' or 'beta' substrings in registryURL - return filtered ? this.filteredKnownHosts$ : this.knownHosts$ + return filtered ? this.filteredRegistries$ : this.registries$ } - getRegistryUrl$() { - return this.registryUrl$ + getCurrentRegistryUrl$() { + return this.currentRegistryUrl$ } - setRegistryUrl(url: string | null) { - const registryUrl = url || this.config.marketplace.start9 - this.registryUrlSubject$.next(registryUrl) + setRegistryUrl(url: string) { + this.currentRegistryUrlSubject$.next(url) } - getRegistry$(): Observable { - return this.registry$ + getCurrentRegistry$(): Observable { + return this.currentRegistry$ } getPackage$( @@ -161,7 +147,7 @@ export class MarketplaceService { flavor: string | null, registryUrl?: string, ): Observable { - return this.registry$.pipe( + return this.currentRegistry$.pipe( switchMap(registry => { const url = registryUrl || registry.url const pkg = registry.packages.find( @@ -176,17 +162,12 @@ export class MarketplaceService { } fetchInfo$(url: string): Observable { - return from(this.api.getRegistryInfo(url)).pipe( + return from(this.api.getRegistryInfo({ registry: url })).pipe( map(info => ({ ...info, - // @TODO Aiden let's drop description. We do not use it. categories should just be Record categories: { all: { name: 'All', - description: { - short: '', - long: '', - }, }, ...info.categories, }, @@ -202,7 +183,7 @@ export class MarketplaceService { } private fetchRegistry$(url: string): Observable { - console.log('FETCHING REGISTRY: ', url) + console.warn('FETCHING REGISTRY: ', url) return combineLatest([this.fetchInfo$(url), this.fetchPackages$(url)]).pipe( map(([info, packages]) => ({ info, packages, url })), catchError(e => { @@ -214,7 +195,14 @@ export class MarketplaceService { } private fetchPackages$(url: string): Observable { - return from(this.api.getRegistryPackages(url)).pipe( + return from( + this.api.getRegistryPackages({ + registry: url, + id: null, + targetVersion: null, + otherVersions: 'short', + }), + ).pipe( map(packages => { return Object.entries(packages).flatMap(([id, pkgInfo]) => Object.keys(pkgInfo.best).map(version => @@ -237,7 +225,12 @@ export class MarketplaceService { flavor: string | null, ): Observable { return from( - this.api.getRegistryPackage(url, id, version ? `=${version}` : null), + this.api.getRegistryPackage({ + registry: url, + id, + targetVersion: version ? `=${version}` : null, + otherVersions: 'short', + }), ).pipe( map(pkgInfo => this.convertRegistryPkgToMarketplacePkg(id, version, flavor, pkgInfo), @@ -288,23 +281,21 @@ export class MarketplaceService { await this.api.installPackage(params) } - private async updateStoreName( + private async updateRegistryName( url: string, - oldName: string | undefined, + oldName: string | null, newName: string, ): Promise { + console.warn(oldName, newName) if (oldName !== newName) { - this.api.setDbValue( - ['marketplace', 'knownHosts', url, 'name'], - newName, - ) + this.api.setDbValue(['registries', url], newName) } } } -function toStoreIdentity(url: string, uiStore: UIStore): StoreIdentity { +function toStoreIdentity(url: string, name?: string | null): StoreIdentity { return { url, - ...uiStore, + name: name || url, } } diff --git a/web/projects/ui/src/app/services/os.service.ts b/web/projects/ui/src/app/services/os.service.ts index f88d558e9..aea7ac21c 100644 --- a/web/projects/ui/src/app/services/os.service.ts +++ b/web/projects/ui/src/app/services/os.service.ts @@ -1,6 +1,12 @@ import { Injectable } from '@angular/core' import { PatchDB } from 'patch-db-client' -import { BehaviorSubject, distinctUntilChanged, map, combineLatest } from 'rxjs' +import { + BehaviorSubject, + distinctUntilChanged, + map, + combineLatest, + firstValueFrom, +} from 'rxjs' import { ApiService } from 'src/app/services/api/embassy-api.service' import { getServerInfo } from 'src/app/utils/get-server-info' import { DataModel } from './patch-db/data-model' @@ -11,7 +17,7 @@ import { RR } from './api/api.types' providedIn: 'root', }) export class OSService { - osUpdate?: RR.GetRegistryOsUpdateRes + osUpdate?: RR.CheckOsUpdateRes updateAvailable$ = new BehaviorSubject(false) readonly updating$ = this.patch.watch$('serverInfo', 'statusInfo').pipe( @@ -47,7 +53,12 @@ export class OSService { async loadOS(): Promise { const { version, id } = await getServerInfo(this.patch) - this.osUpdate = await this.api.checkOSUpdate({ serverId: id }) + const { startosRegistry } = await firstValueFrom(this.patch.watch$('ui')) + + this.osUpdate = await this.api.checkOSUpdate({ + registry: startosRegistry, + serverId: id, + }) const [latestVersion, _] = Object.entries(this.osUpdate).at(-1)! const updateAvailable = Version.parse(latestVersion).compare(Version.parse(version)) === 'greater' diff --git a/web/projects/ui/src/app/services/patch-db/data-model.ts b/web/projects/ui/src/app/services/patch-db/data-model.ts index 5c8731167..7b50028f3 100644 --- a/web/projects/ui/src/app/services/patch-db/data-model.ts +++ b/web/projects/ui/src/app/services/patch-db/data-model.ts @@ -5,30 +5,13 @@ export type DataModel = T.Public & { ui: UIData; packageData: AllPackageData } export type UIData = { name: string | null - marketplace: UIMarketplaceData - gaming: { - snake: { - highScore: number - } - } + registries: Record ackInstructions: Record - theme: string + snakeHighScore: number + startosRegistry: string language: Languages } -export type UIMarketplaceData = { - selectedUrl: string - knownHosts: { - 'https://registry.start9.com/': UIStore - 'https://community-registry.start9.com/': UIStore - [url: string]: UIStore - } -} - -export type UIStore = { - name?: string -} - export type NetworkInfo = T.NetworkInfo & { // @TODO 041 // start9To: {