mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
Misc frontend fixes (#2974)
* fix dependency input warning and extra comma * clean up buttons during install in marketplace preview * chore: grayscale and closing action-bar * fix prerelease precedence * fix duplicate url for addSsl on ssl proto * no warning for soft uninstall * fix: stop logs from repeating disconnected status and add 1 second delay between reconnection attempts * fix stop on reactivation of critical task * fix: fix disconnected toast * fix: updates styles * fix: updates styles * misc fixes * beta.33 * fix updates badge and initialization of marketplace preview controls --------- Co-authored-by: waterplea <alexander@inkin.ru> Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
2
container-runtime/package-lock.json
generated
2
container-runtime/package-lock.json
generated
@@ -38,7 +38,7 @@
|
|||||||
},
|
},
|
||||||
"../sdk/dist": {
|
"../sdk/dist": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.32",
|
"version": "0.4.0-beta.33",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
|
|||||||
@@ -394,7 +394,9 @@ impl RpcContext {
|
|||||||
if let Some(service) = self.services.get(&package_id).await.as_ref() {
|
if let Some(service) = self.services.get(&package_id).await.as_ref() {
|
||||||
if let Some(input) = service
|
if let Some(input) = service
|
||||||
.get_action_input(procedure_id.clone(), action_id.clone())
|
.get_action_input(procedure_id.clone(), action_id.clone())
|
||||||
.await?
|
.await
|
||||||
|
.log_err()
|
||||||
|
.flatten()
|
||||||
.and_then(|i| i.value)
|
.and_then(|i| i.value)
|
||||||
{
|
{
|
||||||
action_input
|
action_input
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ use crate::rpc_continuations::{Guid, RpcContinuation};
|
|||||||
use crate::s9pk::v2::pack::{CONTAINER_DATADIR, CONTAINER_TOOL};
|
use crate::s9pk::v2::pack::{CONTAINER_DATADIR, CONTAINER_TOOL};
|
||||||
use crate::ssh::SSH_DIR;
|
use crate::ssh::SSH_DIR;
|
||||||
use crate::system::{get_mem_info, sync_kiosk};
|
use crate::system::{get_mem_info, sync_kiosk};
|
||||||
use crate::util::io::{create_file, IOHook};
|
use crate::util::io::{create_file, open_file, IOHook};
|
||||||
use crate::util::lshw::lshw;
|
use crate::util::lshw::lshw;
|
||||||
use crate::util::net::WebSocketExt;
|
use crate::util::net::WebSocketExt;
|
||||||
use crate::util::{cpupower, Invoke};
|
use crate::util::{cpupower, Invoke};
|
||||||
@@ -399,6 +399,11 @@ pub async fn init(
|
|||||||
.invoke(crate::ErrorKind::Journald)
|
.invoke(crate::ErrorKind::Journald)
|
||||||
.await?;
|
.await?;
|
||||||
mount_logs.complete();
|
mount_logs.complete();
|
||||||
|
tokio::io::copy(
|
||||||
|
&mut open_file("/run/startos/init.log").await?,
|
||||||
|
&mut tokio::io::stderr(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
tracing::info!("Mounted Logs");
|
tracing::info!("Mounted Logs");
|
||||||
|
|
||||||
load_ca_cert.start();
|
load_ca_cert.start();
|
||||||
|
|||||||
@@ -89,8 +89,13 @@ impl LxcManager {
|
|||||||
log_mount: Option<&Path>,
|
log_mount: Option<&Path>,
|
||||||
config: LxcConfig,
|
config: LxcConfig,
|
||||||
) -> Result<LxcContainer, Error> {
|
) -> Result<LxcContainer, Error> {
|
||||||
let container = LxcContainer::new(self, log_mount, config).await?;
|
|
||||||
let mut guard = self.containers.lock().await;
|
let mut guard = self.containers.lock().await;
|
||||||
|
let container = tokio::time::timeout(
|
||||||
|
Duration::from_secs(30),
|
||||||
|
LxcContainer::new(self, log_mount, config),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.with_kind(ErrorKind::Timeout)??;
|
||||||
*guard = std::mem::take(&mut *guard)
|
*guard = std::mem::take(&mut *guard)
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|g| g.strong_count() > 0)
|
.filter(|g| g.strong_count() > 0)
|
||||||
@@ -223,6 +228,17 @@ impl LxcContainer {
|
|||||||
.arg(&log_mount_point)
|
.arg(&log_mount_point)
|
||||||
.invoke(crate::ErrorKind::Filesystem)
|
.invoke(crate::ErrorKind::Filesystem)
|
||||||
.await?;
|
.await?;
|
||||||
|
match Command::new("chattr")
|
||||||
|
.arg("-R")
|
||||||
|
.arg("+C")
|
||||||
|
.arg(&log_mount_point)
|
||||||
|
.invoke(ErrorKind::Filesystem)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(_) => Ok(()),
|
||||||
|
Err(e) if e.source.to_string().contains("Operation not supported") => Ok(()),
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}?;
|
||||||
Some(log_mount)
|
Some(log_mount)
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
|||||||
@@ -62,12 +62,14 @@ impl BindInfo {
|
|||||||
pub fn new(available_ports: &mut AvailablePorts, options: BindOptions) -> Result<Self, Error> {
|
pub fn new(available_ports: &mut AvailablePorts, options: BindOptions) -> Result<Self, Error> {
|
||||||
let mut assigned_port = None;
|
let mut assigned_port = None;
|
||||||
let mut assigned_ssl_port = None;
|
let mut assigned_ssl_port = None;
|
||||||
if options.secure.is_some() {
|
|
||||||
assigned_port = Some(available_ports.alloc()?);
|
|
||||||
}
|
|
||||||
if options.add_ssl.is_some() {
|
if options.add_ssl.is_some() {
|
||||||
assigned_ssl_port = Some(available_ports.alloc()?);
|
assigned_ssl_port = Some(available_ports.alloc()?);
|
||||||
}
|
}
|
||||||
|
if let Some(secure) = options.secure {
|
||||||
|
if !secure.ssl || !options.add_ssl.is_some() {
|
||||||
|
assigned_port = Some(available_ports.alloc()?);
|
||||||
|
}
|
||||||
|
}
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
options,
|
options,
|
||||||
|
|||||||
@@ -176,29 +176,44 @@ impl Handler<RunAction> for ServiceActor {
|
|||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.with_kind(ErrorKind::Action)?;
|
.with_kind(ErrorKind::Action)?;
|
||||||
if self
|
let package_id = package_id.clone();
|
||||||
|
for to_stop in self
|
||||||
.0
|
.0
|
||||||
.ctx
|
.ctx
|
||||||
.db
|
.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
let mut critical_activated = false;
|
let mut to_stop = Vec::new();
|
||||||
for (_, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? {
|
for (id, pde) in db.as_public_mut().as_package_data_mut().as_entries_mut()? {
|
||||||
critical_activated |= pde.as_tasks_mut().mutate(|tasks| {
|
if pde.as_tasks_mut().mutate(|tasks| {
|
||||||
Ok(update_tasks(tasks, package_id, action_id, &input, true))
|
Ok(update_tasks(tasks, &package_id, action_id, &input, true))
|
||||||
})?;
|
})? {
|
||||||
|
to_stop.push(id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(critical_activated)
|
Ok(to_stop)
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.result?
|
.result?
|
||||||
{
|
{
|
||||||
<Self as Handler<super::control::Stop>>::handle(
|
if to_stop == package_id {
|
||||||
self,
|
<Self as Handler<super::control::Stop>>::handle(
|
||||||
id,
|
self,
|
||||||
super::control::Stop { wait: false },
|
id.clone(),
|
||||||
jobs,
|
super::control::Stop { wait: false },
|
||||||
)
|
jobs,
|
||||||
.await;
|
)
|
||||||
|
.await;
|
||||||
|
} else {
|
||||||
|
self.0
|
||||||
|
.ctx
|
||||||
|
.services
|
||||||
|
.get(&to_stop)
|
||||||
|
.await
|
||||||
|
.as_ref()
|
||||||
|
.or_not_found(&to_stop)?
|
||||||
|
.stop(id.clone(), false)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,9 +17,6 @@ fi
|
|||||||
STARTOS_ENV="$(dpkg-deb --fsys-tarfile $DEB_PATH | tar --to-stdout -xvf - ./usr/lib/startos/ENVIRONMENT.txt)"
|
STARTOS_ENV="$(dpkg-deb --fsys-tarfile $DEB_PATH | tar --to-stdout -xvf - ./usr/lib/startos/ENVIRONMENT.txt)"
|
||||||
PLATFORM="$(dpkg-deb --fsys-tarfile $DEB_PATH | tar --to-stdout -xvf - ./usr/lib/startos/PLATFORM.txt)"
|
PLATFORM="$(dpkg-deb --fsys-tarfile $DEB_PATH | tar --to-stdout -xvf - ./usr/lib/startos/PLATFORM.txt)"
|
||||||
|
|
||||||
if [ -z "$1" ]; then
|
|
||||||
PLATFORM="$(uname -m)"
|
|
||||||
fi
|
|
||||||
if [ "$PLATFORM" = "x86_64" ] || [ "$PLATFORM" = "x86_64-nonfree" ]; then
|
if [ "$PLATFORM" = "x86_64" ] || [ "$PLATFORM" = "x86_64-nonfree" ]; then
|
||||||
ARCH=amd64
|
ARCH=amd64
|
||||||
QEMU_ARCH=x86_64
|
QEMU_ARCH=x86_64
|
||||||
|
|||||||
@@ -104,7 +104,11 @@ export class Action<Id extends T.ActionId, Type extends Record<string, any>>
|
|||||||
this.cachedParser = built.validator
|
this.cachedParser = built.validator
|
||||||
return {
|
return {
|
||||||
spec: built.spec,
|
spec: built.spec,
|
||||||
value: (await this.getInputFn(options)) || null,
|
value:
|
||||||
|
((await this.getInputFn(options)) as
|
||||||
|
| Record<string, unknown>
|
||||||
|
| null
|
||||||
|
| undefined) || null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
async run(options: {
|
async run(options: {
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
import { ExtendedVersion, VersionRange } from "../exver"
|
import { ExtendedVersion, VersionRange } from "../exver"
|
||||||
import { PackageId, HealthCheckId } from "../types"
|
import {
|
||||||
|
PackageId,
|
||||||
|
HealthCheckId,
|
||||||
|
DependencyRequirement,
|
||||||
|
CheckDependenciesResult,
|
||||||
|
} from "../types"
|
||||||
import { Effects } from "../Effects"
|
import { Effects } from "../Effects"
|
||||||
|
|
||||||
export type CheckDependencies<DependencyId extends PackageId = PackageId> = {
|
export type CheckDependencies<DependencyId extends PackageId = PackageId> = {
|
||||||
|
infoFor: (packageId: DependencyId) => {
|
||||||
|
requirement: DependencyRequirement
|
||||||
|
result: CheckDependenciesResult
|
||||||
|
}
|
||||||
|
|
||||||
installedSatisfied: (packageId: DependencyId) => boolean
|
installedSatisfied: (packageId: DependencyId) => boolean
|
||||||
installedVersionSatisfied: (packageId: DependencyId) => boolean
|
installedVersionSatisfied: (packageId: DependencyId) => boolean
|
||||||
runningSatisfied: (packageId: DependencyId) => boolean
|
runningSatisfied: (packageId: DependencyId) => boolean
|
||||||
@@ -41,7 +51,7 @@ export async function checkDependencies<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const find = (packageId: DependencyId) => {
|
const infoFor = (packageId: DependencyId) => {
|
||||||
const dependencyRequirement = dependencies.find((d) => d.id === packageId)
|
const dependencyRequirement = dependencies.find((d) => d.id === packageId)
|
||||||
const dependencyResult = results.find((d) => d.packageId === packageId)
|
const dependencyResult = results.find((d) => d.packageId === packageId)
|
||||||
if (!dependencyRequirement || !dependencyResult) {
|
if (!dependencyRequirement || !dependencyResult) {
|
||||||
@@ -51,9 +61,9 @@ export async function checkDependencies<
|
|||||||
}
|
}
|
||||||
|
|
||||||
const installedSatisfied = (packageId: DependencyId) =>
|
const installedSatisfied = (packageId: DependencyId) =>
|
||||||
!!find(packageId).result.installedVersion
|
!!infoFor(packageId).result.installedVersion
|
||||||
const installedVersionSatisfied = (packageId: DependencyId) => {
|
const installedVersionSatisfied = (packageId: DependencyId) => {
|
||||||
const dep = find(packageId)
|
const dep = infoFor(packageId)
|
||||||
return (
|
return (
|
||||||
!!dep.result.installedVersion &&
|
!!dep.result.installedVersion &&
|
||||||
ExtendedVersion.parse(dep.result.installedVersion).satisfies(
|
ExtendedVersion.parse(dep.result.installedVersion).satisfies(
|
||||||
@@ -62,18 +72,18 @@ export async function checkDependencies<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
const runningSatisfied = (packageId: DependencyId) => {
|
const runningSatisfied = (packageId: DependencyId) => {
|
||||||
const dep = find(packageId)
|
const dep = infoFor(packageId)
|
||||||
return dep.requirement.kind !== "running" || dep.result.isRunning
|
return dep.requirement.kind !== "running" || dep.result.isRunning
|
||||||
}
|
}
|
||||||
const tasksSatisfied = (packageId: DependencyId) =>
|
const tasksSatisfied = (packageId: DependencyId) =>
|
||||||
Object.entries(find(packageId).result.tasks).filter(
|
Object.entries(infoFor(packageId).result.tasks).filter(
|
||||||
([_, t]) => t.active && t.task.severity === "critical",
|
([_, t]) => t.active && t.task.severity === "critical",
|
||||||
).length === 0
|
).length === 0
|
||||||
const healthCheckSatisfied = (
|
const healthCheckSatisfied = (
|
||||||
packageId: DependencyId,
|
packageId: DependencyId,
|
||||||
healthCheckId?: HealthCheckId,
|
healthCheckId?: HealthCheckId,
|
||||||
) => {
|
) => {
|
||||||
const dep = find(packageId)
|
const dep = infoFor(packageId)
|
||||||
if (
|
if (
|
||||||
healthCheckId &&
|
healthCheckId &&
|
||||||
(dep.requirement.kind !== "running" ||
|
(dep.requirement.kind !== "running" ||
|
||||||
@@ -102,14 +112,14 @@ export async function checkDependencies<
|
|||||||
: dependencies.every((d) => pkgSatisfied(d.id as DependencyId))
|
: dependencies.every((d) => pkgSatisfied(d.id as DependencyId))
|
||||||
|
|
||||||
const throwIfInstalledNotSatisfied = (packageId: DependencyId) => {
|
const throwIfInstalledNotSatisfied = (packageId: DependencyId) => {
|
||||||
const dep = find(packageId)
|
const dep = infoFor(packageId)
|
||||||
if (!dep.result.installedVersion) {
|
if (!dep.result.installedVersion) {
|
||||||
throw new Error(`${dep.result.title || packageId} is not installed`)
|
throw new Error(`${dep.result.title || packageId} is not installed`)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const throwIfInstalledVersionNotSatisfied = (packageId: DependencyId) => {
|
const throwIfInstalledVersionNotSatisfied = (packageId: DependencyId) => {
|
||||||
const dep = find(packageId)
|
const dep = infoFor(packageId)
|
||||||
if (!dep.result.installedVersion) {
|
if (!dep.result.installedVersion) {
|
||||||
throw new Error(`${dep.result.title || packageId} is not installed`)
|
throw new Error(`${dep.result.title || packageId} is not installed`)
|
||||||
}
|
}
|
||||||
@@ -127,14 +137,14 @@ export async function checkDependencies<
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const throwIfRunningNotSatisfied = (packageId: DependencyId) => {
|
const throwIfRunningNotSatisfied = (packageId: DependencyId) => {
|
||||||
const dep = find(packageId)
|
const dep = infoFor(packageId)
|
||||||
if (dep.requirement.kind === "running" && !dep.result.isRunning) {
|
if (dep.requirement.kind === "running" && !dep.result.isRunning) {
|
||||||
throw new Error(`${dep.result.title || packageId} is not running`)
|
throw new Error(`${dep.result.title || packageId} is not running`)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const throwIfTasksNotSatisfied = (packageId: DependencyId) => {
|
const throwIfTasksNotSatisfied = (packageId: DependencyId) => {
|
||||||
const dep = find(packageId)
|
const dep = infoFor(packageId)
|
||||||
const reqs = Object.entries(dep.result.tasks)
|
const reqs = Object.entries(dep.result.tasks)
|
||||||
.filter(([_, t]) => t.active && t.task.severity === "critical")
|
.filter(([_, t]) => t.active && t.task.severity === "critical")
|
||||||
.map(([id, _]) => id)
|
.map(([id, _]) => id)
|
||||||
@@ -149,7 +159,7 @@ export async function checkDependencies<
|
|||||||
packageId: DependencyId,
|
packageId: DependencyId,
|
||||||
healthCheckId?: HealthCheckId,
|
healthCheckId?: HealthCheckId,
|
||||||
) => {
|
) => {
|
||||||
const dep = find(packageId)
|
const dep = infoFor(packageId)
|
||||||
if (
|
if (
|
||||||
healthCheckId &&
|
healthCheckId &&
|
||||||
(dep.requirement.kind !== "running" ||
|
(dep.requirement.kind !== "running" ||
|
||||||
@@ -205,6 +215,7 @@ export async function checkDependencies<
|
|||||||
})()
|
})()
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
infoFor,
|
||||||
installedSatisfied,
|
installedSatisfied,
|
||||||
installedVersionSatisfied,
|
installedVersionSatisfied,
|
||||||
runningSatisfied,
|
runningSatisfied,
|
||||||
|
|||||||
@@ -753,7 +753,10 @@ export class Version {
|
|||||||
return "less"
|
return "less"
|
||||||
}
|
}
|
||||||
|
|
||||||
const prereleaseLen = Math.max(this.number.length, other.number.length)
|
const prereleaseLen = Math.max(
|
||||||
|
this.prerelease.length,
|
||||||
|
other.prerelease.length,
|
||||||
|
)
|
||||||
for (let i = 0; i < prereleaseLen; i++) {
|
for (let i = 0; i < prereleaseLen; i++) {
|
||||||
if (typeof this.prerelease[i] === typeof other.prerelease[i]) {
|
if (typeof this.prerelease[i] === typeof other.prerelease[i]) {
|
||||||
if (this.prerelease[i] > other.prerelease[i]) {
|
if (this.prerelease[i] > other.prerelease[i]) {
|
||||||
|
|||||||
@@ -180,8 +180,8 @@ export type KnownError =
|
|||||||
|
|
||||||
export type Dependencies = Array<DependencyRequirement>
|
export type Dependencies = Array<DependencyRequirement>
|
||||||
|
|
||||||
export type DeepPartial<T> = T extends unknown[]
|
export type DeepPartial<T> = T extends [infer A, ...infer Rest]
|
||||||
? T
|
? [DeepPartial<A>, ...DeepPartial<Rest>]
|
||||||
: T extends {}
|
: T extends {}
|
||||||
? { [P in keyof T]?: DeepPartial<T[P]> }
|
? { [P in keyof T]?: DeepPartial<T[P]> }
|
||||||
: T
|
: T
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ export function partialDiff<T>(
|
|||||||
} else if (typeof prev === "object" && typeof next === "object") {
|
} else if (typeof prev === "object" && typeof next === "object") {
|
||||||
if (prev === null || next === null) return { diff: next }
|
if (prev === null || next === null) return { diff: next }
|
||||||
const res = { diff: {} as Record<keyof T, any> }
|
const res = { diff: {} as Record<keyof T, any> }
|
||||||
for (let key in next) {
|
const keys = Object.keys(next) as (keyof T)[]
|
||||||
|
for (let key in prev) {
|
||||||
|
if (!keys.includes(key)) keys.push(key)
|
||||||
|
}
|
||||||
|
for (let key of keys) {
|
||||||
const diff = partialDiff(prev[key], next[key])
|
const diff = partialDiff(prev[key], next[key])
|
||||||
if (diff) {
|
if (diff) {
|
||||||
res.diff[key] = diff.diff
|
res.diff[key] = diff.diff
|
||||||
|
|||||||
@@ -710,7 +710,7 @@ export class StartSdk<Manifest extends T.SDKManifest> {
|
|||||||
image,
|
image,
|
||||||
mounts,
|
mounts,
|
||||||
name,
|
name,
|
||||||
)
|
).then((subc) => subc.rc())
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* @description Run a function with a temporary SubContainer
|
* @description Run a function with a temporary SubContainer
|
||||||
|
|||||||
4
sdk/package/package-lock.json
generated
4
sdk/package/package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.32",
|
"version": "0.4.0-beta.33",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.32",
|
"version": "0.4.0-beta.33",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@iarna/toml": "^3.0.0",
|
"@iarna/toml": "^3.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@start9labs/start-sdk",
|
"name": "@start9labs/start-sdk",
|
||||||
"version": "0.4.0-beta.32",
|
"version": "0.4.0-beta.33",
|
||||||
"description": "Software development kit to facilitate packaging services for StartOS",
|
"description": "Software development kit to facilitate packaging services for StartOS",
|
||||||
"main": "./package/lib/index.js",
|
"main": "./package/lib/index.js",
|
||||||
"types": "./package/lib/index.d.ts",
|
"types": "./package/lib/index.d.ts",
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import { ClientStorageService } from './services/client-storage.service'
|
|||||||
import { DateTransformerService } from './services/date-transformer.service'
|
import { DateTransformerService } from './services/date-transformer.service'
|
||||||
import { DatetimeTransformerService } from './services/datetime-transformer.service'
|
import { DatetimeTransformerService } from './services/datetime-transformer.service'
|
||||||
import { StorageService } from './services/storage.service'
|
import { StorageService } from './services/storage.service'
|
||||||
|
import { FilterUpdatesPipe } from './routes/portal/routes/updates/filter-updates.pipe'
|
||||||
|
|
||||||
const {
|
const {
|
||||||
useMocks,
|
useMocks,
|
||||||
@@ -56,6 +57,7 @@ export const APP_PROVIDERS = [
|
|||||||
provideEventPlugins(),
|
provideEventPlugins(),
|
||||||
I18N_PROVIDERS,
|
I18N_PROVIDERS,
|
||||||
FilterPackagesPipe,
|
FilterPackagesPipe,
|
||||||
|
FilterUpdatesPipe,
|
||||||
UntypedFormBuilder,
|
UntypedFormBuilder,
|
||||||
tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }),
|
tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }),
|
||||||
tuiButtonOptionsProvider({ size: 'm' }),
|
tuiButtonOptionsProvider({ size: 'm' }),
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ import { HeaderStatusComponent } from './status.component'
|
|||||||
|
|
||||||
&:has([data-status='neutral']) {
|
&:has([data-status='neutral']) {
|
||||||
--status: var(--tui-status-neutral);
|
--status: var(--tui-status-neutral);
|
||||||
filter: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&:has([data-status='success']) {
|
&:has([data-status='success']) {
|
||||||
|
|||||||
@@ -18,18 +18,18 @@
|
|||||||
|
|
||||||
@if (followLogs | logs | async; as logs) {
|
@if (followLogs | logs | async; as logs) {
|
||||||
<section childList (waMutationObserver)="scrollToBottom()">
|
<section childList (waMutationObserver)="scrollToBottom()">
|
||||||
@for (log of logs; track log) {
|
@for (log of logs; track $index) {
|
||||||
<pre [innerHTML]="log | dompurify"></pre>
|
<pre [innerHTML]="log | dompurify"></pre>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if ((status$ | async) !== 'connected') {
|
@if ((status$ | async) !== 'connected') {
|
||||||
<p class="loading-dots" [attr.data-status]="status$.value">
|
<div class="loading-dots" [attr.data-status]="status$.value">
|
||||||
{{
|
{{
|
||||||
status$.value === 'reconnecting'
|
status$.value === 'reconnecting'
|
||||||
? ('Reconnecting' | i18n)
|
? ('Reconnecting' | i18n)
|
||||||
: ('Waiting for network connectivity' | i18n)
|
: ('Waiting for network connectivity' | i18n)
|
||||||
}}
|
}}
|
||||||
</p>
|
</div>
|
||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
} @else {
|
} @else {
|
||||||
|
|||||||
@@ -48,4 +48,5 @@
|
|||||||
pre {
|
pre {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,16 +8,19 @@ import {
|
|||||||
import {
|
import {
|
||||||
bufferTime,
|
bufferTime,
|
||||||
catchError,
|
catchError,
|
||||||
|
concat,
|
||||||
defer,
|
defer,
|
||||||
|
delay,
|
||||||
|
EMPTY,
|
||||||
filter,
|
filter,
|
||||||
ignoreElements,
|
ignoreElements,
|
||||||
map,
|
map,
|
||||||
merge,
|
merge,
|
||||||
Observable,
|
Observable,
|
||||||
|
of,
|
||||||
repeat,
|
repeat,
|
||||||
scan,
|
scan,
|
||||||
skipWhile,
|
skipWhile,
|
||||||
startWith,
|
|
||||||
switchMap,
|
switchMap,
|
||||||
take,
|
take,
|
||||||
tap,
|
tap,
|
||||||
@@ -62,12 +65,19 @@ export class LogsPipe implements PipeTransform {
|
|||||||
),
|
),
|
||||||
).pipe(
|
).pipe(
|
||||||
catchError(() =>
|
catchError(() =>
|
||||||
this.connection.pipe(
|
concat(
|
||||||
tap(v => this.logs.status$.next(v ? 'reconnecting' : 'disconnected')),
|
this.logs.status$.value === 'connected'
|
||||||
filter(Boolean),
|
? of(this.getMessage(false))
|
||||||
take(1),
|
: EMPTY,
|
||||||
ignoreElements(),
|
this.connection.pipe(
|
||||||
startWith(this.getMessage(false)),
|
tap(v =>
|
||||||
|
this.logs.status$.next(v ? 'reconnecting' : 'disconnected'),
|
||||||
|
),
|
||||||
|
filter(Boolean),
|
||||||
|
delay(1000),
|
||||||
|
take(1),
|
||||||
|
ignoreElements(),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
repeat(),
|
repeat(),
|
||||||
@@ -76,11 +86,11 @@ export class LogsPipe implements PipeTransform {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getMessage(success: boolean): string {
|
private getMessage(success: boolean): string {
|
||||||
return `<p style="color: ${
|
return `<div style="color: ${
|
||||||
success ? 'var(--tui-status-positive)' : 'var(--tui-status-negative)'
|
success ? 'var(--tui-status-positive)' : 'var(--tui-status-negative)'
|
||||||
}; text-align: center;">${this.i18n.transform(
|
}; text-align: center;">${this.i18n.transform(
|
||||||
success ? 'Reconnected' : 'Disconnected',
|
success ? 'Reconnected' : 'Disconnected',
|
||||||
)} at ${toLocalIsoString(new Date())}</p>`
|
)} at ${toLocalIsoString(new Date())}</div>`
|
||||||
}
|
}
|
||||||
|
|
||||||
private get options() {
|
private get options() {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { HeaderComponent } from './components/header/header.component'
|
|||||||
</main>
|
</main>
|
||||||
<app-tabs />
|
<app-tabs />
|
||||||
@if (update(); as update) {
|
@if (update(); as update) {
|
||||||
<tui-action-bar *tuiActionBar="bar">
|
<tui-action-bar *tuiActionBar="bar()">
|
||||||
@if (update === true) {
|
@if (update === true) {
|
||||||
<tui-icon icon="@tui.check" class="g-positive" />
|
<tui-icon icon="@tui.check" class="g-positive" />
|
||||||
Download complete, restart to apply changes
|
Download complete, restart to apply changes
|
||||||
@@ -77,8 +77,7 @@ import { HeaderComponent } from './components/header/header.component'
|
|||||||
|
|
||||||
@include taiga.transition(filter);
|
@include taiga.transition(filter);
|
||||||
|
|
||||||
header:has([data-status='success']) + &,
|
header:has([data-status='success']) + & {
|
||||||
header:has([data-status='neutral']) + & {
|
|
||||||
filter: none;
|
filter: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -104,7 +103,7 @@ export class PortalComponent {
|
|||||||
|
|
||||||
readonly name = toSignal(this.patch.watch$('ui', 'name'))
|
readonly name = toSignal(this.patch.watch$('ui', 'name'))
|
||||||
readonly update = toSignal(inject(OSService).updating$)
|
readonly update = toSignal(inject(OSService).updating$)
|
||||||
bar = true
|
readonly bar = signal(true)
|
||||||
|
|
||||||
getProgress(size: number, downloaded: number): number {
|
getProgress(size: number, downloaded: number): number {
|
||||||
return Math.round((100 * downloaded) / (size || 1))
|
return Math.round((100 * downloaded) / (size || 1))
|
||||||
@@ -114,7 +113,7 @@ export class PortalComponent {
|
|||||||
const loader = this.loader.open('Beginning restart').subscribe()
|
const loader = this.loader.open('Beginning restart').subscribe()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
this.bar = false
|
this.bar.set(false)
|
||||||
await this.api.restartServer({})
|
await this.api.restartServer({})
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
|
|||||||
@@ -3,11 +3,9 @@ import {
|
|||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
inject,
|
inject,
|
||||||
Input,
|
input,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { toSignal } from '@angular/core/rxjs-interop'
|
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import { MarketplacePkgBase } from '@start9labs/marketplace'
|
|
||||||
import {
|
import {
|
||||||
ErrorService,
|
ErrorService,
|
||||||
Exver,
|
Exver,
|
||||||
@@ -30,21 +28,20 @@ import {
|
|||||||
import { dryUpdate } from 'src/app/utils/dry-update'
|
import { dryUpdate } from 'src/app/utils/dry-update'
|
||||||
import { getAllPackages, getManifest } from 'src/app/utils/get-package-data'
|
import { getAllPackages, getManifest } from 'src/app/utils/get-package-data'
|
||||||
import { hasCurrentDeps } from 'src/app/utils/has-deps'
|
import { hasCurrentDeps } from 'src/app/utils/has-deps'
|
||||||
|
|
||||||
import { MarketplacePreviewComponent } from '../modals/preview.component'
|
import { MarketplacePreviewComponent } from '../modals/preview.component'
|
||||||
import { MarketplaceAlertsService } from '../services/alerts.service'
|
import { MarketplaceAlertsService } from '../services/alerts.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-controls',
|
selector: 'marketplace-controls',
|
||||||
template: `
|
template: `
|
||||||
@if (localPkg) {
|
@if (localPkg(); as local) {
|
||||||
@if (localPkg | toManifest; as localManifest) {
|
@if (local.stateInfo.state === 'installed') {
|
||||||
@switch (localManifest.version | compareExver: version() || '') {
|
@switch ((local | toManifest).version | compareExver: version()) {
|
||||||
@case (1) {
|
@case (1) {
|
||||||
<button
|
<button
|
||||||
tuiButton
|
tuiButton
|
||||||
type="button"
|
type="button"
|
||||||
appearance="secondary-destructive"
|
appearance="warning"
|
||||||
(click)="tryInstall()"
|
(click)="tryInstall()"
|
||||||
>
|
>
|
||||||
{{ 'Downgrade' | i18n }}
|
{{ 'Downgrade' | i18n }}
|
||||||
@@ -81,17 +78,17 @@ import { MarketplaceAlertsService } from '../services/alerts.service'
|
|||||||
{{
|
{{
|
||||||
('View' | i18n) +
|
('View' | i18n) +
|
||||||
' ' +
|
' ' +
|
||||||
($any(localPkg.stateInfo.state | titlecase) | i18n)
|
($any(local.stateInfo.state | titlecase) | i18n)
|
||||||
}}
|
}}
|
||||||
</button>
|
</button>
|
||||||
} @else {
|
} @else {
|
||||||
<button
|
<button
|
||||||
tuiButton
|
tuiButton
|
||||||
type="button"
|
type="button"
|
||||||
[appearance]="localFlavor ? 'warning' : 'primary'"
|
appearance="primary"
|
||||||
(click)="tryInstall()"
|
(click)="tryInstall()"
|
||||||
>
|
>
|
||||||
{{ localFlavor ? ('Switch' | i18n) : ('Install' | i18n) }}
|
{{ localFlavor() ? ('Switch' | i18n) : ('Install' | i18n) }}
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
@@ -116,29 +113,23 @@ export class MarketplaceControlsComponent {
|
|||||||
private readonly api = inject(ApiService)
|
private readonly api = inject(ApiService)
|
||||||
private readonly preview = inject(MarketplacePreviewComponent)
|
private readonly preview = inject(MarketplacePreviewComponent)
|
||||||
|
|
||||||
protected readonly version = toSignal(this.preview.version$)
|
version = input.required<string>()
|
||||||
|
installAlert = input.required<string | null>()
|
||||||
@Input({ required: true })
|
localPkg = input.required<PackageDataEntry | null>()
|
||||||
pkg!: MarketplacePkgBase
|
localFlavor = input.required<boolean>()
|
||||||
|
|
||||||
@Input()
|
|
||||||
localPkg!: PackageDataEntry | null
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
localFlavor!: boolean
|
|
||||||
|
|
||||||
// only present if side loading
|
// only present if side loading
|
||||||
@Input()
|
file = input<File>()
|
||||||
file?: File
|
|
||||||
|
|
||||||
async tryInstall() {
|
async tryInstall() {
|
||||||
const currentUrl = this.file
|
const localPkg = this.localPkg()
|
||||||
|
|
||||||
|
const currentUrl = this.file()
|
||||||
? null
|
? null
|
||||||
: await firstValueFrom(this.marketplaceService.currentRegistryUrl$)
|
: await firstValueFrom(this.marketplaceService.currentRegistryUrl$)
|
||||||
const originalUrl = this.localPkg?.registry || null
|
const originalUrl = localPkg?.registry || null
|
||||||
|
|
||||||
if (!this.localPkg) {
|
if (!localPkg) {
|
||||||
if (await this.alerts.alertInstall(this.pkg)) {
|
if (await this.alerts.alertInstall(this.installAlert() || '')) {
|
||||||
this.installOrUpload(currentUrl)
|
this.installOrUpload(currentUrl)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
@@ -152,12 +143,11 @@ export class MarketplaceControlsComponent {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const localManifest = getManifest(this.localPkg)
|
const localManifest = getManifest(localPkg)
|
||||||
const version = this.version() || ''
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
hasCurrentDeps(localManifest.id, await getAllPackages(this.patch)) &&
|
hasCurrentDeps(localManifest.id, await getAllPackages(this.patch)) &&
|
||||||
this.exver.compareExver(localManifest.version, version) !== 0
|
this.exver.compareExver(localManifest.version, this.version()) !== 0
|
||||||
) {
|
) {
|
||||||
this.dryInstall(currentUrl)
|
this.dryInstall(currentUrl)
|
||||||
} else {
|
} else {
|
||||||
@@ -171,9 +161,8 @@ export class MarketplaceControlsComponent {
|
|||||||
|
|
||||||
private async dryInstall(url: string | null) {
|
private async dryInstall(url: string | null) {
|
||||||
const id = this.preview.pkgId
|
const id = this.preview.pkgId
|
||||||
const version = this.version() || ''
|
|
||||||
const breakages = dryUpdate(
|
const breakages = dryUpdate(
|
||||||
{ id, version },
|
{ id, version: this.version() },
|
||||||
await getAllPackages(this.patch),
|
await getAllPackages(this.patch),
|
||||||
this.exver,
|
this.exver,
|
||||||
)
|
)
|
||||||
@@ -187,7 +176,7 @@ export class MarketplaceControlsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async installOrUpload(url: string | null) {
|
private async installOrUpload(url: string | null) {
|
||||||
if (this.file) {
|
if (this.file()) {
|
||||||
await this.upload()
|
await this.upload()
|
||||||
this.router.navigate(['/portal', 'services'])
|
this.router.navigate(['/portal', 'services'])
|
||||||
} else if (url) {
|
} else if (url) {
|
||||||
@@ -197,11 +186,10 @@ export class MarketplaceControlsComponent {
|
|||||||
|
|
||||||
private async install(url: string) {
|
private async install(url: string) {
|
||||||
const loader = this.loader.open('Beginning install').subscribe()
|
const loader = this.loader.open('Beginning install').subscribe()
|
||||||
const version = this.version() || ''
|
|
||||||
const id = this.preview.pkgId
|
const id = this.preview.pkgId
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.marketplaceService.installPackage(id, version, url)
|
await this.marketplaceService.installPackage(id, this.version(), url)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -210,11 +198,14 @@ export class MarketplaceControlsComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async upload() {
|
private async upload() {
|
||||||
|
const file = this.file()
|
||||||
|
if (!file) throw new Error('no file detected')
|
||||||
|
|
||||||
const loader = this.loader.open('Starting upload').subscribe()
|
const loader = this.loader.open('Starting upload').subscribe()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { upload } = await this.api.sideloadPackage()
|
const { upload } = await this.api.sideloadPackage()
|
||||||
this.api.uploadPackage(upload, this.file!).catch(console.error)
|
this.api.uploadPackage(upload, file).catch(console.error)
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errorService.handleError(e)
|
this.errorService.handleError(e)
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ import { MarketplaceControlsComponent } from './controls.component'
|
|||||||
<marketplace-controls
|
<marketplace-controls
|
||||||
slot="controls"
|
slot="controls"
|
||||||
class="controls-wrapper"
|
class="controls-wrapper"
|
||||||
[pkg]="pkg()"
|
[version]="pkg().version"
|
||||||
|
[installAlert]="pkg().alerts.install"
|
||||||
[localPkg]="local$ | async"
|
[localPkg]="local$ | async"
|
||||||
[localFlavor]="!!(flavor$ | async)"
|
[localFlavor]="!!(flavor$ | async)"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -62,9 +62,7 @@ export class MarketplaceAlertsService {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async alertInstall({ alerts }: MarketplacePkgBase): Promise<boolean> {
|
async alertInstall(content: string): Promise<boolean> {
|
||||||
const content = alerts.install
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
!content ||
|
!content ||
|
||||||
(!!content &&
|
(!!content &&
|
||||||
|
|||||||
@@ -90,17 +90,20 @@ export type PackageActionData = {
|
|||||||
`,
|
`,
|
||||||
styles: `
|
styles: `
|
||||||
tui-notification {
|
tui-notification {
|
||||||
font-size: 1rem;
|
margin-bottom: 1.5rem;
|
||||||
margin-bottom: 1.4rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.service-title {
|
.service-title {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: 1.4rem;
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
img {
|
img {
|
||||||
height: 20px;
|
height: 1.25rem;
|
||||||
margin-right: 4px;
|
margin-right: 0.25rem;
|
||||||
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
h4 {
|
h4 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
@@ -192,7 +195,7 @@ export class ActionInputModal {
|
|||||||
task.when?.condition === 'input-not-matches' &&
|
task.when?.condition === 'input-not-matches' &&
|
||||||
task.input &&
|
task.input &&
|
||||||
json
|
json
|
||||||
.compare(input, task.input)
|
.compare(input, task.input.value)
|
||||||
.some(op => op.op === 'add' || op.op === 'replace'),
|
.some(op => op.op === 'add' || op.op === 'replace'),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -201,9 +204,8 @@ export class ActionInputModal {
|
|||||||
if (!breakages.length) return true
|
if (!breakages.length) return true
|
||||||
|
|
||||||
const message = `${this.i18n.transform('As a result of this change, the following services will no longer work properly and may crash')}:<ul>`
|
const message = `${this.i18n.transform('As a result of this change, the following services will no longer work properly and may crash')}:<ul>`
|
||||||
const content = `${message}${breakages.map(
|
const content =
|
||||||
id => `<li><b>${getManifest(packages[id]!).title}</b></li>`,
|
`${message}${breakages.map(id => `<li><b>${getManifest(packages[id]!).title}</b></li>`)}</ul>` as i18nKey
|
||||||
)}</ul>` as i18nKey
|
|
||||||
|
|
||||||
return firstValueFrom(
|
return firstValueFrom(
|
||||||
this.dialog
|
this.dialog
|
||||||
|
|||||||
@@ -29,7 +29,8 @@ import { MarketplacePkgSideload } from './sideload.utils'
|
|||||||
<marketplace-controls
|
<marketplace-controls
|
||||||
slot="controls"
|
slot="controls"
|
||||||
class="controls-wrapper"
|
class="controls-wrapper"
|
||||||
[pkg]="pkg()"
|
[version]="pkg().version"
|
||||||
|
[installAlert]="pkg().alerts.install"
|
||||||
[localPkg]="local$ | async"
|
[localPkg]="local$ | async"
|
||||||
[localFlavor]="!!(flavor$ | async)"
|
[localFlavor]="!!(flavor$ | async)"
|
||||||
[file]="file()"
|
[file]="file()"
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
import { inject, Pipe, PipeTransform } from '@angular/core'
|
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||||
import { Exver } from '@start9labs/shared'
|
import { Exver } from '@start9labs/shared'
|
||||||
import { MarketplacePkg } from '@start9labs/marketplace'
|
import { MarketplacePkg } from '@start9labs/marketplace'
|
||||||
import {
|
import { PackageDataEntry } from 'src/app/services/patch-db/data-model'
|
||||||
InstalledState,
|
|
||||||
PackageDataEntry,
|
|
||||||
UpdatingState,
|
|
||||||
} from 'src/app/services/patch-db/data-model'
|
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'filterUpdates',
|
name: 'filterUpdates',
|
||||||
@@ -15,15 +11,14 @@ export class FilterUpdatesPipe implements PipeTransform {
|
|||||||
|
|
||||||
transform(
|
transform(
|
||||||
pkgs: MarketplacePkg[],
|
pkgs: MarketplacePkg[],
|
||||||
local: Record<
|
local: Record<string, PackageDataEntry> = {},
|
||||||
string,
|
|
||||||
PackageDataEntry<InstalledState | UpdatingState>
|
|
||||||
> = {},
|
|
||||||
): MarketplacePkg[] {
|
): MarketplacePkg[] {
|
||||||
return pkgs.filter(({ id, version, flavor }) => {
|
return pkgs.filter(({ id, flavor, version }) => {
|
||||||
const localPkg = local[id]
|
const localPkg = local[id]
|
||||||
return (
|
return (
|
||||||
localPkg &&
|
!!localPkg &&
|
||||||
|
(localPkg.stateInfo.state === 'installed' ||
|
||||||
|
localPkg.stateInfo.state === 'updating') &&
|
||||||
this.exver.getFlavor(localPkg.stateInfo.manifest.version) === flavor &&
|
this.exver.getFlavor(localPkg.stateInfo.manifest.version) === flavor &&
|
||||||
this.exver.compareExver(
|
this.exver.compareExver(
|
||||||
version,
|
version,
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import UpdatesComponent from './updates.component'
|
|||||||
template: `
|
template: `
|
||||||
<tr (click)="expanded.set(!expanded())">
|
<tr (click)="expanded.set(!expanded())">
|
||||||
<td>
|
<td>
|
||||||
<div [style.gap.rem]="0.75">
|
<div [style.gap.rem]="0.75" [style.padding-inline-end.rem]="1">
|
||||||
<tui-avatar size="s"><img alt="" [src]="item().icon" /></tui-avatar>
|
<tui-avatar size="s"><img alt="" [src]="item().icon" /></tui-avatar>
|
||||||
<span tuiTitle [style.margin]="'-0.125rem 0 0'">
|
<span tuiTitle [style.margin]="'-0.125rem 0 0'">
|
||||||
<b tuiFade>{{ item().title }}</b>
|
<b tuiFade>{{ item().title }}</b>
|
||||||
@@ -81,7 +81,6 @@ import UpdatesComponent from './updates.component'
|
|||||||
</button>
|
</button>
|
||||||
@if (local().stateInfo.state === 'updating') {
|
@if (local().stateInfo.state === 'updating') {
|
||||||
<tui-progress-circle
|
<tui-progress-circle
|
||||||
class="g-positive"
|
|
||||||
size="xs"
|
size="xs"
|
||||||
[max]="100"
|
[max]="100"
|
||||||
[value]="
|
[value]="
|
||||||
@@ -175,7 +174,7 @@ import UpdatesComponent from './updates.component'
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
|
||||||
div {
|
div {
|
||||||
justify-content: flex-end;
|
justify-content: flex-start;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { inject, Injectable } from '@angular/core'
|
import { inject, Injectable } from '@angular/core'
|
||||||
import { Exver } from '@start9labs/shared'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import {
|
import {
|
||||||
combineLatest,
|
combineLatest,
|
||||||
@@ -19,13 +18,13 @@ import { MarketplaceService } from 'src/app/services/marketplace.service'
|
|||||||
import { NotificationService } from 'src/app/services/notification.service'
|
import { NotificationService } from 'src/app/services/notification.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { getManifest } from 'src/app/utils/get-package-data'
|
import { getManifest } from 'src/app/utils/get-package-data'
|
||||||
|
import { FilterUpdatesPipe } from '../routes/portal/routes/updates/filter-updates.pipe'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
})
|
})
|
||||||
export class BadgeService {
|
export class BadgeService {
|
||||||
private readonly notifications = inject(NotificationService)
|
private readonly notifications = inject(NotificationService)
|
||||||
private readonly exver = inject(Exver)
|
|
||||||
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
private readonly patch = inject<PatchDB<DataModel>>(PatchDB)
|
||||||
private readonly system$ = inject(OSService).updateAvailable$.pipe(
|
private readonly system$ = inject(OSService).updateAvailable$.pipe(
|
||||||
map(Number),
|
map(Number),
|
||||||
@@ -34,6 +33,7 @@ export class BadgeService {
|
|||||||
.watch$('serverInfo', 'ntpSynced')
|
.watch$('serverInfo', 'ntpSynced')
|
||||||
.pipe(map(synced => Number(!synced)))
|
.pipe(map(synced => Number(!synced)))
|
||||||
private readonly marketplaceService = inject(MarketplaceService)
|
private readonly marketplaceService = inject(MarketplaceService)
|
||||||
|
private readonly filterUpdatesPipe = inject(FilterUpdatesPipe)
|
||||||
|
|
||||||
private readonly local$ = inject(ConnectionService).pipe(
|
private readonly local$ = inject(ConnectionService).pipe(
|
||||||
filter(Boolean),
|
filter(Boolean),
|
||||||
@@ -66,17 +66,9 @@ export class BadgeService {
|
|||||||
([marketplace, local]) =>
|
([marketplace, local]) =>
|
||||||
Object.entries(marketplace).reduce(
|
Object.entries(marketplace).reduce(
|
||||||
(list, [_, store]) =>
|
(list, [_, store]) =>
|
||||||
store?.packages.reduce(
|
this.filterUpdatesPipe
|
||||||
(result, { id, version }) =>
|
.transform(store?.packages || [], local)
|
||||||
local[id] &&
|
.reduce((result, { id }) => result.add(id), list),
|
||||||
this.exver.compareExver(
|
|
||||||
version,
|
|
||||||
getManifest(local[id]!).version,
|
|
||||||
) === 1
|
|
||||||
? result.add(id)
|
|
||||||
: result,
|
|
||||||
list,
|
|
||||||
) || list,
|
|
||||||
new Set<string>(),
|
new Set<string>(),
|
||||||
).size,
|
).size,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -57,6 +57,10 @@ export class StandardActionsService {
|
|||||||
content = `${content}${content ? ' ' : ''}${this.i18n.transform('Services that depend on')} ${title} ${this.i18n.transform('will no longer work properly and may crash.')}`
|
content = `${content}${content ? ' ' : ''}${this.i18n.transform('Services that depend on')} ${title} ${this.i18n.transform('will no longer work properly and may crash.')}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!content) {
|
||||||
|
return this.doUninstall({ id, force, soft })
|
||||||
|
}
|
||||||
|
|
||||||
this.dialog
|
this.dialog
|
||||||
.openConfirm({
|
.openConfirm({
|
||||||
label: 'Warning',
|
label: 'Warning',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { inject, Injectable } from '@angular/core'
|
import { inject, Injectable } from '@angular/core'
|
||||||
import { CanActivateFn, IsActiveMatchOptions, Router } from '@angular/router'
|
import { CanActivateFn, IsActiveMatchOptions, Router } from '@angular/router'
|
||||||
import { DialogService, i18nPipe } from '@start9labs/shared'
|
import { i18nPipe } from '@start9labs/shared'
|
||||||
import { TUI_TRUE_HANDLER } from '@taiga-ui/cdk'
|
import { TUI_TRUE_HANDLER } from '@taiga-ui/cdk'
|
||||||
import { TuiAlertService } from '@taiga-ui/core'
|
import { TuiAlertService } from '@taiga-ui/core'
|
||||||
import {
|
import {
|
||||||
@@ -47,9 +47,7 @@ export class StateService extends Observable<RR.ServerState | null> {
|
|||||||
private readonly api = inject(ApiService)
|
private readonly api = inject(ApiService)
|
||||||
private readonly router = inject(Router)
|
private readonly router = inject(Router)
|
||||||
private readonly network$ = inject(NetworkService)
|
private readonly network$ = inject(NetworkService)
|
||||||
|
|
||||||
private readonly single$ = new Subject<RR.ServerState>()
|
private readonly single$ = new Subject<RR.ServerState>()
|
||||||
|
|
||||||
private readonly trigger$ = new BehaviorSubject<void>(undefined)
|
private readonly trigger$ = new BehaviorSubject<void>(undefined)
|
||||||
private readonly poll$ = this.trigger$.pipe(
|
private readonly poll$ = this.trigger$.pipe(
|
||||||
switchMap(() =>
|
switchMap(() =>
|
||||||
@@ -101,7 +99,7 @@ export class StateService extends Observable<RR.ServerState | null> {
|
|||||||
})
|
})
|
||||||
.pipe(
|
.pipe(
|
||||||
takeUntil(
|
takeUntil(
|
||||||
combineLatest([this.stream$, this.network$]).pipe(
|
combineLatest([this.stream$.pipe(skip(1)), this.network$]).pipe(
|
||||||
filter(state => state.every(Boolean)),
|
filter(state => state.every(Boolean)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,22 +1,16 @@
|
|||||||
{
|
{
|
||||||
"/rpc/v1": {
|
"/rpc/v1": {
|
||||||
"target": "http://<CHANGE_ME>/rpc/v1"
|
"target": "http://<CHANGE_ME>"
|
||||||
},
|
},
|
||||||
"/ws/*": {
|
"/ws/**": {
|
||||||
"target": "http://<CHANGE_ME>",
|
"target": "wss://159.223.45.210",
|
||||||
"secure": false,
|
"secure": false,
|
||||||
"ws": true
|
"ws": true
|
||||||
},
|
},
|
||||||
"/public/*": {
|
"/public/**": {
|
||||||
"target": "http://<CHANGE_ME>/public",
|
"target": "http://<CHANGE_ME>"
|
||||||
"pathRewrite": {
|
|
||||||
"^/public": ""
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"/rest/rpc/*": {
|
"/rest/rpc/**": {
|
||||||
"target": "http://<CHANGE_ME>/rest/rpc",
|
"target": "http://<CHANGE_ME>"
|
||||||
"pathRewrite": {
|
|
||||||
"^/rest/rpc": ""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user