mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 10:21:52 +00:00
Compare commits
4 Commits
v0.4.0-alp
...
v0.4.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24eb27f005 | ||
|
|
009d76ea35 | ||
|
|
6e8a425eb1 | ||
|
|
66188d791b |
4
.github/workflows/start-tunnel.yaml
vendored
4
.github/workflows/start-tunnel.yaml
vendored
@@ -41,7 +41,7 @@ env:
|
||||
|
||||
jobs:
|
||||
compile:
|
||||
name: Compile Base Binaries
|
||||
name: Build Debian Package
|
||||
strategy:
|
||||
fail-fast: true
|
||||
matrix:
|
||||
@@ -97,4 +97,4 @@ jobs:
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: start-tunnel_${{ matrix.arch }}.deb
|
||||
path: start-tunnel-*_${{ matrix.arch }}.deb
|
||||
path: results/start-tunnel-*_${{ matrix.arch }}.deb
|
||||
|
||||
201
agents/VERSION_BUMP.md
Normal file
201
agents/VERSION_BUMP.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# StartOS Version Bump Guide
|
||||
|
||||
This document explains how to bump the StartOS version across the entire codebase.
|
||||
|
||||
## Overview
|
||||
|
||||
When bumping from version `X.Y.Z-alpha.N` to `X.Y.Z-alpha.N+1`, you need to update files in multiple locations across the repository. The `// VERSION_BUMP` comment markers indicate where changes are needed.
|
||||
|
||||
## Files to Update
|
||||
|
||||
### 1. Core Rust Crate Version
|
||||
|
||||
**File: `core/startos/Cargo.toml`**
|
||||
|
||||
Update the version string (line ~18):
|
||||
|
||||
```toml
|
||||
version = "0.4.0-alpha.15" # VERSION_BUMP
|
||||
```
|
||||
|
||||
**File: `core/Cargo.lock`**
|
||||
|
||||
This file is auto-generated. After updating `Cargo.toml`, run:
|
||||
|
||||
```bash
|
||||
cd core
|
||||
cargo check
|
||||
```
|
||||
|
||||
This will update the version in `Cargo.lock` automatically.
|
||||
|
||||
### 2. Create New Version Migration Module
|
||||
|
||||
**File: `core/startos/src/version/vX_Y_Z_alpha_N+1.rs`**
|
||||
|
||||
Create a new version file by copying the previous version and updating:
|
||||
|
||||
```rust
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{VersionT, v0_4_0_alpha_14}; // Update to previous version
|
||||
use crate::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref V0_4_0_alpha_15: exver::Version = exver::Version::new(
|
||||
[0, 4, 0],
|
||||
[PreReleaseSegment::String("alpha".into()), 15.into()] // Update number
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Version;
|
||||
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_4_0_alpha_14::Version; // Update to previous version
|
||||
type PreUpRes = ();
|
||||
|
||||
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn semver(self) -> exver::Version {
|
||||
V0_4_0_alpha_15.clone() // Update version name
|
||||
}
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||
// Add migration logic here if needed
|
||||
Ok(Value::Null)
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
// Add rollback logic here if needed
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Update Version Module Registry
|
||||
|
||||
**File: `core/startos/src/version/mod.rs`**
|
||||
|
||||
Make changes in **5 locations**:
|
||||
|
||||
#### Location 1: Module Declaration (~line 57)
|
||||
|
||||
Add the new module after the previous version:
|
||||
|
||||
```rust
|
||||
mod v0_4_0_alpha_14;
|
||||
mod v0_4_0_alpha_15; // Add this
|
||||
```
|
||||
|
||||
#### Location 2: Current Type Alias (~line 59)
|
||||
|
||||
Update the `Current` type and move the `// VERSION_BUMP` comment:
|
||||
|
||||
```rust
|
||||
pub type Current = v0_4_0_alpha_15::Version; // VERSION_BUMP
|
||||
```
|
||||
|
||||
#### Location 3: Version Enum (~line 175)
|
||||
|
||||
Remove `// VERSION_BUMP` from the previous version, add new variant, add comment:
|
||||
|
||||
```rust
|
||||
V0_4_0_alpha_14(Wrapper<v0_4_0_alpha_14::Version>),
|
||||
V0_4_0_alpha_15(Wrapper<v0_4_0_alpha_15::Version>), // VERSION_BUMP
|
||||
Other(exver::Version),
|
||||
```
|
||||
|
||||
#### Location 4: as_version_t() Match (~line 233)
|
||||
|
||||
Remove `// VERSION_BUMP`, add new match arm, add comment:
|
||||
|
||||
```rust
|
||||
Self::V0_4_0_alpha_14(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_15(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||
Self::Other(v) => {
|
||||
```
|
||||
|
||||
#### Location 5: as_exver() Match (~line 284, inside #[cfg(test)])
|
||||
|
||||
Remove `// VERSION_BUMP`, add new match arm, add comment:
|
||||
|
||||
```rust
|
||||
Version::V0_4_0_alpha_14(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_15(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||
Version::Other(x) => x.clone(),
|
||||
```
|
||||
|
||||
### 4. SDK TypeScript Version
|
||||
|
||||
**File: `sdk/package/lib/StartSdk.ts`**
|
||||
|
||||
Update the OSVersion constant (~line 64):
|
||||
|
||||
```typescript
|
||||
export const OSVersion = testTypeVersion("0.4.0-alpha.15");
|
||||
```
|
||||
|
||||
### 5. Web UI Package Version
|
||||
|
||||
**File: `web/package.json`**
|
||||
|
||||
Update the version field:
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "startos-ui",
|
||||
"version": "0.4.0-alpha.15",
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
**File: `web/package-lock.json`**
|
||||
|
||||
This file is auto-generated, but it's faster to update manually. Find all instances of "startos-ui" and update the version field.
|
||||
|
||||
## Verification Step
|
||||
|
||||
```
|
||||
make
|
||||
```
|
||||
|
||||
## VERSION_BUMP Comment Pattern
|
||||
|
||||
The `// VERSION_BUMP` comment serves as a marker for where to make changes next time:
|
||||
|
||||
- Always **remove** it from the old location
|
||||
- **Add** the new version entry
|
||||
- **Move** the comment to mark the new location
|
||||
|
||||
This pattern helps you quickly find all the places that need updating in the next version bump.
|
||||
|
||||
## Summary Checklist
|
||||
|
||||
- [ ] Update `core/startos/Cargo.toml` version
|
||||
- [ ] Create new `core/startos/src/version/vX_Y_Z_alpha_N+1.rs` file
|
||||
- [ ] Update `core/startos/src/version/mod.rs` in 5 locations
|
||||
- [ ] Run `cargo check` to update `core/Cargo.lock`
|
||||
- [ ] Update `sdk/package/lib/StartSdk.ts` OSVersion
|
||||
- [ ] Update `web/package.json` and `web/package-lock.json` version
|
||||
- [ ] Verify all changes compile/build successfully
|
||||
|
||||
## Migration Logic
|
||||
|
||||
The `up()` and `down()` methods in the version file handle database migrations:
|
||||
|
||||
- **up()**: Migrates the database from the previous version to this version
|
||||
- **down()**: Rolls back from this version to the previous version
|
||||
- **pre_up()**: Runs before migration, useful for pre-migration checks or data gathering
|
||||
|
||||
If no migration is needed, return `Ok(Value::Null)` for `up()` and `Ok(())` for `down()`.
|
||||
|
||||
For complex migrations, you may need to:
|
||||
|
||||
1. Update `type PreUpRes` to pass data between `pre_up()` and `up()`
|
||||
2. Implement database transformations in the `up()` method
|
||||
3. Implement reverse transformations in `down()` for rollback support
|
||||
@@ -61,7 +61,7 @@ fi
|
||||
|
||||
chroot /media/startos/next bash -e << "EOF"
|
||||
|
||||
if dpkg -s grub-common 2>&1 > /dev/null; then
|
||||
if [ -f /boot/grub/grub.cfg ]; then
|
||||
grub-install /dev/$(eval $(lsblk -o MOUNTPOINT,PKNAME -P | grep 'MOUNTPOINT="/media/startos/root"') && echo $PKNAME)
|
||||
update-grub
|
||||
fi
|
||||
@@ -70,7 +70,7 @@ EOF
|
||||
|
||||
sync
|
||||
|
||||
umount -R /media/startos/next
|
||||
umount -Rl /media/startos/next
|
||||
umount /media/startos/upper
|
||||
umount /media/startos/lower
|
||||
|
||||
|
||||
2
core/Cargo.lock
generated
2
core/Cargo.lock
generated
@@ -7908,7 +7908,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "start-os"
|
||||
version = "0.4.0-alpha.14"
|
||||
version = "0.4.0-alpha.15"
|
||||
dependencies = [
|
||||
"aes 0.7.5",
|
||||
"arti-client",
|
||||
|
||||
@@ -15,7 +15,7 @@ license = "MIT"
|
||||
name = "start-os"
|
||||
readme = "README.md"
|
||||
repository = "https://github.com/Start9Labs/start-os"
|
||||
version = "0.4.0-alpha.14" # VERSION_BUMP
|
||||
version = "0.4.0-alpha.15" # VERSION_BUMP
|
||||
|
||||
[lib]
|
||||
name = "startos"
|
||||
|
||||
@@ -29,6 +29,7 @@ use crate::registry::context::{RegistryContext, RegistryUrlParams};
|
||||
use crate::registry::package::get::GetPackageResponse;
|
||||
use crate::rpc_continuations::{Guid, RpcContinuation};
|
||||
use crate::s9pk::manifest::PackageId;
|
||||
use crate::s9pk::v2::SIG_CONTEXT;
|
||||
use crate::upload::upload;
|
||||
use crate::util::Never;
|
||||
use crate::util::io::open_file;
|
||||
@@ -154,6 +155,8 @@ pub async fn install(
|
||||
})?
|
||||
.s9pk;
|
||||
|
||||
asset.validate(SIG_CONTEXT, asset.all_signers())?;
|
||||
|
||||
let progress_tracker = FullProgressTracker::new();
|
||||
let download_progress = progress_tracker.add_phase("Downloading".into(), Some(100));
|
||||
let download = ctx
|
||||
|
||||
@@ -217,10 +217,15 @@ where
|
||||
.write_all(&buffered)
|
||||
.await
|
||||
.with_kind(ErrorKind::Network)?;
|
||||
return Ok(Some((
|
||||
metadata,
|
||||
Box::pin(mid.into_stream(Arc::new(cfg)).await?) as AcceptStream,
|
||||
)));
|
||||
let stream = match mid.into_stream(Arc::new(cfg)).await {
|
||||
Ok(stream) => Box::pin(stream) as AcceptStream,
|
||||
Err(e) => {
|
||||
tracing::trace!("Error completing TLS handshake: {e}");
|
||||
tracing::trace!("{e:?}");
|
||||
return Ok(None);
|
||||
}
|
||||
};
|
||||
return Ok(Some((metadata, stream)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
|
||||
@@ -39,6 +39,23 @@ pub struct AddTunnelParams {
|
||||
public: bool,
|
||||
}
|
||||
|
||||
fn sanitize_config(config: &str) -> String {
|
||||
let mut res = String::with_capacity(config.len());
|
||||
for line in config.lines() {
|
||||
if line
|
||||
.trim()
|
||||
.strip_prefix("AllowedIPs")
|
||||
.map_or(false, |l| l.trim().starts_with("="))
|
||||
{
|
||||
res.push_str("AllowedIPs = 0.0.0.0/0, ::/0");
|
||||
} else {
|
||||
res.push_str(line);
|
||||
}
|
||||
res.push('\n');
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub async fn add_tunnel(
|
||||
ctx: RpcContext,
|
||||
AddTunnelParams {
|
||||
@@ -86,7 +103,7 @@ pub async fn add_tunnel(
|
||||
|
||||
let tmpdir = TmpDir::new().await?;
|
||||
let conf = tmpdir.join(&iface).with_extension("conf");
|
||||
write_file_atomic(&conf, &config).await?;
|
||||
write_file_atomic(&conf, &sanitize_config(&config)).await?;
|
||||
Command::new("nmcli")
|
||||
.arg("connection")
|
||||
.arg("import")
|
||||
|
||||
@@ -353,7 +353,7 @@ impl FullProgressTracker {
|
||||
}
|
||||
}
|
||||
pub fn progress_bar_task(&self, name: &str) -> NonDetachingJoinHandle<()> {
|
||||
let mut stream = self.stream(None);
|
||||
let mut stream = self.stream(Some(Duration::from_millis(200)));
|
||||
let mut bar = PhasedProgressBar::new(name);
|
||||
tokio::spawn(async move {
|
||||
while let Some(progress) = stream.next().await {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
@@ -84,6 +85,26 @@ impl RegistryAsset<MerkleArchiveCommitment> {
|
||||
)
|
||||
.await
|
||||
}
|
||||
pub async fn download_to(
|
||||
&self,
|
||||
path: impl AsRef<Path>,
|
||||
client: Client,
|
||||
progress: PhaseProgressTrackerHandle,
|
||||
) -> Result<
|
||||
(
|
||||
S9pk<Section<Arc<BufferedHttpSource>>>,
|
||||
Arc<BufferedHttpSource>,
|
||||
),
|
||||
Error,
|
||||
> {
|
||||
let source = Arc::new(
|
||||
BufferedHttpSource::with_path(path, client, self.url.clone(), progress).await?,
|
||||
);
|
||||
Ok((
|
||||
S9pk::deserialize(&source, Some(&self.commitment)).await?,
|
||||
source,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BufferedHttpSource {
|
||||
@@ -91,6 +112,19 @@ pub struct BufferedHttpSource {
|
||||
file: UploadingFile,
|
||||
}
|
||||
impl BufferedHttpSource {
|
||||
pub async fn with_path(
|
||||
path: impl AsRef<Path>,
|
||||
client: Client,
|
||||
url: Url,
|
||||
progress: PhaseProgressTrackerHandle,
|
||||
) -> Result<Self, Error> {
|
||||
let (mut handle, file) = UploadingFile::with_path(path, progress).await?;
|
||||
let response = client.get(url).send().await?;
|
||||
Ok(Self {
|
||||
_download: tokio::spawn(async move { handle.download(response).await }).into(),
|
||||
file,
|
||||
})
|
||||
}
|
||||
pub async fn new(
|
||||
client: Client,
|
||||
url: Url,
|
||||
@@ -103,6 +137,9 @@ impl BufferedHttpSource {
|
||||
file,
|
||||
})
|
||||
}
|
||||
pub async fn wait_for_buffered(&self) -> Result<(), Error> {
|
||||
self.file.wait_for_complete().await
|
||||
}
|
||||
}
|
||||
impl ArchiveSource for BufferedHttpSource {
|
||||
type FetchReader = <UploadingFile as ArchiveSource>::FetchReader;
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use clap::{Parser, ValueEnum};
|
||||
use exver::{ExtendedVersion, VersionRange};
|
||||
use imbl_value::InternedString;
|
||||
use helpers::to_tmp_path;
|
||||
use imbl_value::{InternedString, json};
|
||||
use itertools::Itertools;
|
||||
use models::PackageId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::context::CliContext;
|
||||
use crate::prelude::*;
|
||||
use crate::progress::{FullProgressTracker, ProgressUnits};
|
||||
use crate::registry::context::RegistryContext;
|
||||
use crate::registry::device_info::DeviceInfo;
|
||||
use crate::registry::package::index::{PackageIndex, PackageVersionInfo};
|
||||
use crate::s9pk::merkle_archive::source::ArchiveSource;
|
||||
use crate::s9pk::v2::SIG_CONTEXT;
|
||||
use crate::util::VersionString;
|
||||
use crate::util::io::TrackingIO;
|
||||
use crate::util::serde::{WithIoFormat, display_serializable};
|
||||
use crate::util::tui::choose;
|
||||
|
||||
#[derive(
|
||||
Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, TS, ValueEnum,
|
||||
@@ -352,8 +360,7 @@ pub fn display_package_info(
|
||||
info: Value,
|
||||
) -> Result<(), Error> {
|
||||
if let Some(format) = params.format {
|
||||
display_serializable(format, info);
|
||||
return Ok(());
|
||||
return display_serializable(format, info);
|
||||
}
|
||||
|
||||
if let Some(_) = params.rest.id {
|
||||
@@ -387,3 +394,90 @@ pub fn display_package_info(
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, TS, Parser)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct CliDownloadParams {
|
||||
pub id: PackageId,
|
||||
#[arg(long, short = 'v')]
|
||||
#[ts(type = "string | null")]
|
||||
pub target_version: Option<VersionRange>,
|
||||
#[arg(short, long)]
|
||||
pub dest: Option<PathBuf>,
|
||||
}
|
||||
|
||||
pub async fn cli_download(
|
||||
ctx: CliContext,
|
||||
CliDownloadParams {
|
||||
ref id,
|
||||
target_version,
|
||||
dest,
|
||||
}: CliDownloadParams,
|
||||
) -> Result<(), Error> {
|
||||
let progress_tracker = FullProgressTracker::new();
|
||||
let mut fetching_progress = progress_tracker.add_phase("Fetching".into(), Some(1));
|
||||
let download_progress = progress_tracker.add_phase("Downloading".into(), Some(100));
|
||||
let mut verify_progress = progress_tracker.add_phase("Verifying".into(), Some(10));
|
||||
|
||||
let progress = progress_tracker.progress_bar_task("Downloading S9PK...");
|
||||
|
||||
fetching_progress.start();
|
||||
let mut res: GetPackageResponse = from_value(
|
||||
ctx.call_remote::<RegistryContext>(
|
||||
"package.get",
|
||||
json!({
|
||||
"id": &id,
|
||||
"targetVersion": &target_version,
|
||||
}),
|
||||
)
|
||||
.await?,
|
||||
)?;
|
||||
let PackageVersionInfo { s9pk, .. } = match res.best.len() {
|
||||
0 => {
|
||||
return Err(Error::new(
|
||||
eyre!(
|
||||
"Could not find a version of {id} that satisfies {}",
|
||||
target_version.unwrap_or(VersionRange::Any)
|
||||
),
|
||||
ErrorKind::NotFound,
|
||||
));
|
||||
}
|
||||
1 => res.best.pop_first().unwrap().1,
|
||||
_ => {
|
||||
let choices = res.best.keys().cloned().collect::<Vec<_>>();
|
||||
let version = choose(
|
||||
&format!("Multiple flavors of {id} available. Choose a version to download:"),
|
||||
&choices,
|
||||
)
|
||||
.await?;
|
||||
res.best.remove(version).unwrap()
|
||||
}
|
||||
};
|
||||
s9pk.validate(SIG_CONTEXT, s9pk.all_signers())?;
|
||||
fetching_progress.complete();
|
||||
|
||||
let dest = dest.unwrap_or_else(|| Path::new(".").join(id).with_extension("s9pk"));
|
||||
let dest_tmp = to_tmp_path(&dest).with_kind(ErrorKind::Filesystem)?;
|
||||
let (mut parsed, source) = s9pk
|
||||
.download_to(&dest_tmp, ctx.client.clone(), download_progress)
|
||||
.await?;
|
||||
if let Some(size) = source.size().await {
|
||||
verify_progress.set_total(size);
|
||||
}
|
||||
verify_progress.set_units(Some(ProgressUnits::Bytes));
|
||||
let mut progress_sink = verify_progress.writer(tokio::io::sink());
|
||||
parsed
|
||||
.serialize(&mut TrackingIO::new(0, &mut progress_sink), true)
|
||||
.await?;
|
||||
progress_sink.into_inner().1.complete();
|
||||
|
||||
source.wait_for_buffered().await?;
|
||||
tokio::fs::rename(dest_tmp, dest).await?;
|
||||
|
||||
progress_tracker.complete();
|
||||
progress.await.unwrap();
|
||||
|
||||
println!("Download Complete");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async};
|
||||
use rpc_toolkit::{Context, HandlerExt, ParentHandler, from_fn_async, from_fn_async_local};
|
||||
|
||||
use crate::context::CliContext;
|
||||
use crate::prelude::*;
|
||||
@@ -54,6 +54,12 @@ pub fn package_api<C: Context>() -> ParentHandler<C> {
|
||||
.with_about("List installation candidate package(s)")
|
||||
.with_call_remote::<CliContext>(),
|
||||
)
|
||||
.subcommand(
|
||||
"download",
|
||||
from_fn_async_local(get::cli_download)
|
||||
.no_display()
|
||||
.with_about("Download an s9pk"),
|
||||
)
|
||||
.subcommand(
|
||||
"category",
|
||||
category::category_api::<C>()
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::io::SeekFrom;
|
||||
use std::path::Path;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::Poll;
|
||||
@@ -56,6 +57,7 @@ struct Progress {
|
||||
tracker: PhaseProgressTrackerHandle,
|
||||
expected_size: Option<u64>,
|
||||
written: u64,
|
||||
complete: bool,
|
||||
error: Option<Error>,
|
||||
}
|
||||
impl Progress {
|
||||
@@ -111,9 +113,7 @@ impl Progress {
|
||||
}
|
||||
async fn ready(watch: &mut watch::Receiver<Self>) -> Result<(), Error> {
|
||||
match &*watch
|
||||
.wait_for(|progress| {
|
||||
progress.error.is_some() || Some(progress.written) == progress.expected_size
|
||||
})
|
||||
.wait_for(|progress| progress.error.is_some() || progress.complete)
|
||||
.await
|
||||
.map_err(|_| {
|
||||
Error::new(
|
||||
@@ -126,8 +126,9 @@ impl Progress {
|
||||
}
|
||||
}
|
||||
fn complete(&mut self) -> bool {
|
||||
let mut changed = !self.complete;
|
||||
self.tracker.complete();
|
||||
match self {
|
||||
changed |= match self {
|
||||
Self {
|
||||
expected_size: Some(size),
|
||||
written,
|
||||
@@ -165,18 +166,21 @@ impl Progress {
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
};
|
||||
self.complete = true;
|
||||
changed
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UploadingFile {
|
||||
tmp_dir: Arc<TmpDir>,
|
||||
tmp_dir: Option<Arc<TmpDir>>,
|
||||
file: MultiCursorFile,
|
||||
progress: watch::Receiver<Progress>,
|
||||
}
|
||||
impl UploadingFile {
|
||||
pub async fn new(
|
||||
pub async fn with_path(
|
||||
path: impl AsRef<Path>,
|
||||
mut progress: PhaseProgressTrackerHandle,
|
||||
) -> Result<(UploadHandle, Self), Error> {
|
||||
progress.set_units(Some(ProgressUnits::Bytes));
|
||||
@@ -185,25 +189,35 @@ impl UploadingFile {
|
||||
expected_size: None,
|
||||
written: 0,
|
||||
error: None,
|
||||
complete: false,
|
||||
});
|
||||
let tmp_dir = Arc::new(TmpDir::new().await?);
|
||||
let file = create_file(tmp_dir.join("upload.tmp")).await?;
|
||||
let file = create_file(path).await?;
|
||||
let uploading = Self {
|
||||
tmp_dir: tmp_dir.clone(),
|
||||
tmp_dir: None,
|
||||
file: MultiCursorFile::open(&file).await?,
|
||||
progress: progress.1,
|
||||
};
|
||||
Ok((
|
||||
UploadHandle {
|
||||
tmp_dir,
|
||||
tmp_dir: None,
|
||||
file,
|
||||
progress: progress.0,
|
||||
},
|
||||
uploading,
|
||||
))
|
||||
}
|
||||
pub async fn new(progress: PhaseProgressTrackerHandle) -> Result<(UploadHandle, Self), Error> {
|
||||
let tmp_dir = Arc::new(TmpDir::new().await?);
|
||||
let (mut handle, mut file) = Self::with_path(tmp_dir.join("upload.tmp"), progress).await?;
|
||||
handle.tmp_dir = Some(tmp_dir.clone());
|
||||
file.tmp_dir = Some(tmp_dir);
|
||||
Ok((handle, file))
|
||||
}
|
||||
pub async fn wait_for_complete(&self) -> Result<(), Error> {
|
||||
Progress::ready(&mut self.progress.clone()).await
|
||||
}
|
||||
pub async fn delete(self) -> Result<(), Error> {
|
||||
if let Ok(tmp_dir) = Arc::try_unwrap(self.tmp_dir) {
|
||||
if let Some(Ok(tmp_dir)) = self.tmp_dir.map(Arc::try_unwrap) {
|
||||
tmp_dir.delete().await?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -234,7 +248,7 @@ impl ArchiveSource for UploadingFile {
|
||||
|
||||
#[pin_project::pin_project(project = UploadingFileReaderProjection)]
|
||||
pub struct UploadingFileReader {
|
||||
tmp_dir: Arc<TmpDir>,
|
||||
tmp_dir: Option<Arc<TmpDir>>,
|
||||
position: u64,
|
||||
to_seek: Option<SeekFrom>,
|
||||
#[pin]
|
||||
@@ -330,7 +344,7 @@ impl AsyncSeek for UploadingFileReader {
|
||||
|
||||
#[pin_project::pin_project(PinnedDrop)]
|
||||
pub struct UploadHandle {
|
||||
tmp_dir: Arc<TmpDir>,
|
||||
tmp_dir: Option<Arc<TmpDir>>,
|
||||
#[pin]
|
||||
file: File,
|
||||
progress: watch::Sender<Progress>,
|
||||
@@ -377,6 +391,9 @@ impl UploadHandle {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if let Err(e) = self.file.sync_all().await {
|
||||
self.progress.send_if_modified(|p| p.handle_error(&e));
|
||||
}
|
||||
}
|
||||
}
|
||||
#[pin_project::pinned_drop]
|
||||
|
||||
@@ -54,8 +54,9 @@ mod v0_4_0_alpha_11;
|
||||
mod v0_4_0_alpha_12;
|
||||
mod v0_4_0_alpha_13;
|
||||
mod v0_4_0_alpha_14;
|
||||
mod v0_4_0_alpha_15;
|
||||
|
||||
pub type Current = v0_4_0_alpha_14::Version; // VERSION_BUMP
|
||||
pub type Current = v0_4_0_alpha_15::Version; // VERSION_BUMP
|
||||
|
||||
impl Current {
|
||||
#[instrument(skip(self, db))]
|
||||
@@ -171,7 +172,8 @@ enum Version {
|
||||
V0_4_0_alpha_11(Wrapper<v0_4_0_alpha_11::Version>),
|
||||
V0_4_0_alpha_12(Wrapper<v0_4_0_alpha_12::Version>),
|
||||
V0_4_0_alpha_13(Wrapper<v0_4_0_alpha_13::Version>),
|
||||
V0_4_0_alpha_14(Wrapper<v0_4_0_alpha_14::Version>), // VERSION_BUMP
|
||||
V0_4_0_alpha_14(Wrapper<v0_4_0_alpha_14::Version>),
|
||||
V0_4_0_alpha_15(Wrapper<v0_4_0_alpha_15::Version>), // VERSION_BUMP
|
||||
Other(exver::Version),
|
||||
}
|
||||
|
||||
@@ -228,7 +230,8 @@ impl Version {
|
||||
Self::V0_4_0_alpha_11(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_12(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_13(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_14(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||
Self::V0_4_0_alpha_14(v) => DynVersion(Box::new(v.0)),
|
||||
Self::V0_4_0_alpha_15(v) => DynVersion(Box::new(v.0)), // VERSION_BUMP
|
||||
Self::Other(v) => {
|
||||
return Err(Error::new(
|
||||
eyre!("unknown version {v}"),
|
||||
@@ -277,7 +280,8 @@ impl Version {
|
||||
Version::V0_4_0_alpha_11(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_12(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_13(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_14(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||
Version::V0_4_0_alpha_14(Wrapper(x)) => x.semver(),
|
||||
Version::V0_4_0_alpha_15(Wrapper(x)) => x.semver(), // VERSION_BUMP
|
||||
Version::Other(x) => x.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
37
core/startos/src/version/v0_4_0_alpha_15.rs
Normal file
37
core/startos/src/version/v0_4_0_alpha_15.rs
Normal file
@@ -0,0 +1,37 @@
|
||||
use exver::{PreReleaseSegment, VersionRange};
|
||||
|
||||
use super::v0_3_5::V0_3_0_COMPAT;
|
||||
use super::{VersionT, v0_4_0_alpha_14};
|
||||
use crate::prelude::*;
|
||||
|
||||
lazy_static::lazy_static! {
|
||||
static ref V0_4_0_alpha_15: exver::Version = exver::Version::new(
|
||||
[0, 4, 0],
|
||||
[PreReleaseSegment::String("alpha".into()), 15.into()]
|
||||
);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
pub struct Version;
|
||||
|
||||
impl VersionT for Version {
|
||||
type Previous = v0_4_0_alpha_14::Version;
|
||||
type PreUpRes = ();
|
||||
|
||||
async fn pre_up(self) -> Result<Self::PreUpRes, Error> {
|
||||
Ok(())
|
||||
}
|
||||
fn semver(self) -> exver::Version {
|
||||
V0_4_0_alpha_15.clone()
|
||||
}
|
||||
fn compat(self) -> &'static VersionRange {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||
Ok(Value::Null)
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -205,6 +205,7 @@ fi
|
||||
useradd --shell /bin/bash -G startos -m start9
|
||||
echo start9:embassy | chpasswd
|
||||
usermod -aG sudo start9
|
||||
usermod -aG systemd-journal start9
|
||||
|
||||
echo "start9 ALL=(ALL:ALL) NOPASSWD: ALL" | sudo tee "/etc/sudoers.d/010_start9-nopasswd"
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ import {
|
||||
} from "../../base/lib/inits"
|
||||
import { DropGenerator } from "../../base/lib/util/Drop"
|
||||
|
||||
export const OSVersion = testTypeVersion("0.4.0-alpha.14")
|
||||
export const OSVersion = testTypeVersion("0.4.0-alpha.15")
|
||||
|
||||
// prettier-ignore
|
||||
type AnyNeverCond<T extends any[], Then, Else> =
|
||||
|
||||
158
web/package-lock.json
generated
158
web/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "startos-ui",
|
||||
"version": "0.4.0-alpha.14",
|
||||
"version": "0.4.0-alpha.15",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "startos-ui",
|
||||
"version": "0.4.0-alpha.14",
|
||||
"version": "0.4.0-alpha.15",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^20.3.0",
|
||||
@@ -25,18 +25,18 @@
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@start9labs/argon2": "^0.3.0",
|
||||
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
||||
"@taiga-ui/addon-charts": "4.55.0",
|
||||
"@taiga-ui/addon-commerce": "4.55.0",
|
||||
"@taiga-ui/addon-mobile": "4.55.0",
|
||||
"@taiga-ui/addon-table": "4.55.0",
|
||||
"@taiga-ui/cdk": "4.55.0",
|
||||
"@taiga-ui/core": "4.55.0",
|
||||
"@taiga-ui/addon-charts": "4.62.0",
|
||||
"@taiga-ui/addon-commerce": "4.62.0",
|
||||
"@taiga-ui/addon-mobile": "4.62.0",
|
||||
"@taiga-ui/addon-table": "4.62.0",
|
||||
"@taiga-ui/cdk": "4.62.0",
|
||||
"@taiga-ui/core": "4.62.0",
|
||||
"@taiga-ui/dompurify": "4.1.11",
|
||||
"@taiga-ui/event-plugins": "4.7.0",
|
||||
"@taiga-ui/experimental": "4.55.0",
|
||||
"@taiga-ui/icons": "4.55.0",
|
||||
"@taiga-ui/kit": "4.55.0",
|
||||
"@taiga-ui/layout": "4.55.0",
|
||||
"@taiga-ui/experimental": "4.62.0",
|
||||
"@taiga-ui/icons": "4.62.0",
|
||||
"@taiga-ui/kit": "4.62.0",
|
||||
"@taiga-ui/layout": "4.62.0",
|
||||
"@taiga-ui/polymorpheus": "4.9.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"base64-js": "^1.5.1",
|
||||
@@ -3950,9 +3950,9 @@
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-charts": {
|
||||
"version": "4.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.55.0.tgz",
|
||||
"integrity": "sha512-Rwgcc7NaLm75GsHYhqbO4jgNhD1bGbA7Kk0sNFE+Tgz9+V3ARXMuBw7C3cU9UxLSFnOsXz9RYLosmZ3jAVlyuQ==",
|
||||
"version": "4.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-4.62.0.tgz",
|
||||
"integrity": "sha512-tCysUpzEHwRhK/p9hopkt0Jw4jcgA2cF8CYK8mDntghC+fNLnqCVUcrqFIC5plGabAo00WMEz+X+KyGvwvKaVg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -3961,15 +3961,15 @@
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@ng-web-apis/common": "^4.12.0",
|
||||
"@taiga-ui/cdk": "^4.55.0",
|
||||
"@taiga-ui/core": "^4.55.0",
|
||||
"@taiga-ui/cdk": "^4.62.0",
|
||||
"@taiga-ui/core": "^4.62.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-commerce": {
|
||||
"version": "4.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.55.0.tgz",
|
||||
"integrity": "sha512-eOOBkIJSsagtRkpRZ04xlL8ePIP01d4Xo264zSTg2SRxD6vwR/7/QJlf9108BvIJv/jfTpmFukLwSB9LazqmCw==",
|
||||
"version": "4.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-4.62.0.tgz",
|
||||
"integrity": "sha512-J4+bdHeDe2d7Uh8NNObLl4LzBhWLCdzxNHXPac1bMGB+3gX751Htc9px37FkVZlQGnxQATCbxAVXj7Zjveq/QQ==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -3979,22 +3979,22 @@
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@angular/forms": ">=16.0.0",
|
||||
"@maskito/angular": "^3.10.3",
|
||||
"@maskito/core": "^3.10.3",
|
||||
"@maskito/kit": "^3.10.3",
|
||||
"@maskito/angular": "^3.11.1",
|
||||
"@maskito/core": "^3.11.1",
|
||||
"@maskito/kit": "^3.11.1",
|
||||
"@ng-web-apis/common": "^4.12.0",
|
||||
"@taiga-ui/cdk": "^4.55.0",
|
||||
"@taiga-ui/core": "^4.55.0",
|
||||
"@taiga-ui/i18n": "^4.55.0",
|
||||
"@taiga-ui/kit": "^4.55.0",
|
||||
"@taiga-ui/cdk": "^4.62.0",
|
||||
"@taiga-ui/core": "^4.62.0",
|
||||
"@taiga-ui/i18n": "^4.62.0",
|
||||
"@taiga-ui/kit": "^4.62.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-mobile": {
|
||||
"version": "4.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.55.0.tgz",
|
||||
"integrity": "sha512-NQRozVKcXLs9/rd/s1yI7T5rIokzwHQ5IN/c3NLBUEka1iKUr1ZTch+g9CHJf8GTVB0uAwWKNCgX5LxtiSI5zg==",
|
||||
"version": "4.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-mobile/-/addon-mobile-4.62.0.tgz",
|
||||
"integrity": "sha512-seIBG4utgLq2xDJu+YDzksOsVi/V6vsTbm2bljgM1fIBZInbhqk95YOIFZDU9JXT1/vIShcqetavg1vHD1wdkQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4004,18 +4004,18 @@
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@ng-web-apis/common": "^4.12.0",
|
||||
"@taiga-ui/cdk": "^4.55.0",
|
||||
"@taiga-ui/core": "^4.55.0",
|
||||
"@taiga-ui/kit": "^4.55.0",
|
||||
"@taiga-ui/layout": "^4.55.0",
|
||||
"@taiga-ui/cdk": "^4.62.0",
|
||||
"@taiga-ui/core": "^4.62.0",
|
||||
"@taiga-ui/kit": "^4.62.0",
|
||||
"@taiga-ui/layout": "^4.62.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/addon-table": {
|
||||
"version": "4.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.55.0.tgz",
|
||||
"integrity": "sha512-K5qpOS0UQLDqruJXPNDic8scCRvO3oAN/ZCPQ9XOGDrcvydvo4AKUwjKRPj+pZ/z0ulxbwAAruFFFCvRNnbzaA==",
|
||||
"version": "4.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-table/-/addon-table-4.62.0.tgz",
|
||||
"integrity": "sha512-0rolnsO1puYwUK17si5OOpzFxiziS6/OSbpLOSKrVrMkCgsWCoNDvpgPIwtwS5Mq3iF5cwLfUPbDQM8saG7wxQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4024,18 +4024,18 @@
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@ng-web-apis/intersection-observer": "^4.12.0",
|
||||
"@taiga-ui/cdk": "^4.55.0",
|
||||
"@taiga-ui/core": "^4.55.0",
|
||||
"@taiga-ui/i18n": "^4.55.0",
|
||||
"@taiga-ui/kit": "^4.55.0",
|
||||
"@taiga-ui/cdk": "^4.62.0",
|
||||
"@taiga-ui/core": "^4.62.0",
|
||||
"@taiga-ui/i18n": "^4.62.0",
|
||||
"@taiga-ui/kit": "^4.62.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/cdk": {
|
||||
"version": "4.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.55.0.tgz",
|
||||
"integrity": "sha512-vA5nGyx+YIHR1xZeq5D9gSqTRQg74qhe1AOt5FlqFOC0P4LvmLkNg3De7AeahXALNSeRz/DYcqI7WuGo6xpcLQ==",
|
||||
"version": "4.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-4.62.0.tgz",
|
||||
"integrity": "sha512-KWPXEbCHtRp7aIet1L3PySdXpo5Aay4L/36jDzjiFZ/bcbuD2cY/3S2l68zpgv6ZksZA94DuCuaamSEwQIAtPw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -4065,9 +4065,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/core": {
|
||||
"version": "4.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.55.0.tgz",
|
||||
"integrity": "sha512-Z2ATVNmEAlHEk2cgs/tnS6qZML87IchkPDeRl6HQfBT2fjYVjh1oCzXL07t86Lv6tpvkllyUVqoBCTSvDXs9kA==",
|
||||
"version": "4.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-4.62.0.tgz",
|
||||
"integrity": "sha512-PQW10hFH50g8PgnJpPa/ZrGMWljhIsBHad/utvalmlv8wXQY24i8T1BjrGIOFPOjzs20NEwLOICHf7KdZUtiuA==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -4082,9 +4082,9 @@
|
||||
"@angular/router": ">=16.0.0",
|
||||
"@ng-web-apis/common": "^4.12.0",
|
||||
"@ng-web-apis/mutation-observer": "^4.12.0",
|
||||
"@taiga-ui/cdk": "^4.55.0",
|
||||
"@taiga-ui/cdk": "^4.62.0",
|
||||
"@taiga-ui/event-plugins": "^4.7.0",
|
||||
"@taiga-ui/i18n": "^4.55.0",
|
||||
"@taiga-ui/i18n": "^4.62.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
@@ -4120,9 +4120,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/experimental": {
|
||||
"version": "4.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-4.55.0.tgz",
|
||||
"integrity": "sha512-3zq2BTl+fE/N/tEr74TzWbyzT5OoS9YEApKobJehKivW5XxZmF/MDRWp45kSe4jDtVbJ2ueI0Jn8h0BDNykkcg==",
|
||||
"version": "4.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-4.62.0.tgz",
|
||||
"integrity": "sha512-EiL5wJ+9LSf0BfZcFX6ioCavLfx26v0BCOUXh52Rtczp85Uh2qTDt2feM0oBDB+0Kj74/+wqqiKi+s3B8ZV3WA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": ">=2.8.1"
|
||||
@@ -4130,19 +4130,19 @@
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@taiga-ui/addon-commerce": "^4.55.0",
|
||||
"@taiga-ui/cdk": "^4.55.0",
|
||||
"@taiga-ui/core": "^4.55.0",
|
||||
"@taiga-ui/kit": "^4.55.0",
|
||||
"@taiga-ui/layout": "^4.55.0",
|
||||
"@taiga-ui/addon-commerce": "^4.62.0",
|
||||
"@taiga-ui/cdk": "^4.62.0",
|
||||
"@taiga-ui/core": "^4.62.0",
|
||||
"@taiga-ui/kit": "^4.62.0",
|
||||
"@taiga-ui/layout": "^4.62.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/i18n": {
|
||||
"version": "4.59.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.59.0.tgz",
|
||||
"integrity": "sha512-IxPzqkORJlSqagvUdmqBL9fuvfRm/Ca/W/bCDEK2GN/4QtSZ0yFzAyQdWduoIJubqyEPMXRbXZGc7WBtDgAMIQ==",
|
||||
"version": "4.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-4.62.0.tgz",
|
||||
"integrity": "sha512-84hD1nI26EAYd5RUhFKxbg+8WKYhc0GBHyf8wfi15xuwaT6oh2gbJx7pNTlGN3klH4CeDB9HF998tkhieevqQw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -4155,18 +4155,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/icons": {
|
||||
"version": "4.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.55.0.tgz",
|
||||
"integrity": "sha512-sYqSG9wgUcwHBrDRnMhLCMEkvAAw/SZrvvq0jdY2oWGmKwAj/6WBt+wA+jnFkDDKEZ7mjzdPIQffpVaUjSwsiw==",
|
||||
"version": "4.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-4.62.0.tgz",
|
||||
"integrity": "sha512-vD+bJk3Wot/+NcbdPwAJGBnqXG6T1OJVeg2IkaEE6DBixwdwDpukZWiV9asXyXiJkyEpG2Ar7SASvdCYZEVlxw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/kit": {
|
||||
"version": "4.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.55.0.tgz",
|
||||
"integrity": "sha512-xTvi7viI+wI2ifPv2bsf8prhYWWS4g1lbx059jXV3f5Cttc0Xg6DEb6xpaQOf4loBkcrP2FzkA4njACUuiouzw==",
|
||||
"version": "4.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-4.62.0.tgz",
|
||||
"integrity": "sha512-tdEaXJTks1PZQJAwMiVQTZrtCpaLIYV6T9VdVPZUKAJXq7K6J2kcD0oIISjwE9rqgLVwqytMZrwHx1nSRzkb/A==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -4177,25 +4177,25 @@
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@angular/forms": ">=16.0.0",
|
||||
"@angular/router": ">=16.0.0",
|
||||
"@maskito/angular": "^3.10.3",
|
||||
"@maskito/core": "^3.10.3",
|
||||
"@maskito/kit": "^3.10.3",
|
||||
"@maskito/phone": "^3.10.3",
|
||||
"@maskito/angular": "^3.11.1",
|
||||
"@maskito/core": "^3.11.1",
|
||||
"@maskito/kit": "^3.11.1",
|
||||
"@maskito/phone": "^3.11.1",
|
||||
"@ng-web-apis/common": "^4.12.0",
|
||||
"@ng-web-apis/intersection-observer": "^4.12.0",
|
||||
"@ng-web-apis/mutation-observer": "^4.12.0",
|
||||
"@ng-web-apis/resize-observer": "^4.12.0",
|
||||
"@taiga-ui/cdk": "^4.55.0",
|
||||
"@taiga-ui/core": "^4.55.0",
|
||||
"@taiga-ui/i18n": "^4.55.0",
|
||||
"@taiga-ui/cdk": "^4.62.0",
|
||||
"@taiga-ui/core": "^4.62.0",
|
||||
"@taiga-ui/i18n": "^4.62.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@taiga-ui/layout": {
|
||||
"version": "4.55.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.55.0.tgz",
|
||||
"integrity": "sha512-C+e4gudZwjIc46VITil5vySas1FPpfe+D4uwLRggJOTuUosZlZHBuc51v91wCCc0pL0Xfu+TD0s8W9kRd1sQHA==",
|
||||
"version": "4.62.0",
|
||||
"resolved": "https://registry.npmjs.org/@taiga-ui/layout/-/layout-4.62.0.tgz",
|
||||
"integrity": "sha512-xd8eLLeR5FE3RhnVMGl1QlC3JXXJLsLAAASpBf9DQsTt+YBBl8BQt/cXGbBcJecC2mJLZlS6zytSkMTHY7VAhw==",
|
||||
"license": "Apache-2.0",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
@@ -4204,9 +4204,9 @@
|
||||
"peerDependencies": {
|
||||
"@angular/common": ">=16.0.0",
|
||||
"@angular/core": ">=16.0.0",
|
||||
"@taiga-ui/cdk": "^4.55.0",
|
||||
"@taiga-ui/core": "^4.55.0",
|
||||
"@taiga-ui/kit": "^4.55.0",
|
||||
"@taiga-ui/cdk": "^4.62.0",
|
||||
"@taiga-ui/core": "^4.62.0",
|
||||
"@taiga-ui/kit": "^4.62.0",
|
||||
"@taiga-ui/polymorpheus": "^4.9.0",
|
||||
"rxjs": ">=7.0.0"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "startos-ui",
|
||||
"version": "0.4.0-alpha.14",
|
||||
"version": "0.4.0-alpha.15",
|
||||
"author": "Start9 Labs, Inc",
|
||||
"homepage": "https://start9.com/",
|
||||
"license": "MIT",
|
||||
@@ -49,18 +49,18 @@
|
||||
"@noble/hashes": "^1.4.0",
|
||||
"@start9labs/argon2": "^0.3.0",
|
||||
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
||||
"@taiga-ui/addon-charts": "4.55.0",
|
||||
"@taiga-ui/addon-commerce": "4.55.0",
|
||||
"@taiga-ui/addon-mobile": "4.55.0",
|
||||
"@taiga-ui/addon-table": "4.55.0",
|
||||
"@taiga-ui/cdk": "4.55.0",
|
||||
"@taiga-ui/core": "4.55.0",
|
||||
"@taiga-ui/addon-charts": "4.62.0",
|
||||
"@taiga-ui/addon-commerce": "4.62.0",
|
||||
"@taiga-ui/addon-mobile": "4.62.0",
|
||||
"@taiga-ui/addon-table": "4.62.0",
|
||||
"@taiga-ui/cdk": "4.62.0",
|
||||
"@taiga-ui/core": "4.62.0",
|
||||
"@taiga-ui/dompurify": "4.1.11",
|
||||
"@taiga-ui/event-plugins": "4.7.0",
|
||||
"@taiga-ui/experimental": "4.55.0",
|
||||
"@taiga-ui/icons": "4.55.0",
|
||||
"@taiga-ui/kit": "4.55.0",
|
||||
"@taiga-ui/layout": "4.55.0",
|
||||
"@taiga-ui/experimental": "4.62.0",
|
||||
"@taiga-ui/icons": "4.62.0",
|
||||
"@taiga-ui/kit": "4.62.0",
|
||||
"@taiga-ui/layout": "4.62.0",
|
||||
"@taiga-ui/polymorpheus": "4.9.0",
|
||||
"ansi-to-html": "^0.7.2",
|
||||
"base64-js": "^1.5.1",
|
||||
|
||||
@@ -70,6 +70,7 @@ import { MarketplaceItemComponent } from './item.component'
|
||||
|
||||
<div class="background-border box-shadow-lg shadow-color-light">
|
||||
<div class="box-container">
|
||||
<h2 class="additional-detail-title">{{ 'Description' | i18n }}</h2>
|
||||
<p [innerHTML]="pkg().description.long"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -10,7 +10,12 @@ import { MarketplacePkgBase } from '../../../types'
|
||||
selector: 'marketplace-dep-item',
|
||||
template: `
|
||||
<div class="outer-container">
|
||||
<tui-avatar class="dep-img" size="l" [src]="getImage(dep.key)" />
|
||||
<tui-avatar
|
||||
appearance="action-grayscale"
|
||||
class="dep-img"
|
||||
size="l"
|
||||
[src]="getImage(dep.key)"
|
||||
/>
|
||||
<div>
|
||||
<tui-line-clamp [linesLimit]="2" [content]="titleContent" />
|
||||
<ng-template #titleContent>
|
||||
|
||||
@@ -21,7 +21,10 @@ import { MarketplacePkg } from '../../types'
|
||||
[queryParams]="{ id: pkg.id, flavor: pkg.flavor }"
|
||||
queryParamsHandling="merge"
|
||||
>
|
||||
<tui-avatar [src]="pkg.icon | trustUrl" />
|
||||
<tui-avatar
|
||||
appearance="action-grayscale"
|
||||
[src]="pkg.icon | trustUrl"
|
||||
/>
|
||||
<span tuiTitle>
|
||||
{{ pkg.title }}
|
||||
<span tuiSubtitle>{{ pkg.version }}</span>
|
||||
|
||||
@@ -40,7 +40,9 @@ import { HintPipe } from '../pipes/hint.pipe'
|
||||
[(ngModel)]="selected"
|
||||
/>
|
||||
}
|
||||
<tui-data-list-wrapper *tuiTextfieldDropdown new [items]="items" />
|
||||
@if (!mobile) {
|
||||
<tui-data-list-wrapper *tuiTextfieldDropdown new [items]="items" />
|
||||
}
|
||||
@if (spec | hint; as hint) {
|
||||
<tui-icon [tuiTooltip]="hint" />
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
tuiButtonOptionsProvider,
|
||||
TuiDataList,
|
||||
TuiDropdown,
|
||||
TuiIcon,
|
||||
TuiTextfield,
|
||||
} from '@taiga-ui/core'
|
||||
import { PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||
@@ -27,13 +28,9 @@ import { InterfaceComponent } from '../interface.component'
|
||||
selector: 'td[actions]',
|
||||
template: `
|
||||
<div class="desktop">
|
||||
<button
|
||||
tuiIconButton
|
||||
appearance="flat-grayscale"
|
||||
iconStart="@tui.info"
|
||||
(click)="viewDetails()"
|
||||
>
|
||||
<button tuiIconButton appearance="flat-grayscale" (click)="viewDetails()">
|
||||
{{ 'Address details' | i18n }}
|
||||
<tui-icon class="info" icon="@tui.info" background="@tui.info-filled" />
|
||||
</button>
|
||||
@if (interface.value()?.type === 'ui') {
|
||||
<a
|
||||
@@ -113,6 +110,19 @@ import { InterfaceComponent } from '../interface.component'
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
:host-context(.uncommon-hidden) .desktop {
|
||||
height: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.info {
|
||||
background: var(--tui-status-info);
|
||||
|
||||
&::after {
|
||||
mask-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
@@ -127,7 +137,14 @@ import { InterfaceComponent } from '../interface.component'
|
||||
}
|
||||
}
|
||||
`,
|
||||
imports: [TuiButton, TuiDropdown, TuiDataList, i18nPipe, TuiTextfield],
|
||||
imports: [
|
||||
TuiButton,
|
||||
TuiDropdown,
|
||||
TuiDataList,
|
||||
i18nPipe,
|
||||
TuiTextfield,
|
||||
TuiIcon,
|
||||
],
|
||||
providers: [tuiButtonOptionsProvider({ appearance: 'icon' })],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import { ChangeDetectionStrategy, Component, input } from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiButton } from '@taiga-ui/core'
|
||||
import { TuiAccordion } from '@taiga-ui/experimental'
|
||||
import { TuiSkeleton } from '@taiga-ui/kit'
|
||||
import { TuiElasticContainer, TuiSkeleton } from '@taiga-ui/kit'
|
||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
|
||||
@@ -12,91 +13,79 @@ import { InterfaceAddressItemComponent } from './item.component'
|
||||
selector: 'section[addresses]',
|
||||
template: `
|
||||
<header>{{ 'Addresses' | i18n }}</header>
|
||||
<table [appTable]="['Type', 'Access', 'Gateway', 'URL', null]">
|
||||
@for (address of addresses()?.common; track $index) {
|
||||
<tr [address]="address" [isRunning]="isRunning()"></tr>
|
||||
} @empty {
|
||||
@if (addresses()) {
|
||||
<tr>
|
||||
<td colspan="5">
|
||||
<app-placeholder icon="@tui.list-x">
|
||||
{{ 'No addresses' | i18n }}
|
||||
</app-placeholder>
|
||||
</td>
|
||||
</tr>
|
||||
} @else {
|
||||
@for (_ of [0, 1]; track $index) {
|
||||
<tui-elastic-container>
|
||||
<table [appTable]="['Type', 'Access', 'Gateway', 'URL', null]">
|
||||
@for (address of addresses()?.common; track $index) {
|
||||
<tr [address]="address" [isRunning]="isRunning()"></tr>
|
||||
} @empty {
|
||||
@if (addresses()) {
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
<div [tuiSkeleton]="true">{{ 'Loading' | i18n }}</div>
|
||||
<td colspan="5">
|
||||
<app-placeholder icon="@tui.list-x">
|
||||
{{ 'No addresses' | i18n }}
|
||||
</app-placeholder>
|
||||
</td>
|
||||
</tr>
|
||||
} @else {
|
||||
@for (_ of [0, 1]; track $index) {
|
||||
<tr>
|
||||
<td colspan="6">
|
||||
<div [tuiSkeleton]="true">{{ 'Loading' | i18n }}</div>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</table>
|
||||
@if (addresses()?.uncommon?.length) {
|
||||
<tui-accordion>
|
||||
<tui-expand>
|
||||
<hr />
|
||||
<table class="g-table">
|
||||
@for (address of addresses()?.uncommon; track $index) {
|
||||
<tr [address]="address" [isRunning]="isRunning()"></tr>
|
||||
}
|
||||
</table>
|
||||
</tui-expand>
|
||||
<button
|
||||
appearance="secondary-grayscale"
|
||||
iconEnd=""
|
||||
[(tuiAccordion)]="uncommon"
|
||||
>
|
||||
@if (uncommon) {
|
||||
Hide uncommon
|
||||
} @else {
|
||||
Show uncommon
|
||||
<tbody [class.uncommon-hidden]="!uncommon">
|
||||
@if (addresses()?.uncommon?.length && uncommon) {
|
||||
<tr [style.background]="'var(--tui-background-neutral-1)'">
|
||||
<td colspan="5"></td>
|
||||
</tr>
|
||||
}
|
||||
</button>
|
||||
</tui-accordion>
|
||||
}
|
||||
@for (address of addresses()?.uncommon; track $index) {
|
||||
<tr [address]="address" [isRunning]="isRunning()"></tr>
|
||||
}
|
||||
</tbody>
|
||||
@if (addresses()?.uncommon?.length) {
|
||||
<caption [style.caption-side]="'bottom'">
|
||||
<button
|
||||
tuiButton
|
||||
size="m"
|
||||
appearance="secondary-grayscale"
|
||||
(click)="uncommon = !uncommon"
|
||||
>
|
||||
@if (uncommon) {
|
||||
Hide uncommon
|
||||
} @else {
|
||||
Show uncommon
|
||||
}
|
||||
</button>
|
||||
</caption>
|
||||
}
|
||||
</table>
|
||||
</tui-elastic-container>
|
||||
`,
|
||||
styles: `
|
||||
tui-accordion {
|
||||
border-radius: 0;
|
||||
.g-table:has(caption) {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
[tuiAccordion],
|
||||
tui-expand {
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
[tuiAccordion] {
|
||||
justify-content: center;
|
||||
height: 3rem;
|
||||
border-radius: 0 0 var(--tui-radius-m) var(--tui-radius-m) !important;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 0;
|
||||
height: 0.25rem;
|
||||
border-radius: 1rem;
|
||||
}
|
||||
|
||||
:host-context(tui-root._mobile) {
|
||||
[tuiAccordion] {
|
||||
margin: 0.5rem 0;
|
||||
border-radius: var(--tui-radius-m) !important;
|
||||
}
|
||||
[tuiButton] {
|
||||
width: 100%;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
`,
|
||||
host: { class: 'g-card' },
|
||||
imports: [
|
||||
TuiSkeleton,
|
||||
TuiButton,
|
||||
TableComponent,
|
||||
PlaceholderComponent,
|
||||
i18nPipe,
|
||||
InterfaceAddressItemComponent,
|
||||
TuiAccordion,
|
||||
TuiSkeleton,
|
||||
TuiElasticContainer,
|
||||
],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
|
||||
@@ -8,27 +8,31 @@ import { TuiBadge } from '@taiga-ui/kit'
|
||||
selector: 'tr[address]',
|
||||
template: `
|
||||
@if (address(); as address) {
|
||||
<td>{{ address.type }}</td>
|
||||
<td>
|
||||
@if (address.access === 'public') {
|
||||
<tui-badge size="s" appearance="primary-success">
|
||||
{{ 'public' | i18n }}
|
||||
</tui-badge>
|
||||
} @else if (address.access === 'private') {
|
||||
<tui-badge size="s" appearance="primary-destructive">
|
||||
{{ 'private' | i18n }}
|
||||
</tui-badge>
|
||||
} @else {
|
||||
-
|
||||
}
|
||||
<div class="wrapper">{{ address.type }}</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="wrapper">
|
||||
@if (address.access === 'public') {
|
||||
<tui-badge size="s" appearance="primary-success">
|
||||
{{ 'public' | i18n }}
|
||||
</tui-badge>
|
||||
} @else if (address.access === 'private') {
|
||||
<tui-badge size="s" appearance="primary-destructive">
|
||||
{{ 'private' | i18n }}
|
||||
</tui-badge>
|
||||
} @else {
|
||||
-
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
<td [style.order]="-1">
|
||||
<div [title]="address.gatewayName">
|
||||
<div class="wrapper" [title]="address.gatewayName">
|
||||
{{ address.gatewayName || '-' }}
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div [title]="address.url">{{ address.url }}</div>
|
||||
<div class="wrapper" [title]="address.url">{{ address.url }}</div>
|
||||
</td>
|
||||
<td
|
||||
actions
|
||||
@@ -48,6 +52,18 @@ import { TuiBadge } from '@taiga-ui/kit'
|
||||
}
|
||||
}
|
||||
|
||||
:host-context(.uncommon-hidden) {
|
||||
.wrapper {
|
||||
height: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
td {
|
||||
padding-block: 0;
|
||||
border: hidden;
|
||||
}
|
||||
}
|
||||
|
||||
div {
|
||||
white-space: normal;
|
||||
word-break: break-all;
|
||||
|
||||
@@ -13,6 +13,8 @@ import { i18nKey, i18nPipe } from '@start9labs/shared'
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><ng-content /></tbody>
|
||||
<ng-content select="tbody" />
|
||||
<ng-content select="caption" />
|
||||
`,
|
||||
styles: `
|
||||
:host:has(app-placeholder) thead {
|
||||
|
||||
@@ -25,7 +25,7 @@ import { ToManifestPipe } from '../../../pipes/to-manifest'
|
||||
[queryParams]="services[d.key] ? {} : { search: d.key }"
|
||||
[class.error]="getError(d.key)"
|
||||
>
|
||||
<tui-avatar>
|
||||
<tui-avatar appearance="action-grayscale">
|
||||
<img
|
||||
alt=""
|
||||
[src]="
|
||||
|
||||
@@ -25,7 +25,7 @@ import { getManifest } from 'src/app/utils/get-package-data'
|
||||
selector: 'tr[task]',
|
||||
template: `
|
||||
<td tuiFade class="row">
|
||||
<tui-avatar size="xs">
|
||||
<tui-avatar appearance="action-grayscale" size="xs">
|
||||
<img [src]="pkg()?.icon || fallback()?.icon" alt="" />
|
||||
</tui-avatar>
|
||||
<span>{{ title() || fallback()?.title }}</span>
|
||||
|
||||
@@ -73,9 +73,7 @@ export default class ServiceActionsRoute {
|
||||
.pipe(
|
||||
filter(pkg => pkg.stateInfo.state === 'installed'),
|
||||
map(pkg => {
|
||||
const specialGroup = Object.values(pkg.actions).some(
|
||||
pkg => !!pkg.group,
|
||||
)
|
||||
const specialGroup = Object.values(pkg.actions).some(a => !!a.group)
|
||||
? 'Other'
|
||||
: 'General'
|
||||
return {
|
||||
@@ -90,9 +88,15 @@ export default class ServiceActionsRoute {
|
||||
group: action.group || specialGroup,
|
||||
}))
|
||||
.sort((a, b) => {
|
||||
if (a.group === specialGroup) return 1
|
||||
if (b.group === specialGroup) return -1
|
||||
return a.group.localeCompare(b.group) // Optional: sort others alphabetically
|
||||
if (a.group === specialGroup && b.group !== specialGroup)
|
||||
return 1
|
||||
if (b.group === specialGroup && a.group !== specialGroup)
|
||||
return -1
|
||||
|
||||
const groupCompare = a.group.localeCompare(b.group) // sort groups lexicographically
|
||||
if (groupCompare !== 0) return groupCompare
|
||||
|
||||
return a.id.localeCompare(b.id) // sort actions within groups lexicographically
|
||||
})
|
||||
.reduce<
|
||||
Record<
|
||||
|
||||
@@ -47,7 +47,9 @@ const INACTIVE: PrimaryStatus[] = [
|
||||
</div>
|
||||
<aside class="g-aside">
|
||||
<header tuiCell routerLink="./">
|
||||
<tui-avatar><img alt="" [src]="service()?.icon" /></tui-avatar>
|
||||
<tui-avatar appearance="action-grayscale">
|
||||
<img alt="" [src]="service()?.icon" />
|
||||
</tui-avatar>
|
||||
<span tuiTitle>
|
||||
<strong tuiFade>{{ manifest()?.title }}</strong>
|
||||
<span tuiSubtitle>{{ manifest()?.version }}</span>
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||
import { i18nPipe } from '@start9labs/shared'
|
||||
import { TuiSkeleton } from '@taiga-ui/kit'
|
||||
import { TableComponent } from 'src/app/routes/portal/components/table.component'
|
||||
import { AuthorityItemComponent } from './item.component'
|
||||
import { AuthorityService } from './authority.service'
|
||||
@@ -12,15 +10,11 @@ import { AuthorityService } from './authority.service'
|
||||
<tr [authority]="{ name: 'Local Root CA' }"></tr>
|
||||
@for (authority of authorityService.authorities(); track $index) {
|
||||
<tr [authority]="authority"></tr>
|
||||
} @empty {
|
||||
<td [attr.colspan]="4">
|
||||
<div [tuiSkeleton]="true">{{ 'Loading' | i18n }}</div>
|
||||
</td>
|
||||
}
|
||||
</table>
|
||||
`,
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
imports: [TuiSkeleton, i18nPipe, TableComponent, AuthorityItemComponent],
|
||||
imports: [TableComponent, AuthorityItemComponent],
|
||||
})
|
||||
export class AuthoritiesTableComponent {
|
||||
protected readonly authorityService = inject(AuthorityService)
|
||||
|
||||
@@ -18,7 +18,7 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
@for (pkg of pkgs() | keyvalue; track $index) {
|
||||
@if (backupProgress()?.[pkg.key]; as progress) {
|
||||
<div tuiCell>
|
||||
<tui-avatar>
|
||||
<tui-avatar appearance="action-grayscale">
|
||||
<img alt="" [src]="pkg.value.icon" />
|
||||
</tui-avatar>
|
||||
<span tuiTitle>
|
||||
|
||||
@@ -45,7 +45,9 @@ import UpdatesComponent from './updates.component'
|
||||
<tr (click)="expanded.set(!expanded())">
|
||||
<td>
|
||||
<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 appearance="action-grayscale" size="s">
|
||||
<img alt="" [src]="item().icon" />
|
||||
</tui-avatar>
|
||||
<span tuiTitle [style.margin]="'-0.125rem 0 0'">
|
||||
<b tuiFade>{{ item().title }}</b>
|
||||
<span tuiSubtitle tuiFade class="mobile">
|
||||
|
||||
@@ -69,7 +69,7 @@ interface UpdatesData {
|
||||
[class.g-secondary]="current()?.url !== registry.url"
|
||||
(click)="current.set(registry)"
|
||||
>
|
||||
<tui-avatar>
|
||||
<tui-avatar appearance="action-grayscale">
|
||||
<store-icon [url]="registry.url" />
|
||||
</tui-avatar>
|
||||
<span tuiTitle>
|
||||
|
||||
Reference in New Issue
Block a user