mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
update marketplace url to reflect build version (#2914)
* update marketplace url to reflect build version * adjust marketplace config * use helper function to compare urls * rework some registry stuff * #2900, #2899, and other registry changes * alpha.1 * trailing / * add startosRegistry * fix migration --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> Co-authored-by: Aiden McClelland <me@drbonez.dev> Co-authored-by: Aiden McClelland <3732071+dr-bonez@users.noreply.github.com>
This commit is contained in:
14
Makefile
14
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
|
||||
|
||||
2
core/Cargo.lock
generated
2
core/Cargo.lock
generated
@@ -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",
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -22,16 +22,30 @@ pub fn admin_api<C: Context>() -> ParentHandler<C> {
|
||||
"signer",
|
||||
signers_api::<C>().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::<CliContext>(),
|
||||
)
|
||||
.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")]
|
||||
|
||||
@@ -51,6 +51,28 @@ pub fn add_api<C: Context>() -> ParentHandler<C> {
|
||||
)
|
||||
}
|
||||
|
||||
pub fn remove_api<C: Context>() -> ParentHandler<C> {
|
||||
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<OsVersionInfo>,
|
||||
) -> &mut Model<BTreeMap<InternedString, RegistryAsset<Blake3Commitment>>>
|
||||
+ 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
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ pub fn asset_api<C: Context>() -> ParentHandler<C> {
|
||||
.no_display()
|
||||
.with_about("Add asset to registry"),
|
||||
)
|
||||
.subcommand("remove", add::remove_api::<C>())
|
||||
.subcommand("sign", sign::sign_api::<C>())
|
||||
.subcommand(
|
||||
"sign",
|
||||
@@ -20,6 +21,7 @@ pub fn asset_api<C: Context>() -> ParentHandler<C> {
|
||||
.no_display()
|
||||
.with_about("Sign file and add to registry index"),
|
||||
)
|
||||
// TODO: remove signature api
|
||||
.subcommand(
|
||||
"get",
|
||||
get::get_api::<C>().with_about("Commands to download image, iso, or squashfs files"),
|
||||
|
||||
@@ -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<C: Context>() -> ParentHandler<C> {
|
||||
.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<Version>,
|
||||
#[ts(type = "string | null")]
|
||||
#[arg(long = "target")]
|
||||
pub target: Option<VersionRange>,
|
||||
pub source_version: Option<Version>,
|
||||
#[ts(type = "string | null")]
|
||||
#[arg(long)]
|
||||
pub target_version: Option<VersionRange>,
|
||||
#[arg(long)]
|
||||
pub include_prerelease: Option<bool>,
|
||||
#[arg(long = "id")]
|
||||
server_id: Option<String>,
|
||||
#[ts(type = "string | null")]
|
||||
#[arg(long = "arch")]
|
||||
arch: Option<String>,
|
||||
#[arg(long)]
|
||||
platform: Option<InternedString>,
|
||||
#[ts(skip)]
|
||||
#[arg(skip)]
|
||||
#[serde(rename = "__device_info")]
|
||||
pub device_info: Option<DeviceInfo>,
|
||||
}
|
||||
|
||||
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<BTreeMap<Version, OsVersionInfo>, 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))
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<C: Context>() -> ParentHandler<C> {
|
||||
@@ -31,6 +31,22 @@ pub fn category_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_about("Remove a category from the registry")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.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::<CliContext>(),
|
||||
)
|
||||
.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::<CliContext>(),
|
||||
)
|
||||
.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<BTreeMap<InternedString, Category>, Error> {
|
||||
@@ -134,16 +193,9 @@ pub fn display_categories<T>(
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -45,7 +45,9 @@ pub struct PackageInfoShort {
|
||||
pub struct GetPackageParams {
|
||||
pub id: Option<PackageId>,
|
||||
#[ts(type = "string | null")]
|
||||
pub version: Option<VersionRange>,
|
||||
#[arg(long, short = 'v')]
|
||||
pub target_version: Option<VersionRange>,
|
||||
#[arg(long)]
|
||||
pub source_version: Option<VersionString>,
|
||||
#[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))
|
||||
|
||||
@@ -47,7 +47,6 @@ pub struct PackageInfo {
|
||||
#[ts(export)]
|
||||
pub struct Category {
|
||||
pub name: String,
|
||||
pub description: Description,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, HasModel, TS)]
|
||||
|
||||
@@ -31,6 +31,13 @@ pub fn package_api<C: Context>() -> ParentHandler<C> {
|
||||
.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::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"signer",
|
||||
signer::signer_api::<C>().with_about("Add, remove, and list package signers"),
|
||||
|
||||
@@ -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_16::Version>),
|
||||
V0_3_6_alpha_17(Wrapper<v0_3_6_alpha_17::Version>),
|
||||
V0_3_6_alpha_18(Wrapper<v0_3_6_alpha_18::Version>),
|
||||
V0_4_0_alpha_0(Wrapper<v0_4_0_alpha_0::Version>), // VERSION_BUMP
|
||||
V0_4_0_alpha_0(Wrapper<v0_4_0_alpha_0::Version>),
|
||||
V0_4_0_alpha_1(Wrapper<v0_4_0_alpha_1::Version>), // 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(),
|
||||
}
|
||||
}
|
||||
|
||||
72
core/startos/src/version/v0_4_0_alpha_1.rs
Normal file
72
core/startos/src/version/v0_4_0_alpha_1.rs
Normal file
@@ -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<Self::PreUpRes, Error> {
|
||||
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(())
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
|
||||
4
sdk/base/lib/osBindings/AddPackageToCategoryParams.ts
Normal file
4
sdk/base/lib/osBindings/AddPackageToCategoryParams.ts
Normal file
@@ -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 }
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
4
sdk/base/lib/osBindings/RemoveAdminParams.ts
Normal file
4
sdk/base/lib/osBindings/RemoveAdminParams.ts
Normal file
@@ -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 }
|
||||
3
sdk/base/lib/osBindings/RemoveAssetParams.ts
Normal file
3
sdk/base/lib/osBindings/RemoveAssetParams.ts
Normal file
@@ -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 }
|
||||
@@ -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 }
|
||||
5
sdk/base/lib/osBindings/RemovePackageParams.ts
Normal file
5
sdk/base/lib/osBindings/RemovePackageParams.ts
Normal file
@@ -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 }
|
||||
@@ -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"
|
||||
|
||||
@@ -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<T extends any[], Then, Else> =
|
||||
|
||||
@@ -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,
|
||||
|
||||
4
web/package-lock.json
generated
4
web/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": {}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
[style.border-radius.%]="!registry ? 100 : null"
|
||||
size="60px"
|
||||
[url]="registry?.url || ''"
|
||||
[marketplace]="iconConfig"
|
||||
/>
|
||||
<h1 [tuiSkeleton]="!registry">
|
||||
{{ registry?.info?.name || 'Unnamed Registry' }}
|
||||
@@ -27,7 +26,6 @@
|
||||
[style.height.px]="42"
|
||||
[style.border-radius.%]="100"
|
||||
[url]="registry?.url || ''"
|
||||
[marketplace]="iconConfig"
|
||||
[tuiSkeleton]="!registry"
|
||||
/>
|
||||
<tui-drawer
|
||||
|
||||
@@ -60,7 +60,6 @@ header {
|
||||
}
|
||||
|
||||
store-icon {
|
||||
border-radius: 100%;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
OnDestroy,
|
||||
signal,
|
||||
} from '@angular/core'
|
||||
import { MarketplaceConfig } from '@start9labs/shared'
|
||||
import { Subject, takeUntil } from 'rxjs'
|
||||
import { AbstractCategoryService } from '../../services/category.service'
|
||||
import { StoreDataWithUrl } from '../../types'
|
||||
@@ -18,9 +17,6 @@ import { StoreDataWithUrl } from '../../types'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MenuComponent implements OnDestroy {
|
||||
@Input({ required: true })
|
||||
iconConfig!: MarketplaceConfig
|
||||
|
||||
@Input({ required: true })
|
||||
registry!: StoreDataWithUrl | null
|
||||
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
import { TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { StoreIconComponentModule } from './store-icon/store-icon.component.module'
|
||||
import { MarketplaceConfig } from '@start9labs/shared'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
selector: '[registry]',
|
||||
template: `
|
||||
<store-icon [url]="registry.url" [marketplace]="marketplace" size="40px" />
|
||||
<store-icon [url]="registry.url" size="40px" />
|
||||
<div tuiTitle>
|
||||
{{ registry.name }}
|
||||
<div tuiSubtitle>{{ registry.url }}</div>
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
<ng-template #noIcon>
|
||||
<img
|
||||
[style.max-width]="size || '100%'"
|
||||
src="assets/img/storefront-outline.png"
|
||||
alt="Marketplace Icon"
|
||||
alt="Registry Icon"
|
||||
/>
|
||||
</ng-template>
|
||||
`,
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,11 +39,11 @@ export class CategoriesComponent {
|
||||
readonly categoryChange = new EventEmitter<string>()
|
||||
|
||||
readonly fallback: Record<string, T.Category> = {
|
||||
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 {
|
||||
|
||||
@@ -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<string, StoreData | null>
|
||||
|
||||
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
BIN
web/projects/shared/assets/img/icon_alpha.png
Normal file
BIN
web/projects/shared/assets/img/icon_alpha.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 52 KiB |
BIN
web/projects/shared/assets/img/icon_beta.png
Normal file
BIN
web/projects/shared/assets/img/icon_beta.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 42 KiB |
@@ -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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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: `
|
||||
<menu [iconConfig]="marketplace" [registry]="registry$ | async">
|
||||
<menu [registry]="registry$ | async">
|
||||
<button
|
||||
slot="desktop"
|
||||
tuiIconButton
|
||||
@@ -54,9 +53,8 @@ import { DialogService, i18nPipe } from '@start9labs/shared'
|
||||
})
|
||||
export class MarketplaceMenuComponent {
|
||||
private readonly dialog = inject(DialogService)
|
||||
readonly marketplace = inject(ConfigService).marketplace
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
readonly registry$ = this.marketplaceService.getRegistry$()
|
||||
readonly registry$ = this.marketplaceService.getCurrentRegistry$()
|
||||
|
||||
changeRegistry() {
|
||||
this.dialog
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Component, inject, Input } from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { Component, Input } from '@angular/core'
|
||||
import { i18nPipe, knownRegistries, sameUrl } from '@start9labs/shared'
|
||||
import { TuiNotification } from '@taiga-ui/core'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -57,24 +56,24 @@ import { ConfigService } from 'src/app/services/config.service'
|
||||
imports: [TuiNotification, i18nPipe],
|
||||
})
|
||||
export class MarketplaceNotificationComponent {
|
||||
private readonly marketplace = inject(ConfigService).marketplace
|
||||
|
||||
@Input() url = ''
|
||||
|
||||
get status() {
|
||||
if (this.url === this.marketplace.start9) {
|
||||
const { start9, community, start9Beta, start9Alpha } = knownRegistries
|
||||
|
||||
if (sameUrl(this.url, start9)) {
|
||||
return 'success'
|
||||
}
|
||||
|
||||
if (this.url === this.marketplace.community) {
|
||||
if (sameUrl(this.url, community)) {
|
||||
return 'info'
|
||||
}
|
||||
|
||||
if (this.url.includes('beta')) {
|
||||
if (sameUrl(this.url, start9Beta)) {
|
||||
return 'warning'
|
||||
}
|
||||
|
||||
if (this.url.includes('alpha')) {
|
||||
if (sameUrl(this.url, start9Alpha)) {
|
||||
return 'error'
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
FilterPackagesPipe,
|
||||
FilterPackagesPipeModule,
|
||||
} from '@start9labs/marketplace'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { defaultRegistries, i18nPipe } from '@start9labs/shared'
|
||||
import { TuiScrollbar } from '@taiga-ui/core'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { tap, withLatestFrom } from 'rxjs'
|
||||
@@ -17,6 +17,7 @@ import { TitleDirective } from 'src/app/services/title.service'
|
||||
import { MarketplaceMenuComponent } from './components/menu.component'
|
||||
import { MarketplaceNotificationComponent } from './components/notification.component'
|
||||
import { MarketplaceTileComponent } from './components/tile.component'
|
||||
import { StorageService } from 'src/app/services/storage.service'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
@@ -161,16 +162,19 @@ export default class MarketplaceComponent {
|
||||
private readonly categoryService = inject(AbstractCategoryService)
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
private readonly router = inject(Router)
|
||||
private readonly patch = inject(PatchDB<DataModel>)
|
||||
private readonly storage = inject(StorageService)
|
||||
private readonly route = inject(ActivatedRoute)
|
||||
.queryParamMap.pipe(
|
||||
takeUntilDestroyed(),
|
||||
withLatestFrom(this.patch.watch$('ui', 'marketplace', 'selectedUrl')),
|
||||
tap(([params, selectedUrl]) => {
|
||||
tap(params => {
|
||||
const registry = params.get('registry')
|
||||
if (!registry) {
|
||||
this.router.navigate([], {
|
||||
queryParams: { registry: selectedUrl },
|
||||
queryParams: {
|
||||
registry:
|
||||
this.storage.get('selectedRegistry') ||
|
||||
defaultRegistries.start9,
|
||||
},
|
||||
queryParamsHandling: 'merge',
|
||||
})
|
||||
} else {
|
||||
@@ -180,8 +184,8 @@ export default class MarketplaceComponent {
|
||||
)
|
||||
.subscribe()
|
||||
|
||||
readonly url$ = this.marketplaceService.getRegistryUrl$()
|
||||
readonly url$ = this.marketplaceService.getCurrentRegistryUrl$()
|
||||
readonly category$ = this.categoryService.getCategory$()
|
||||
readonly query$ = this.categoryService.getQuery$()
|
||||
readonly registry$ = this.marketplaceService.getRegistry$()
|
||||
readonly registry$ = this.marketplaceService.getCurrentRegistry$()
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ export class MarketplacePreviewComponent {
|
||||
|
||||
readonly flavors$ = this.flavor$.pipe(
|
||||
switchMap(current =>
|
||||
this.marketplaceService.getRegistry$().pipe(
|
||||
this.marketplaceService.getCurrentRegistry$().pipe(
|
||||
map(({ packages }) =>
|
||||
packages.filter(
|
||||
({ id, flavor }) => id === this.pkgId && flavor !== current,
|
||||
|
||||
@@ -14,36 +14,28 @@ import {
|
||||
sameUrl,
|
||||
toUrl,
|
||||
} from '@start9labs/shared'
|
||||
import {
|
||||
TuiButton,
|
||||
TuiDialogContext,
|
||||
TuiDialogOptions,
|
||||
TuiIcon,
|
||||
TuiTitle,
|
||||
} from '@taiga-ui/core'
|
||||
import { TuiButton, TuiDialogContext, TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||
import { TuiCell } from '@taiga-ui/layout'
|
||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
import { combineLatest, filter, firstValueFrom, map, Subscription } from 'rxjs'
|
||||
import { FormComponent } from 'src/app/routes/portal/components/form.component'
|
||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||
import { ConfigService } from 'src/app/services/config.service'
|
||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||
import { DataModel, UIStore } from 'src/app/services/patch-db/data-model'
|
||||
import { TuiConfirmData } from '@taiga-ui/kit'
|
||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
import { IST, utils } from '@start9labs/start-sdk'
|
||||
import { StorageService } from 'src/app/services/storage.service'
|
||||
|
||||
@Component({
|
||||
standalone: true,
|
||||
template: `
|
||||
@if (stores$ | async; as stores) {
|
||||
@if (registries$ | async; as registries) {
|
||||
<h3 class="g-title">{{ 'Default Registries' | i18n }}</h3>
|
||||
@for (registry of stores.standard; track $index) {
|
||||
@for (registry of registries.standard; track $index) {
|
||||
<button
|
||||
tuiCell
|
||||
[disabled]="registry.selected"
|
||||
[marketplace]="marketplaceConfig"
|
||||
[registry]="registry"
|
||||
(click)="connect(registry.url)"
|
||||
></button>
|
||||
@@ -53,19 +45,18 @@ import { IST, utils } from '@start9labs/start-sdk'
|
||||
<tui-icon icon="@tui.plus" [style.margin-inline.rem]="'0.5'" />
|
||||
<div tuiTitle>{{ 'Add custom registry' | i18n }}</div>
|
||||
</button>
|
||||
@for (registry of stores.alt; track $index) {
|
||||
@for (registry of registries.alt; track $index) {
|
||||
<div class="connect-container">
|
||||
<button
|
||||
tuiCell
|
||||
[registry]="registry"
|
||||
[marketplace]="marketplaceConfig"
|
||||
(click)="connect(registry.url)"
|
||||
></button>
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="icon"
|
||||
iconStart="@tui.trash-2"
|
||||
(click)="delete(registry.url, registry.name)"
|
||||
(click)="delete(registry.url)"
|
||||
>
|
||||
{{ 'Delete' | i18n }}
|
||||
</button>
|
||||
@@ -103,26 +94,28 @@ export class MarketplaceRegistryModal {
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
private readonly context = injectContext<TuiDialogContext>()
|
||||
private readonly router = inject(Router)
|
||||
private readonly hosts$ = inject<PatchDB<DataModel>>(PatchDB).watch$(
|
||||
private readonly rawRegistries$ = inject<PatchDB<DataModel>>(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<string>(['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<void> {
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<DataModel>>(PatchDB).watch$(
|
||||
'ui',
|
||||
'marketplace',
|
||||
)
|
||||
private readonly marketplaceService = inject(MarketplaceService)
|
||||
private readonly i18n = inject(i18nPipe)
|
||||
|
||||
async alertMarketplace(url: string, originalUrl: string): Promise<boolean> {
|
||||
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<boolean> {
|
||||
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<boolean>({
|
||||
@@ -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',
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -242,10 +242,9 @@ export default class SystemGeneralComponent {
|
||||
readonly translation: TuiStringHandler<TuiContext<Languages>> = ({
|
||||
$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)
|
||||
|
||||
@@ -37,10 +37,7 @@ export class SnekDirective {
|
||||
const loader = this.loader.open('Saving high score').subscribe()
|
||||
|
||||
try {
|
||||
await this.api.setDbValue<number>(
|
||||
['gaming', 'snake', 'highScore'],
|
||||
score,
|
||||
)
|
||||
await this.api.setDbValue<number>(['snakeHighScore'], score)
|
||||
} catch (e: any) {
|
||||
this.errorService.handleError(e)
|
||||
} finally {
|
||||
|
||||
@@ -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<DataModel>,
|
||||
) {}
|
||||
|
||||
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)
|
||||
|
||||
@@ -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)"
|
||||
>
|
||||
<tui-avatar>
|
||||
<store-icon [url]="registry.url" [marketplace]="mp" />
|
||||
<store-icon [url]="registry.url" />
|
||||
</tui-avatar>
|
||||
<span tuiTitle>
|
||||
<b tuiFade>{{ registry.name }}</b>
|
||||
@@ -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<StoreIdentity | null>(null)
|
||||
|
||||
readonly data = toSignal<UpdatesData>(
|
||||
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<DataModel>>(PatchDB)
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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<RR.FollowServerMetricsRes>
|
||||
|
||||
abstract updateServer(url?: string): Promise<RR.UpdateServerRes>
|
||||
abstract updateServer(params: RR.UpdateServerReq): Promise<RR.UpdateServerRes>
|
||||
|
||||
abstract restartServer(
|
||||
params: RR.RestartServerReq,
|
||||
@@ -145,24 +140,21 @@ export abstract class ApiService {
|
||||
|
||||
// marketplace URLs
|
||||
|
||||
abstract registryRequest<T>(
|
||||
registryUrl: string,
|
||||
options: RPCOptions,
|
||||
): Promise<T>
|
||||
|
||||
abstract checkOSUpdate(
|
||||
qp: RR.CheckOSUpdateReq,
|
||||
): Promise<RR.GetRegistryOsUpdateRes>
|
||||
params: RR.CheckOsUpdateReq,
|
||||
): Promise<RR.CheckOsUpdateRes>
|
||||
|
||||
abstract getRegistryInfo(registryUrl: string): Promise<T.RegistryInfo>
|
||||
abstract getRegistryInfo(
|
||||
params: RR.GetRegistryInfoReq,
|
||||
): Promise<RR.GetRegistryInfoRes>
|
||||
|
||||
abstract getRegistryPackage(
|
||||
url: string,
|
||||
id: string,
|
||||
versionRange: string | null,
|
||||
): Promise<GetPackageRes>
|
||||
params: RR.GetRegistryPackageReq,
|
||||
): Promise<RR.GetRegistryPackageRes>
|
||||
|
||||
abstract getRegistryPackages(registryUrl: string): Promise<GetPackagesRes>
|
||||
abstract getRegistryPackages(
|
||||
params: RR.GetRegistryPackagesReq,
|
||||
): Promise<RR.GetRegistryPackagesRes>
|
||||
|
||||
// notification
|
||||
|
||||
|
||||
@@ -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<Dump<DataModel>>,
|
||||
) {
|
||||
@@ -248,10 +240,7 @@ export class LiveApiService extends ApiService {
|
||||
return this.rpcRequest({ method: 'server.metrics.follow', params })
|
||||
}
|
||||
|
||||
async updateServer(url?: string): Promise<RR.UpdateServerRes> {
|
||||
const params = {
|
||||
registry: url || this.config.startosRegistry,
|
||||
}
|
||||
async updateServer(params: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
|
||||
return this.rpcRequest({ method: 'server.update', params })
|
||||
}
|
||||
|
||||
@@ -283,61 +272,38 @@ export class LiveApiService extends ApiService {
|
||||
|
||||
// marketplace URLs
|
||||
|
||||
async registryRequest<T>(
|
||||
registryUrl: string,
|
||||
options: RPCOptions,
|
||||
): Promise<T> {
|
||||
return this.rpcRequest({
|
||||
...options,
|
||||
method: `registry.${options.method}`,
|
||||
params: { registry: registryUrl, ...options.params },
|
||||
})
|
||||
}
|
||||
|
||||
async checkOSUpdate(
|
||||
qp: RR.CheckOSUpdateReq,
|
||||
): Promise<RR.GetRegistryOsUpdateRes> {
|
||||
const { serverId } = qp
|
||||
|
||||
return this.registryRequest(this.config.startosRegistry, {
|
||||
method: 'os.version.get',
|
||||
params: { serverId },
|
||||
})
|
||||
}
|
||||
|
||||
async getRegistryInfo(registryUrl: string): Promise<T.RegistryInfo> {
|
||||
return this.registryRequest(registryUrl, {
|
||||
method: 'info',
|
||||
params: {},
|
||||
})
|
||||
}
|
||||
|
||||
async getRegistryPackage(
|
||||
registryUrl: string,
|
||||
id: string,
|
||||
versionRange: string | null,
|
||||
): Promise<GetPackageRes> {
|
||||
const params: GetPackageReq = {
|
||||
id,
|
||||
version: versionRange,
|
||||
otherVersions: 'short',
|
||||
}
|
||||
|
||||
return this.registryRequest<GetPackageRes>(registryUrl, {
|
||||
method: 'package.get',
|
||||
params: RR.CheckOsUpdateReq,
|
||||
): Promise<RR.CheckOsUpdateRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'registry.os.version.get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
async getRegistryPackages(registryUrl: string): Promise<GetPackagesRes> {
|
||||
const params: GetPackagesReq = {
|
||||
id: null,
|
||||
version: null,
|
||||
otherVersions: 'short',
|
||||
}
|
||||
async getRegistryInfo(
|
||||
params: RR.GetRegistryInfoReq,
|
||||
): Promise<RR.GetRegistryInfoRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'registry.info',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
return this.registryRequest<GetPackagesRes>(registryUrl, {
|
||||
method: 'package.get',
|
||||
async getRegistryPackage(
|
||||
params: RR.GetRegistryPackageReq,
|
||||
): Promise<RR.GetRegistryPackageRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'registry.package.get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
async getRegistryPackages(
|
||||
params: RR.GetRegistryPackagesReq,
|
||||
): Promise<RR.GetRegistryPackagesRes> {
|
||||
return this.rpcRequest({
|
||||
method: 'registry.package.get',
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -169,6 +169,7 @@ export class MockApiService extends ApiService {
|
||||
pathArr: Array<string | number>,
|
||||
value: T,
|
||||
): Promise<RR.SetDBValueRes> {
|
||||
console.warn(pathArr, value)
|
||||
const pointer = pathFromArray(pathArr)
|
||||
const params: RR.SetDBValueReq<T> = { pointer, value }
|
||||
await pauseFor(2000)
|
||||
@@ -367,7 +368,7 @@ export class MockApiService extends ApiService {
|
||||
}
|
||||
}
|
||||
|
||||
async updateServer(url?: string): Promise<RR.UpdateServerRes> {
|
||||
async updateServer(params?: RR.UpdateServerReq): Promise<RR.UpdateServerRes> {
|
||||
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<any> {
|
||||
await pauseFor(2000)
|
||||
|
||||
return Error('do not call directly')
|
||||
}
|
||||
|
||||
async checkOSUpdate(
|
||||
qp: RR.CheckOSUpdateReq,
|
||||
): Promise<RR.GetRegistryOsUpdateRes> {
|
||||
params: RR.CheckOsUpdateReq,
|
||||
): Promise<RR.CheckOsUpdateRes> {
|
||||
await pauseFor(2000)
|
||||
return Mock.RegistryOSUpdate
|
||||
}
|
||||
|
||||
async getRegistryInfo(registryUrl: string): Promise<T.RegistryInfo> {
|
||||
async getRegistryInfo(
|
||||
params: RR.GetRegistryInfoReq,
|
||||
): Promise<RR.GetRegistryInfoRes> {
|
||||
await pauseFor(2000)
|
||||
return Mock.RegistryInfo
|
||||
}
|
||||
|
||||
async getRegistryPackage(
|
||||
url: string,
|
||||
id: string,
|
||||
versionRange: string,
|
||||
): Promise<GetPackageRes> {
|
||||
params: RR.GetRegistryPackageReq,
|
||||
): Promise<RR.GetRegistryPackageRes> {
|
||||
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<GetPackagesRes> {
|
||||
async getRegistryPackages(
|
||||
params: RR.GetRegistryPackagesReq,
|
||||
): Promise<RR.GetRegistryPackagesRes> {
|
||||
await pauseFor(2000)
|
||||
return Mock.RegistryPackages
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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<string>(1)
|
||||
private readonly registryUrl$ = this.registryUrlSubject$.pipe(
|
||||
private readonly currentRegistryUrlSubject$ = new ReplaySubject<string>(1)
|
||||
private readonly currentRegistryUrl$ = this.currentRegistryUrlSubject$.pipe(
|
||||
distinctUntilChanged(),
|
||||
)
|
||||
|
||||
private readonly registry$: Observable<StoreDataWithUrl> =
|
||||
this.registryUrl$.pipe(
|
||||
private readonly currentRegistry$: Observable<StoreDataWithUrl> =
|
||||
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<string, string>
|
||||
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<StoreIdentity[]> = this.patch
|
||||
.watch$('ui', 'marketplace', 'knownHosts')
|
||||
private readonly registries$: Observable<StoreIdentity[]> = 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<StoreIdentity[]> =
|
||||
private readonly filteredRegistries$: Observable<StoreIdentity[]> =
|
||||
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<string[]>([])
|
||||
|
||||
readonly marketplace$: Observable<Marketplace> = this.knownHosts$.pipe(
|
||||
readonly marketplace$: Observable<Marketplace> = this.registries$.pipe(
|
||||
startWith<StoreIdentity[]>([]),
|
||||
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<StoreData | null, [string, StoreData | null]>(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<DataModel>,
|
||||
private readonly config: ConfigService,
|
||||
private readonly clientStorageService: ClientStorageService,
|
||||
private readonly exver: Exver,
|
||||
) {}
|
||||
|
||||
getKnownHosts$(filtered = false): Observable<StoreIdentity[]> {
|
||||
getRegistries$(filtered = false): Observable<StoreIdentity[]> {
|
||||
// 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<StoreDataWithUrl> {
|
||||
return this.registry$
|
||||
getCurrentRegistry$(): Observable<StoreDataWithUrl> {
|
||||
return this.currentRegistry$
|
||||
}
|
||||
|
||||
getPackage$(
|
||||
@@ -161,7 +147,7 @@ export class MarketplaceService {
|
||||
flavor: string | null,
|
||||
registryUrl?: string,
|
||||
): Observable<MarketplacePkg> {
|
||||
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<T.RegistryInfo> {
|
||||
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<string, string>
|
||||
categories: {
|
||||
all: {
|
||||
name: 'All',
|
||||
description: {
|
||||
short: '',
|
||||
long: '',
|
||||
},
|
||||
},
|
||||
...info.categories,
|
||||
},
|
||||
@@ -202,7 +183,7 @@ export class MarketplaceService {
|
||||
}
|
||||
|
||||
private fetchRegistry$(url: string): Observable<StoreDataWithUrl | null> {
|
||||
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<MarketplacePkg[]> {
|
||||
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<MarketplacePkg> {
|
||||
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<void> {
|
||||
console.warn(oldName, newName)
|
||||
if (oldName !== newName) {
|
||||
this.api.setDbValue<string>(
|
||||
['marketplace', 'knownHosts', url, 'name'],
|
||||
newName,
|
||||
)
|
||||
this.api.setDbValue<string>(['registries', url], newName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function toStoreIdentity(url: string, uiStore: UIStore): StoreIdentity {
|
||||
function toStoreIdentity(url: string, name?: string | null): StoreIdentity {
|
||||
return {
|
||||
url,
|
||||
...uiStore,
|
||||
name: name || url,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<boolean>(false)
|
||||
|
||||
readonly updating$ = this.patch.watch$('serverInfo', 'statusInfo').pipe(
|
||||
@@ -47,7 +53,12 @@ export class OSService {
|
||||
|
||||
async loadOS(): Promise<void> {
|
||||
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'
|
||||
|
||||
@@ -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<string, string | null>
|
||||
ackInstructions: Record<string, boolean>
|
||||
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: {
|
||||
|
||||
Reference in New Issue
Block a user