mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
ST port labels and move logout to settings (#3134)
* chore: update packages (#3132) * chore: update packages * start tunnel messaging * chore: standalone * pbpaste instead --------- Co-authored-by: Matt Hill <mattnine@protonmail.com> * port labels and move logout to settings * enable-disable forwards * Fix docs URLs in start-tunnel installer output (#3135) --------- Co-authored-by: Alex Inkin <alexander@inkin.ru> Co-authored-by: gStart9 <106188942+gStart9@users.noreply.github.com>
This commit is contained in:
@@ -11,6 +11,7 @@ use crate::db::model::public::NetworkInterfaceType;
|
|||||||
use crate::net::forward::add_iptables_rule;
|
use crate::net::forward::add_iptables_rule;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::tunnel::context::TunnelContext;
|
use crate::tunnel::context::TunnelContext;
|
||||||
|
use crate::tunnel::db::PortForwardEntry;
|
||||||
use crate::tunnel::wg::{WIREGUARD_INTERFACE_NAME, WgConfig, WgSubnetClients, WgSubnetConfig};
|
use crate::tunnel::wg::{WIREGUARD_INTERFACE_NAME, WgConfig, WgSubnetClients, WgSubnetConfig};
|
||||||
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
use crate::util::serde::{HandlerExtSerde, display_serializable};
|
||||||
|
|
||||||
@@ -51,6 +52,22 @@ pub fn tunnel_api<C: Context>() -> ParentHandler<C> {
|
|||||||
.no_display()
|
.no_display()
|
||||||
.with_about("about.remove-port-forward")
|
.with_about("about.remove-port-forward")
|
||||||
.with_call_remote::<CliContext>(),
|
.with_call_remote::<CliContext>(),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"update-label",
|
||||||
|
from_fn_async(update_forward_label)
|
||||||
|
.with_metadata("sync_db", Value::Bool(true))
|
||||||
|
.no_display()
|
||||||
|
.with_about("about.update-port-forward-label")
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
|
)
|
||||||
|
.subcommand(
|
||||||
|
"set-enabled",
|
||||||
|
from_fn_async(set_forward_enabled)
|
||||||
|
.with_metadata("sync_db", Value::Bool(true))
|
||||||
|
.no_display()
|
||||||
|
.with_about("about.enable-or-disable-port-forward")
|
||||||
|
.with_call_remote::<CliContext>(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.subcommand(
|
.subcommand(
|
||||||
@@ -453,11 +470,17 @@ pub async fn show_config(
|
|||||||
pub struct AddPortForwardParams {
|
pub struct AddPortForwardParams {
|
||||||
source: SocketAddrV4,
|
source: SocketAddrV4,
|
||||||
target: SocketAddrV4,
|
target: SocketAddrV4,
|
||||||
|
#[arg(long)]
|
||||||
|
label: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn add_forward(
|
pub async fn add_forward(
|
||||||
ctx: TunnelContext,
|
ctx: TunnelContext,
|
||||||
AddPortForwardParams { source, target }: AddPortForwardParams,
|
AddPortForwardParams {
|
||||||
|
source,
|
||||||
|
target,
|
||||||
|
label,
|
||||||
|
}: AddPortForwardParams,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let prefix = ctx
|
let prefix = ctx
|
||||||
.net_iface
|
.net_iface
|
||||||
@@ -482,10 +505,12 @@ pub async fn add_forward(
|
|||||||
m.insert(source, rc);
|
m.insert(source, rc);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let entry = PortForwardEntry { target, label, enabled: true };
|
||||||
|
|
||||||
ctx.db
|
ctx.db
|
||||||
.mutate(|db| {
|
.mutate(|db| {
|
||||||
db.as_port_forwards_mut()
|
db.as_port_forwards_mut()
|
||||||
.insert(&source, &target)
|
.insert(&source, &entry)
|
||||||
.and_then(|replaced| {
|
.and_then(|replaced| {
|
||||||
if replaced.is_some() {
|
if replaced.is_some() {
|
||||||
Err(Error::new(
|
Err(Error::new(
|
||||||
@@ -523,3 +548,92 @@ pub async fn remove_forward(
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct UpdatePortForwardLabelParams {
|
||||||
|
source: SocketAddrV4,
|
||||||
|
label: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_forward_label(
|
||||||
|
ctx: TunnelContext,
|
||||||
|
UpdatePortForwardLabelParams { source, label }: UpdatePortForwardLabelParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
db.as_port_forwards_mut().mutate(|pf| {
|
||||||
|
let entry = pf.0.get_mut(&source).ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
|
eyre!("Port forward from {source} not found"),
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
entry.label = label.clone();
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Parser)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct SetPortForwardEnabledParams {
|
||||||
|
source: SocketAddrV4,
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn set_forward_enabled(
|
||||||
|
ctx: TunnelContext,
|
||||||
|
SetPortForwardEnabledParams { source, enabled }: SetPortForwardEnabledParams,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let target = ctx
|
||||||
|
.db
|
||||||
|
.mutate(|db| {
|
||||||
|
db.as_port_forwards_mut().mutate(|pf| {
|
||||||
|
let entry = pf.0.get_mut(&source).ok_or_else(|| {
|
||||||
|
Error::new(
|
||||||
|
eyre!("Port forward from {source} not found"),
|
||||||
|
ErrorKind::NotFound,
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
entry.enabled = enabled;
|
||||||
|
Ok(entry.target)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.result?;
|
||||||
|
|
||||||
|
if enabled {
|
||||||
|
let prefix = ctx
|
||||||
|
.net_iface
|
||||||
|
.peek(|i| {
|
||||||
|
i.iter()
|
||||||
|
.find_map(|(_, i)| {
|
||||||
|
i.ip_info.as_ref().and_then(|i| {
|
||||||
|
i.subnets
|
||||||
|
.iter()
|
||||||
|
.find(|s| s.contains(&IpAddr::from(*target.ip())))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.cloned()
|
||||||
|
})
|
||||||
|
.map(|s| s.prefix_len())
|
||||||
|
.unwrap_or(32);
|
||||||
|
let rc = ctx
|
||||||
|
.forward
|
||||||
|
.add_forward(source, target, prefix, None)
|
||||||
|
.await?;
|
||||||
|
ctx.active_forwards.mutate(|m| {
|
||||||
|
m.insert(source, rc);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if let Some(rc) = ctx.active_forwards.mutate(|m| m.remove(&source)) {
|
||||||
|
drop(rc);
|
||||||
|
ctx.forward.gc().await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@@ -184,7 +184,11 @@ impl TunnelContext {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut active_forwards = BTreeMap::new();
|
let mut active_forwards = BTreeMap::new();
|
||||||
for (from, to) in peek.as_port_forwards().de()?.0 {
|
for (from, entry) in peek.as_port_forwards().de()?.0 {
|
||||||
|
if !entry.enabled {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let to = entry.target;
|
||||||
let prefix = net_iface
|
let prefix = net_iface
|
||||||
.peek(|i| {
|
.peek(|i| {
|
||||||
i.iter()
|
i.iter()
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ impl Model<TunnelDatabase> {
|
|||||||
}
|
}
|
||||||
self.as_port_forwards_mut().mutate(|pf| {
|
self.as_port_forwards_mut().mutate(|pf| {
|
||||||
Ok(pf.0.retain(|k, v| {
|
Ok(pf.0.retain(|k, v| {
|
||||||
if keep_targets.contains(v.ip()) {
|
if keep_targets.contains(v.target.ip()) {
|
||||||
keep_sources.insert(*k);
|
keep_sources.insert(*k);
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
@@ -70,11 +70,25 @@ fn export_bindings_tunnel_db() {
|
|||||||
TunnelDatabase::export_all_to("bindings/tunnel").unwrap();
|
TunnelDatabase::export_all_to("bindings/tunnel").unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PortForwardEntry {
|
||||||
|
pub target: SocketAddrV4,
|
||||||
|
#[serde(default)]
|
||||||
|
pub label: String,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_true() -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
#[derive(Clone, Debug, Default, Deserialize, Serialize, TS)]
|
||||||
pub struct PortForwards(pub BTreeMap<SocketAddrV4, SocketAddrV4>);
|
pub struct PortForwards(pub BTreeMap<SocketAddrV4, PortForwardEntry>);
|
||||||
impl Map for PortForwards {
|
impl Map for PortForwards {
|
||||||
type Key = SocketAddrV4;
|
type Key = SocketAddrV4;
|
||||||
type Value = SocketAddrV4;
|
type Value = PortForwardEntry;
|
||||||
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
fn key_str(key: &Self::Key) -> Result<impl AsRef<str>, Error> {
|
||||||
Self::key_string(key)
|
Self::key_string(key)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -524,26 +524,26 @@ pub async fn init_web(ctx: CliContext) -> Result<(), Error> {
|
|||||||
"To access your Web URL securely, trust your Root CA (displayed above) on your client device(s):\n",
|
"To access your Web URL securely, trust your Root CA (displayed above) on your client device(s):\n",
|
||||||
" - MacOS\n",
|
" - MacOS\n",
|
||||||
" 1. Open the Terminal app\n",
|
" 1. Open the Terminal app\n",
|
||||||
" 2. Paste the following command (**DO NOT** click Return): pbcopy < ~/Desktop/ca.crt\n",
|
" 2. Type or copy/paste the following command (**DO NOT** click Enter/Return yet): pbpaste > ~/Desktop/tunnel-ca.crt\n",
|
||||||
" 3. Copy your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
|
" 3. Copy your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
|
||||||
" 4. Back in Terminal, click Return. ca.crt is saved to your Desktop\n",
|
" 4. Back in Terminal, click Enter/Return. tunnel-ca.crt is saved to your Desktop\n",
|
||||||
" 5. Complete by trusting your Root CA: https://docs.start9.com/device-guides/mac/ca.html\n",
|
" 5. Complete by trusting your Root CA: https://docs.start9.com/start-os/0.4.0.x/user-manual/trust-ca.html?platform=Mac\n",
|
||||||
" - Linux\n",
|
" - Linux\n",
|
||||||
" 1. Open gedit, nano, or any editor\n",
|
" 1. Open gedit, nano, or any editor\n",
|
||||||
" 2. Copy/paste your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
|
" 2. Copy/paste your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
|
||||||
" 3. Name the file ca.crt and save as plaintext\n",
|
" 3. Name the file tunnel-ca.crt and save as plaintext\n",
|
||||||
" 4. Complete by trusting your Root CA: https://docs.start9.com/device-guides/linux/ca.html\n",
|
" 4. Complete by trusting your Root CA: https://docs.start9.com/start-os/0.4.0.x/user-manual/trust-ca.html?platform=Debian+%252F+Ubuntu\n",
|
||||||
" - Windows\n",
|
" - Windows\n",
|
||||||
" 1. Open the Notepad app\n",
|
" 1. Open the Notepad app\n",
|
||||||
" 2. Copy/paste your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
|
" 2. Copy/paste your Root CA (including -----BEGIN CERTIFICATE----- and -----END CERTIFICATE-----)\n",
|
||||||
" 3. Name the file ca.crt and save as plaintext\n",
|
" 3. Name the file tunnel-ca.crt and save as plaintext\n",
|
||||||
" 4. Complete by trusting your Root CA: https://docs.start9.com/device-guides/windows/ca.html\n",
|
" 4. Complete by trusting your Root CA: https://docs.start9.com/start-os/0.4.0.x/user-manual/trust-ca.html?platform=Windows\n",
|
||||||
" - Android/Graphene\n",
|
" - Android/Graphene\n",
|
||||||
" 1. Send the ca.crt file (created above) to yourself\n",
|
" 1. Send the tunnel-ca.crt file (created above) to yourself\n",
|
||||||
" 2. Complete by trusting your Root CA: https://docs.start9.com/device-guides/android/ca.html\n",
|
" 2. Complete by trusting your Root CA: https://docs.start9.com/start-os/0.4.0.x/user-manual/trust-ca.html?platform=Android+%252F+Graphene\n",
|
||||||
" - iOS\n",
|
" - iOS\n",
|
||||||
" 1. Send the ca.crt file (created above) to yourself\n",
|
" 1. Send the tunnel-ca.crt file (created above) to yourself\n",
|
||||||
" 2. Complete by trusting your Root CA: https://docs.start9.com/device-guides/ios/ca.html\n",
|
" 2. Complete by trusting your Root CA: https://docs.start9.com/start-os/0.4.0.x/user-manual/trust-ca.html?platform=iOS\n",
|
||||||
));
|
));
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|||||||
2498
web/package-lock.json
generated
2498
web/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -33,34 +33,33 @@
|
|||||||
"format:check": "prettier --check projects/"
|
"format:check": "prettier --check projects/"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@angular/animations": "^20.3.0",
|
"@angular/animations": "^21.2.1",
|
||||||
"@angular/cdk": "^20.1.0",
|
"@angular/cdk": "^21.2.1",
|
||||||
"@angular/common": "^20.3.0",
|
"@angular/common": "^21.2.1",
|
||||||
"@angular/compiler": "^20.3.0",
|
"@angular/compiler": "^21.2.1",
|
||||||
"@angular/core": "^20.3.0",
|
"@angular/core": "^21.2.1",
|
||||||
"@angular/forms": "^20.3.0",
|
"@angular/forms": "^21.2.1",
|
||||||
"@angular/platform-browser": "^20.3.0",
|
"@angular/platform-browser": "^21.2.1",
|
||||||
"@angular/platform-browser-dynamic": "^20.1.0",
|
"@angular/pwa": "^21.2.1",
|
||||||
"@angular/pwa": "^20.3.0",
|
"@angular/router": "^21.2.1",
|
||||||
"@angular/router": "^20.3.0",
|
"@angular/service-worker": "^21.2.1",
|
||||||
"@angular/service-worker": "^20.3.0",
|
|
||||||
"@materia-ui/ngx-monaco-editor": "^6.0.0",
|
"@materia-ui/ngx-monaco-editor": "^6.0.0",
|
||||||
"@noble/curves": "^1.4.0",
|
"@noble/curves": "^1.4.0",
|
||||||
"@noble/hashes": "^1.4.0",
|
"@noble/hashes": "^1.4.0",
|
||||||
"@start9labs/argon2": "^0.3.0",
|
"@start9labs/argon2": "^0.3.0",
|
||||||
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
"@start9labs/start-sdk": "file:../sdk/baseDist",
|
||||||
"@taiga-ui/addon-charts": "4.66.0",
|
"@taiga-ui/addon-charts": "4.73.0",
|
||||||
"@taiga-ui/addon-commerce": "4.66.0",
|
"@taiga-ui/addon-commerce": "4.73.0",
|
||||||
"@taiga-ui/addon-mobile": "4.66.0",
|
"@taiga-ui/addon-mobile": "4.73.0",
|
||||||
"@taiga-ui/addon-table": "4.66.0",
|
"@taiga-ui/addon-table": "4.73.0",
|
||||||
"@taiga-ui/cdk": "4.66.0",
|
"@taiga-ui/cdk": "4.73.0",
|
||||||
"@taiga-ui/core": "4.66.0",
|
"@taiga-ui/core": "4.73.0",
|
||||||
"@taiga-ui/dompurify": "4.1.11",
|
"@taiga-ui/dompurify": "4.1.11",
|
||||||
"@taiga-ui/event-plugins": "4.7.0",
|
"@taiga-ui/event-plugins": "4.7.0",
|
||||||
"@taiga-ui/experimental": "4.66.0",
|
"@taiga-ui/experimental": "4.73.0",
|
||||||
"@taiga-ui/icons": "4.66.0",
|
"@taiga-ui/icons": "4.73.0",
|
||||||
"@taiga-ui/kit": "4.66.0",
|
"@taiga-ui/kit": "4.73.0",
|
||||||
"@taiga-ui/layout": "4.66.0",
|
"@taiga-ui/layout": "4.73.0",
|
||||||
"@taiga-ui/polymorpheus": "4.9.0",
|
"@taiga-ui/polymorpheus": "4.9.0",
|
||||||
"ansi-to-html": "^0.7.2",
|
"ansi-to-html": "^0.7.2",
|
||||||
"base64-js": "^1.5.1",
|
"base64-js": "^1.5.1",
|
||||||
@@ -80,7 +79,7 @@
|
|||||||
"mime": "^4.0.3",
|
"mime": "^4.0.3",
|
||||||
"monaco-editor": "^0.33.0",
|
"monaco-editor": "^0.33.0",
|
||||||
"mustache": "^4.2.0",
|
"mustache": "^4.2.0",
|
||||||
"ng-qrcode": "^20.0.0",
|
"ng-qrcode": "^21.0.0",
|
||||||
"node-jose": "^2.2.0",
|
"node-jose": "^2.2.0",
|
||||||
"patch-db-client": "file:../patch-db/client",
|
"patch-db-client": "file:../patch-db/client",
|
||||||
"pbkdf2": "^3.1.2",
|
"pbkdf2": "^3.1.2",
|
||||||
@@ -92,10 +91,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@angular-experts/hawkeye": "^1.7.2",
|
"@angular-experts/hawkeye": "^1.7.2",
|
||||||
"@angular/build": "^20.1.0",
|
"@angular/build": "^21.2.1",
|
||||||
"@angular/cli": "^20.1.0",
|
"@angular/cli": "^21.2.1",
|
||||||
"@angular/compiler-cli": "^20.1.0",
|
"@angular/compiler-cli": "^21.2.1",
|
||||||
"@angular/language-service": "^20.1.0",
|
"@angular/language-service": "^21.2.1",
|
||||||
"@types/dompurify": "3.0.5",
|
"@types/dompurify": "3.0.5",
|
||||||
"@types/estree": "^0.0.51",
|
"@types/estree": "^0.0.51",
|
||||||
"@types/js-yaml": "^4.0.5",
|
"@types/js-yaml": "^4.0.5",
|
||||||
@@ -107,7 +106,7 @@
|
|||||||
"@types/uuid": "^8.3.1",
|
"@types/uuid": "^8.3.1",
|
||||||
"husky": "^4.3.8",
|
"husky": "^4.3.8",
|
||||||
"lint-staged": "^13.2.0",
|
"lint-staged": "^13.2.0",
|
||||||
"ng-packagr": "^20.1.0",
|
"ng-packagr": "^21.2.0",
|
||||||
"node-html-parser": "^5.3.3",
|
"node-html-parser": "^5.3.3",
|
||||||
"postcss": "^8.4.21",
|
"postcss": "^8.4.21",
|
||||||
"prettier": "^3.5.3",
|
"prettier": "^3.5.3",
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { NgModule } from '@angular/core'
|
|
||||||
import {
|
|
||||||
DocsLinkDirective,
|
|
||||||
i18nPipe,
|
|
||||||
SharedPipesModule,
|
|
||||||
} from '@start9labs/shared'
|
|
||||||
import {
|
|
||||||
TuiAppearance,
|
|
||||||
TuiButton,
|
|
||||||
TuiIcon,
|
|
||||||
TuiLoader,
|
|
||||||
TuiPopup,
|
|
||||||
} from '@taiga-ui/core'
|
|
||||||
import { TuiDrawer, TuiSkeleton } from '@taiga-ui/kit'
|
|
||||||
import { CategoriesModule } from '../../pages/list/categories/categories.module'
|
|
||||||
import { SearchModule } from '../../pages/list/search/search.module'
|
|
||||||
import { StoreIconComponentModule } from '../store-icon/store-icon.component.module'
|
|
||||||
import { MenuComponent } from './menu.component'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
SharedPipesModule,
|
|
||||||
SearchModule,
|
|
||||||
CategoriesModule,
|
|
||||||
TuiLoader,
|
|
||||||
TuiButton,
|
|
||||||
CategoriesModule,
|
|
||||||
StoreIconComponentModule,
|
|
||||||
TuiAppearance,
|
|
||||||
TuiIcon,
|
|
||||||
TuiSkeleton,
|
|
||||||
TuiDrawer,
|
|
||||||
TuiPopup,
|
|
||||||
i18nPipe,
|
|
||||||
DocsLinkDirective,
|
|
||||||
],
|
|
||||||
declarations: [MenuComponent],
|
|
||||||
exports: [MenuComponent],
|
|
||||||
})
|
|
||||||
export class MenuModule {}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
@@ -6,16 +7,35 @@ import {
|
|||||||
OnDestroy,
|
OnDestroy,
|
||||||
signal,
|
signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
|
import { DocsLinkDirective, i18nPipe } from '@start9labs/shared'
|
||||||
|
import { TuiAppearance, TuiButton, TuiIcon, TuiPopup } from '@taiga-ui/core'
|
||||||
|
import { TuiDrawer, TuiSkeleton } from '@taiga-ui/kit'
|
||||||
import { Subject, takeUntil } from 'rxjs'
|
import { Subject, takeUntil } from 'rxjs'
|
||||||
|
import { CategoriesComponent } from '../../pages/list/categories/categories.component'
|
||||||
|
import { SearchComponent } from '../../pages/list/search/search.component'
|
||||||
import { AbstractCategoryService } from '../../services/category.service'
|
import { AbstractCategoryService } from '../../services/category.service'
|
||||||
import { StoreDataWithUrl } from '../../types'
|
import { StoreDataWithUrl } from '../../types'
|
||||||
|
import { StoreIconComponent } from '../store-icon.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'menu',
|
selector: 'menu',
|
||||||
templateUrl: './menu.component.html',
|
templateUrl: './menu.component.html',
|
||||||
styleUrls: ['./menu.component.scss'],
|
styleUrls: ['./menu.component.scss'],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
SearchComponent,
|
||||||
|
CategoriesComponent,
|
||||||
|
TuiButton,
|
||||||
|
StoreIconComponent,
|
||||||
|
TuiAppearance,
|
||||||
|
TuiIcon,
|
||||||
|
TuiSkeleton,
|
||||||
|
TuiDrawer,
|
||||||
|
TuiPopup,
|
||||||
|
i18nPipe,
|
||||||
|
DocsLinkDirective,
|
||||||
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class MenuComponent implements OnDestroy {
|
export class MenuComponent implements OnDestroy {
|
||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
import { TuiIcon, TuiTitle } from '@taiga-ui/core'
|
import { TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||||
import { StoreIconComponentModule } from './store-icon/store-icon.component.module'
|
import { StoreIconComponent } from './store-icon.component'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: '[registry]',
|
selector: '[registry]',
|
||||||
@@ -17,7 +17,7 @@ import { StoreIconComponentModule } from './store-icon/store-icon.component.modu
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [StoreIconComponentModule, TuiIcon, TuiTitle],
|
imports: [StoreIconComponent, TuiIcon, TuiTitle],
|
||||||
})
|
})
|
||||||
export class MarketplaceRegistryComponent {
|
export class MarketplaceRegistryComponent {
|
||||||
@Input()
|
@Input()
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import { knownRegistries, sameUrl } from '@start9labs/shared'
|
|||||||
`,
|
`,
|
||||||
styles: ':host { overflow: hidden; }',
|
styles: ':host { overflow: hidden; }',
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class StoreIconComponent {
|
export class StoreIconComponent {
|
||||||
@Input()
|
@Input()
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { StoreIconComponent } from './store-icon.component'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [StoreIconComponent],
|
|
||||||
imports: [CommonModule],
|
|
||||||
exports: [StoreIconComponent],
|
|
||||||
})
|
|
||||||
export class StoreIconComponentModule {}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
@@ -5,7 +6,11 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Output,
|
Output,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
|
import { RouterModule } from '@angular/router'
|
||||||
|
import { LocalizePipe } from '@start9labs/shared'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
|
import { TuiAppearance, TuiIcon } from '@taiga-ui/core'
|
||||||
|
import { TuiSkeleton } from '@taiga-ui/kit'
|
||||||
|
|
||||||
const ICONS: Record<string, string> = {
|
const ICONS: Record<string, string> = {
|
||||||
all: '@tui.layout-grid',
|
all: '@tui.layout-grid',
|
||||||
@@ -26,8 +31,15 @@ const ICONS: Record<string, string> = {
|
|||||||
selector: 'marketplace-categories',
|
selector: 'marketplace-categories',
|
||||||
templateUrl: 'categories.component.html',
|
templateUrl: 'categories.component.html',
|
||||||
styleUrls: ['categories.component.scss'],
|
styleUrls: ['categories.component.scss'],
|
||||||
|
imports: [
|
||||||
|
RouterModule,
|
||||||
|
CommonModule,
|
||||||
|
TuiAppearance,
|
||||||
|
TuiIcon,
|
||||||
|
TuiSkeleton,
|
||||||
|
LocalizePipe,
|
||||||
|
],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class CategoriesComponent {
|
export class CategoriesComponent {
|
||||||
@Input()
|
@Input()
|
||||||
|
|||||||
@@ -1,15 +0,0 @@
|
|||||||
import { TuiIcon, TuiAppearance } from '@taiga-ui/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { TuiSkeleton } from '@taiga-ui/kit'
|
|
||||||
import { LocalizePipe } from '@start9labs/shared'
|
|
||||||
|
|
||||||
import { CategoriesComponent } from './categories.component'
|
|
||||||
import { RouterModule } from '@angular/router'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [RouterModule, CommonModule, TuiAppearance, TuiIcon, TuiSkeleton, LocalizePipe],
|
|
||||||
declarations: [CategoriesComponent],
|
|
||||||
exports: [CategoriesComponent],
|
|
||||||
})
|
|
||||||
export class CategoriesModule {}
|
|
||||||
@@ -1,12 +1,15 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
|
import { RouterModule } from '@angular/router'
|
||||||
|
import { LocalizePipe, TickerComponent } from '@start9labs/shared'
|
||||||
import { MarketplacePkg } from '../../../types'
|
import { MarketplacePkg } from '../../../types'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-item',
|
selector: 'marketplace-item',
|
||||||
templateUrl: 'item.component.html',
|
templateUrl: 'item.component.html',
|
||||||
styleUrls: ['item.component.scss'],
|
styleUrls: ['item.component.scss'],
|
||||||
|
imports: [CommonModule, RouterModule, TickerComponent, LocalizePipe],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class ItemComponent {
|
export class ItemComponent {
|
||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { RouterModule } from '@angular/router'
|
|
||||||
import { LocalizePipe, SharedPipesModule, TickerComponent } from '@start9labs/shared'
|
|
||||||
import { ItemComponent } from './item.component'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [ItemComponent],
|
|
||||||
exports: [ItemComponent],
|
|
||||||
imports: [CommonModule, RouterModule, SharedPipesModule, TickerComponent, LocalizePipe],
|
|
||||||
})
|
|
||||||
export class ItemModule {}
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
@@ -5,13 +6,15 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Output,
|
Output,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { TuiIcon } from '@taiga-ui/core'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'marketplace-search',
|
selector: 'marketplace-search',
|
||||||
templateUrl: 'search.component.html',
|
templateUrl: 'search.component.html',
|
||||||
styleUrls: ['search.component.scss'],
|
styleUrls: ['search.component.scss'],
|
||||||
|
imports: [FormsModule, CommonModule, TuiIcon],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class SearchComponent {
|
export class SearchComponent {
|
||||||
@Input()
|
@Input()
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { FormsModule } from '@angular/forms'
|
|
||||||
import { TuiIcon } from '@taiga-ui/core'
|
|
||||||
import { SearchComponent } from './search.component'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [FormsModule, CommonModule, TuiIcon],
|
|
||||||
declarations: [SearchComponent],
|
|
||||||
exports: [SearchComponent],
|
|
||||||
})
|
|
||||||
export class SearchModule {}
|
|
||||||
@@ -1,7 +1,12 @@
|
|||||||
import { KeyValue } from '@angular/common'
|
import { KeyValue } from '@angular/common'
|
||||||
import { ChangeDetectionStrategy, Component, inject, Input } from '@angular/core'
|
import {
|
||||||
|
ChangeDetectionStrategy,
|
||||||
|
Component,
|
||||||
|
inject,
|
||||||
|
Input,
|
||||||
|
} from '@angular/core'
|
||||||
import { RouterModule } from '@angular/router'
|
import { RouterModule } from '@angular/router'
|
||||||
import { ExverPipesModule, i18nPipe, i18nService } from '@start9labs/shared'
|
import { i18nPipe, i18nService } from '@start9labs/shared'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
import { TuiAvatar, TuiLineClamp } from '@taiga-ui/kit'
|
import { TuiAvatar, TuiLineClamp } from '@taiga-ui/kit'
|
||||||
import { MarketplacePkgBase } from '../../../types'
|
import { MarketplacePkgBase } from '../../../types'
|
||||||
@@ -20,9 +25,7 @@ import { MarketplacePkgBase } from '../../../types'
|
|||||||
<tui-line-clamp [linesLimit]="2" [content]="titleContent" />
|
<tui-line-clamp [linesLimit]="2" [content]="titleContent" />
|
||||||
<ng-template #titleContent>
|
<ng-template #titleContent>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<span>
|
<span>{{ getTitle(dep.key) }}</span>
|
||||||
{{ getTitle(dep.key) }}
|
|
||||||
</span>
|
|
||||||
<p>
|
<p>
|
||||||
@if (dep.value.optional) {
|
@if (dep.value.optional) {
|
||||||
<span>({{ 'Optional' | i18n }})</span>
|
<span>({{ 'Optional' | i18n }})</span>
|
||||||
@@ -37,9 +40,7 @@ import { MarketplacePkgBase } from '../../../types'
|
|||||||
[content]="descContent"
|
[content]="descContent"
|
||||||
class="description"
|
class="description"
|
||||||
/>
|
/>
|
||||||
<ng-template #descContent>
|
<ng-template #descContent>{{ dep.value.description }}</ng-template>
|
||||||
{{ dep.value.description }}
|
|
||||||
</ng-template>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
@@ -94,7 +95,7 @@ import { MarketplacePkgBase } from '../../../types'
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [RouterModule, TuiAvatar, ExverPipesModule, TuiLineClamp, i18nPipe],
|
imports: [RouterModule, TuiAvatar, TuiLineClamp, i18nPipe],
|
||||||
})
|
})
|
||||||
export class MarketplaceDepItemComponent {
|
export class MarketplaceDepItemComponent {
|
||||||
private readonly i18nService = inject(i18nService)
|
private readonly i18nService = inject(i18nService)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
import { RouterLink } from '@angular/router'
|
import { RouterLink } from '@angular/router'
|
||||||
import { i18nPipe, SharedPipesModule } from '@start9labs/shared'
|
import { i18nPipe, TrustUrlPipe } from '@start9labs/shared'
|
||||||
import { TuiTitle } from '@taiga-ui/core'
|
import { TuiTitle } from '@taiga-ui/core'
|
||||||
import { TuiAvatar } from '@taiga-ui/kit'
|
import { TuiAvatar } from '@taiga-ui/kit'
|
||||||
import { TuiCell } from '@taiga-ui/layout'
|
import { TuiCell } from '@taiga-ui/layout'
|
||||||
@@ -47,14 +47,7 @@ import { MarketplacePkg } from '../../types'
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [
|
imports: [RouterLink, TuiCell, TuiTitle, TrustUrlPipe, TuiAvatar, i18nPipe],
|
||||||
RouterLink,
|
|
||||||
TuiCell,
|
|
||||||
TuiTitle,
|
|
||||||
SharedPipesModule,
|
|
||||||
TuiAvatar,
|
|
||||||
i18nPipe,
|
|
||||||
],
|
|
||||||
})
|
})
|
||||||
export class MarketplaceFlavorsComponent {
|
export class MarketplaceFlavorsComponent {
|
||||||
@Input()
|
@Input()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
import { SharedPipesModule, TickerComponent } from '@start9labs/shared'
|
import { TickerComponent } from '@start9labs/shared'
|
||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -118,7 +118,7 @@ import { T } from '@start9labs/start-sdk'
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [SharedPipesModule, TickerComponent],
|
imports: [TickerComponent],
|
||||||
})
|
})
|
||||||
export class MarketplacePackageHeroComponent {
|
export class MarketplacePackageHeroComponent {
|
||||||
@Input({ required: true })
|
@Input({ required: true })
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
TemplateRef,
|
TemplateRef,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { FormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { DialogService, i18nPipe, SharedPipesModule } from '@start9labs/shared'
|
import { DialogService, i18nPipe } from '@start9labs/shared'
|
||||||
import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
|
import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
|
||||||
import { TuiRadioList } from '@taiga-ui/kit'
|
import { TuiRadioList } from '@taiga-ui/kit'
|
||||||
import { filter } from 'rxjs'
|
import { filter } from 'rxjs'
|
||||||
@@ -76,7 +76,6 @@ import { MarketplaceItemComponent } from './item.component'
|
|||||||
imports: [
|
imports: [
|
||||||
MarketplaceItemComponent,
|
MarketplaceItemComponent,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
SharedPipesModule,
|
|
||||||
FormsModule,
|
FormsModule,
|
||||||
TuiRadioList,
|
TuiRadioList,
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import Fuse from 'fuse.js'
|
|||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'filterPackages',
|
name: 'filterPackages',
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class FilterPackagesPipe implements PipeTransform {
|
export class FilterPackagesPipe implements PipeTransform {
|
||||||
transform(
|
transform(
|
||||||
@@ -79,9 +78,3 @@ export class FilterPackagesPipe implements PipeTransform {
|
|||||||
.map(a => ({ ...a }))
|
.map(a => ({ ...a }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [FilterPackagesPipe],
|
|
||||||
exports: [FilterPackagesPipe],
|
|
||||||
})
|
|
||||||
export class FilterPackagesPipeModule {}
|
|
||||||
|
|||||||
@@ -3,11 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export * from './pages/list/categories/categories.component'
|
export * from './pages/list/categories/categories.component'
|
||||||
export * from './pages/list/categories/categories.module'
|
|
||||||
export * from './pages/list/item/item.component'
|
export * from './pages/list/item/item.component'
|
||||||
export * from './pages/list/item/item.module'
|
|
||||||
export * from './pages/list/search/search.component'
|
export * from './pages/list/search/search.component'
|
||||||
export * from './pages/list/search/search.module'
|
|
||||||
export * from './pages/show/link.component'
|
export * from './pages/show/link.component'
|
||||||
export * from './pages/show/item.component'
|
export * from './pages/show/item.component'
|
||||||
export * from './pages/show/links.component'
|
export * from './pages/show/links.component'
|
||||||
@@ -22,10 +19,7 @@ export * from './pages/show/release-notes.component'
|
|||||||
|
|
||||||
export * from './pipes/filter-packages.pipe'
|
export * from './pipes/filter-packages.pipe'
|
||||||
|
|
||||||
export * from './components/store-icon/store-icon.component'
|
export * from './components/store-icon.component'
|
||||||
export * from './components/store-icon/store-icon.component.module'
|
|
||||||
export * from './components/store-icon/store-icon.component'
|
|
||||||
export * from './components/menu/menu.component.module'
|
|
||||||
export * from './components/menu/menu.component'
|
export * from './components/menu/menu.component'
|
||||||
export * from './components/registry.component'
|
export * from './components/registry.component'
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,17 @@
|
|||||||
import { Component, inject, DOCUMENT } from '@angular/core'
|
import { Component, DOCUMENT, inject, OnInit } from '@angular/core'
|
||||||
import { Router } from '@angular/router'
|
import { Router, RouterOutlet } from '@angular/router'
|
||||||
import { ErrorService } from '@start9labs/shared'
|
import { ErrorService } from '@start9labs/shared'
|
||||||
|
import { TuiRoot } from '@taiga-ui/core'
|
||||||
|
|
||||||
import { ApiService } from './services/api.service'
|
import { ApiService } from './services/api.service'
|
||||||
import { StateService } from './services/state.service'
|
import { StateService } from './services/state.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
template: '<tui-root tuiTheme="dark"><router-outlet /></tui-root>',
|
template: '<tui-root tuiTheme="dark"><router-outlet /></tui-root>',
|
||||||
standalone: false,
|
imports: [TuiRoot, RouterOutlet],
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent implements OnInit {
|
||||||
private readonly api = inject(ApiService)
|
private readonly api = inject(ApiService)
|
||||||
private readonly errorService = inject(ErrorService)
|
private readonly errorService = inject(ErrorService)
|
||||||
private readonly router = inject(Router)
|
private readonly router = inject(Router)
|
||||||
|
|||||||
@@ -3,9 +3,20 @@ import {
|
|||||||
withFetch,
|
withFetch,
|
||||||
withInterceptorsFromDi,
|
withInterceptorsFromDi,
|
||||||
} from '@angular/common/http'
|
} from '@angular/common/http'
|
||||||
import { inject, NgModule, provideAppInitializer } from '@angular/core'
|
import {
|
||||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
|
ApplicationConfig,
|
||||||
import { PreloadAllModules, RouterModule } from '@angular/router'
|
inject,
|
||||||
|
provideAppInitializer,
|
||||||
|
provideZoneChangeDetection,
|
||||||
|
signal,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { provideAnimations } from '@angular/platform-browser/animations'
|
||||||
|
import {
|
||||||
|
PreloadAllModules,
|
||||||
|
provideRouter,
|
||||||
|
withDisabledInitialNavigation,
|
||||||
|
withPreloading,
|
||||||
|
} from '@angular/router'
|
||||||
import { WA_LOCATION } from '@ng-web-apis/common'
|
import { WA_LOCATION } from '@ng-web-apis/common'
|
||||||
import initArgon from '@start9labs/argon2'
|
import initArgon from '@start9labs/argon2'
|
||||||
import {
|
import {
|
||||||
@@ -15,13 +26,16 @@ import {
|
|||||||
VERSION,
|
VERSION,
|
||||||
WorkspaceConfig,
|
WorkspaceConfig,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
import { tuiButtonOptionsProvider, TuiRoot } from '@taiga-ui/core'
|
import {
|
||||||
import { NG_EVENT_PLUGINS } from '@taiga-ui/event-plugins'
|
tuiButtonOptionsProvider,
|
||||||
|
tuiTextfieldOptionsProvider,
|
||||||
|
} from '@taiga-ui/core'
|
||||||
|
import { provideEventPlugins } from '@taiga-ui/event-plugins'
|
||||||
|
|
||||||
|
import { ROUTES } from './app.routes'
|
||||||
import { ApiService } from './services/api.service'
|
import { ApiService } from './services/api.service'
|
||||||
import { LiveApiService } from './services/live-api.service'
|
import { LiveApiService } from './services/live-api.service'
|
||||||
import { MockApiService } from './services/mock-api.service'
|
import { MockApiService } from './services/mock-api.service'
|
||||||
import { AppComponent } from './app.component'
|
|
||||||
import { ROUTES } from './app.routes'
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
useMocks,
|
useMocks,
|
||||||
@@ -30,18 +44,16 @@ const {
|
|||||||
|
|
||||||
const version = require('../../../../package.json').version
|
const version = require('../../../../package.json').version
|
||||||
|
|
||||||
@NgModule({
|
export const APP_CONFIG: ApplicationConfig = {
|
||||||
declarations: [AppComponent],
|
|
||||||
imports: [
|
|
||||||
BrowserAnimationsModule,
|
|
||||||
RouterModule.forRoot(ROUTES, {
|
|
||||||
preloadingStrategy: PreloadAllModules,
|
|
||||||
initialNavigation: 'disabled',
|
|
||||||
}),
|
|
||||||
TuiRoot,
|
|
||||||
],
|
|
||||||
providers: [
|
providers: [
|
||||||
NG_EVENT_PLUGINS,
|
provideZoneChangeDetection(),
|
||||||
|
provideAnimations(),
|
||||||
|
provideEventPlugins(),
|
||||||
|
provideRouter(
|
||||||
|
ROUTES,
|
||||||
|
withDisabledInitialNavigation(),
|
||||||
|
withPreloading(PreloadAllModules),
|
||||||
|
),
|
||||||
I18N_PROVIDERS,
|
I18N_PROVIDERS,
|
||||||
provideSetupLogsService(ApiService),
|
provideSetupLogsService(ApiService),
|
||||||
tuiButtonOptionsProvider({ size: 'm' }),
|
tuiButtonOptionsProvider({ size: 'm' }),
|
||||||
@@ -64,7 +76,6 @@ const version = require('../../../../package.json').version
|
|||||||
|
|
||||||
initArgon({ module_or_path })
|
initArgon({ module_or_path })
|
||||||
}),
|
}),
|
||||||
|
tuiTextfieldOptionsProvider({ cleaner: signal(false) }),
|
||||||
],
|
],
|
||||||
bootstrap: [AppComponent],
|
}
|
||||||
})
|
|
||||||
export class AppModule {}
|
|
||||||
@@ -5,7 +5,6 @@ import { TuiDialogContext } from '@taiga-ui/core'
|
|||||||
import { injectContext } from '@taiga-ui/polymorpheus'
|
import { injectContext } from '@taiga-ui/polymorpheus'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
|
||||||
imports: [TuiButton, i18nPipe],
|
imports: [TuiButton, i18nPipe],
|
||||||
template: `
|
template: `
|
||||||
<p>{{ 'This drive contains existing StartOS data.' | i18n }}</p>
|
<p>{{ 'This drive contains existing StartOS data.' | i18n }}</p>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { TuiButton, TuiDialogContext } from '@taiga-ui/core'
|
|||||||
import { injectContext } from '@taiga-ui/polymorpheus'
|
import { injectContext } from '@taiga-ui/polymorpheus'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
|
||||||
imports: [TuiButton, i18nPipe],
|
imports: [TuiButton, i18nPipe],
|
||||||
template: `
|
template: `
|
||||||
<div class="animation-container">
|
<div class="animation-container">
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ interface Data {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
|
||||||
imports: [FormsModule, TuiTextfield, TuiSelect, TuiDataListWrapper, i18nPipe],
|
imports: [FormsModule, TuiTextfield, TuiSelect, TuiDataListWrapper, i18nPipe],
|
||||||
template: `
|
template: `
|
||||||
<p>{{ 'Multiple backups found. Select which one to restore.' | i18n }}</p>
|
<p>{{ 'Multiple backups found. Select which one to restore.' | i18n }}</p>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import { TuiPassword } from '@taiga-ui/kit'
|
|||||||
import { injectContext } from '@taiga-ui/polymorpheus'
|
import { injectContext } from '@taiga-ui/polymorpheus'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
standalone: true,
|
|
||||||
imports: [
|
imports: [
|
||||||
FormsModule,
|
FormsModule,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import { enableProdMode } from '@angular/core'
|
import { enableProdMode } from '@angular/core'
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
import { bootstrapApplication } from '@angular/platform-browser'
|
||||||
|
import { AppComponent } from 'src/app/app.component'
|
||||||
import { AppModule } from './app/app.module'
|
import { APP_CONFIG } from 'src/app/app.config'
|
||||||
import { environment } from './environments/environment'
|
import { environment } from 'src/environments/environment'
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
enableProdMode()
|
enableProdMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
platformBrowserDynamic()
|
bootstrapApplication(AppComponent, APP_CONFIG).catch(console.error)
|
||||||
.bootstrapModule(AppModule)
|
|
||||||
.catch(err => console.error(err))
|
|
||||||
|
|||||||
22
web/projects/shared/src/pipes/convert-bytes.pipe.ts
Normal file
22
web/projects/shared/src/pipes/convert-bytes.pipe.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
|
|
||||||
|
// converts bytes to gigabytes
|
||||||
|
@Pipe({
|
||||||
|
name: 'convertBytes',
|
||||||
|
})
|
||||||
|
export class ConvertBytesPipe implements PipeTransform {
|
||||||
|
transform(bytes: number): string {
|
||||||
|
return convertBytes(bytes)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertBytes(bytes: number): string {
|
||||||
|
if (bytes === 0) return '0 Bytes'
|
||||||
|
|
||||||
|
const k = 1024
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { Pipe, PipeTransform } from '@angular/core'
|
||||||
import { isEmptyObject } from '../../util/misc.util'
|
import { isEmptyObject } from '../util/misc.util'
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'empty',
|
name: 'empty',
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class EmptyPipe implements PipeTransform {
|
export class EmptyPipe implements PipeTransform {
|
||||||
transform(val: object | [] = {}): boolean {
|
transform(val: object | [] = {}): boolean {
|
||||||
if (Array.isArray(val)) return !val.length
|
return Array.isArray(val) ? !val.length : isEmptyObject(val)
|
||||||
return isEmptyObject(val)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,28 +1,11 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||||
import { Exver } from '../../services/exver.service'
|
import { Exver } from '../services/exver.service'
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'satisfiesExver',
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class ExverSatisfiesPipe implements PipeTransform {
|
|
||||||
constructor(private readonly exver: Exver) {}
|
|
||||||
|
|
||||||
transform(versionUnderTest?: string, range?: string): boolean {
|
|
||||||
return (
|
|
||||||
!!versionUnderTest &&
|
|
||||||
!!range &&
|
|
||||||
this.exver.satisfies(versionUnderTest, range)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'compareExver',
|
name: 'compareExver',
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class ExverComparesPipe implements PipeTransform {
|
export class ExverComparesPipe implements PipeTransform {
|
||||||
constructor(private readonly exver: Exver) {}
|
private readonly exver = inject(Exver)
|
||||||
|
|
||||||
transform(first: string, second: string): SemverResult {
|
transform(first: string, second: string): SemverResult {
|
||||||
try {
|
try {
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { ExverComparesPipe, ExverSatisfiesPipe } from './exver.pipe'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [ExverComparesPipe, ExverSatisfiesPipe],
|
|
||||||
exports: [ExverComparesPipe, ExverSatisfiesPipe],
|
|
||||||
})
|
|
||||||
export class ExverPipesModule {}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'includes',
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class IncludesPipe implements PipeTransform {
|
|
||||||
transform<T>(list: T[], val: T): boolean {
|
|
||||||
return list.includes(val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { IncludesPipe } from './includes.pipe'
|
|
||||||
import { EmptyPipe } from './empty.pipe'
|
|
||||||
import { TrustUrlPipe } from './trust.pipe'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [IncludesPipe, EmptyPipe, TrustUrlPipe],
|
|
||||||
exports: [IncludesPipe, EmptyPipe, TrustUrlPipe],
|
|
||||||
})
|
|
||||||
export class SharedPipesModule {}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'sort',
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class SortPipe implements PipeTransform {
|
|
||||||
transform(
|
|
||||||
value: any[],
|
|
||||||
column: string = '',
|
|
||||||
direction: string = 'asc',
|
|
||||||
): any[] {
|
|
||||||
// If the value is not an array or is empty, return the original value
|
|
||||||
if (!Array.isArray(value) || value.length === 0) {
|
|
||||||
return value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone the array to avoid modifying the original value
|
|
||||||
const sortedValue = [...value]
|
|
||||||
|
|
||||||
// Define the sorting function based on the column and direction parameters
|
|
||||||
const sortingFn = (a: any, b: any): number => {
|
|
||||||
if (a[column] < b[column]) {
|
|
||||||
return direction === 'asc' ? -1 : 1
|
|
||||||
} else if (a[column] > b[column]) {
|
|
||||||
return direction === 'asc' ? 1 : -1
|
|
||||||
} else {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort the array and return the result
|
|
||||||
return sortedValue.sort(sortingFn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
import { inject, Pipe, PipeTransform } from '@angular/core'
|
||||||
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
|
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser'
|
||||||
|
|
||||||
@Pipe({
|
@Pipe({
|
||||||
name: 'trustUrl',
|
name: 'trustUrl',
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class TrustUrlPipe implements PipeTransform {
|
export class TrustUrlPipe implements PipeTransform {
|
||||||
constructor(private readonly sanitizer: DomSanitizer) {}
|
private readonly sanitizer = inject(DomSanitizer)
|
||||||
|
|
||||||
transform(base64Icon: string): SafeResourceUrl {
|
transform(base64Icon: string): SafeResourceUrl {
|
||||||
return this.sanitizer.bypassSecurityTrustResourceUrl(base64Icon)
|
return this.sanitizer.bypassSecurityTrustResourceUrl(base64Icon)
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { ConvertBytesPipe, DurationToSecondsPipe } from './unit-conversion.pipe'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [ConvertBytesPipe, DurationToSecondsPipe],
|
|
||||||
exports: [ConvertBytesPipe, DurationToSecondsPipe],
|
|
||||||
})
|
|
||||||
export class UnitConversionPipesModule {}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
|
||||||
|
|
||||||
// converts bytes to gigabytes
|
|
||||||
@Pipe({
|
|
||||||
name: 'convertBytes',
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class ConvertBytesPipe implements PipeTransform {
|
|
||||||
transform(bytes: number): string {
|
|
||||||
return convertBytes(bytes)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function convertBytes(bytes: number): string {
|
|
||||||
if (bytes === 0) return '0 Bytes'
|
|
||||||
|
|
||||||
const k = 1024
|
|
||||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
||||||
|
|
||||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]
|
|
||||||
}
|
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'durationToSeconds',
|
|
||||||
standalone: false,
|
|
||||||
})
|
|
||||||
export class DurationToSecondsPipe implements PipeTransform {
|
|
||||||
transform(duration?: string | null): number {
|
|
||||||
if (!duration) return 0
|
|
||||||
|
|
||||||
const regex = /^([0-9]*(\.[0-9]+)?)(ns|µs|ms|s|m|d)$/
|
|
||||||
const [, num, , unit] = duration.match(regex) || []
|
|
||||||
const multiplier = (unit && unitsToSeconds[unit]) || NaN
|
|
||||||
|
|
||||||
return unit ? Number(num) * multiplier : NaN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
|
||||||
|
|
||||||
const unitsToSeconds: Record<string, number> = {
|
|
||||||
ns: 1e-9,
|
|
||||||
µs: 1e-6,
|
|
||||||
ms: 0.001,
|
|
||||||
s: 1,
|
|
||||||
m: 60,
|
|
||||||
h: 3600,
|
|
||||||
d: 86400,
|
|
||||||
}
|
|
||||||
@@ -20,14 +20,10 @@ export * from './i18n/i18n.providers'
|
|||||||
export * from './i18n/i18n.service'
|
export * from './i18n/i18n.service'
|
||||||
export * from './i18n/localize.pipe'
|
export * from './i18n/localize.pipe'
|
||||||
|
|
||||||
export * from './pipes/exver/exver.module'
|
export * from './pipes/exver-compares.pipe'
|
||||||
export * from './pipes/exver/exver.pipe'
|
export * from './pipes/empty.pipe'
|
||||||
export * from './pipes/shared/shared.module'
|
export * from './pipes/trust.pipe'
|
||||||
export * from './pipes/shared/empty.pipe'
|
export * from './pipes/convert-bytes.pipe'
|
||||||
export * from './pipes/shared/includes.pipe'
|
|
||||||
export * from './pipes/shared/trust.pipe'
|
|
||||||
export * from './pipes/unit-conversion/unit-conversion.module'
|
|
||||||
export * from './pipes/unit-conversion/unit-conversion.pipe'
|
|
||||||
export * from './pipes/markdown.pipe'
|
export * from './pipes/markdown.pipe'
|
||||||
|
|
||||||
export * from './services/copy.service'
|
export * from './services/copy.service'
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
import { Router, RouterLink, RouterLinkActive } from '@angular/router'
|
import { RouterLink, RouterLinkActive } from '@angular/router'
|
||||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
|
||||||
import { TuiButton } from '@taiga-ui/core'
|
import { TuiButton } from '@taiga-ui/core'
|
||||||
import { TuiBadgeNotification } from '@taiga-ui/kit'
|
import { TuiBadgeNotification } from '@taiga-ui/kit'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
|
||||||
import { AuthService } from 'src/app/services/auth.service'
|
|
||||||
import { SidebarService } from 'src/app/services/sidebar.service'
|
import { SidebarService } from 'src/app/services/sidebar.service'
|
||||||
import { UpdateService } from 'src/app/services/update.service'
|
import { UpdateService } from 'src/app/services/update.service'
|
||||||
|
|
||||||
@@ -38,15 +35,6 @@ import { UpdateService } from 'src/app/services/update.service'
|
|||||||
}
|
}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<button
|
|
||||||
tuiButton
|
|
||||||
iconStart="@tui.log-out"
|
|
||||||
appearance="neutral"
|
|
||||||
size="s"
|
|
||||||
(click)="logout()"
|
|
||||||
>
|
|
||||||
Logout
|
|
||||||
</button>
|
|
||||||
`,
|
`,
|
||||||
styles: `
|
styles: `
|
||||||
:host {
|
:host {
|
||||||
@@ -79,12 +67,6 @@ import { UpdateService } from 'src/app/services/update.service'
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
button {
|
|
||||||
width: 100%;
|
|
||||||
border-radius: 0;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
:host-context(tui-root._mobile) {
|
:host-context(tui-root._mobile) {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 3.5rem;
|
top: 3.5rem;
|
||||||
@@ -106,12 +88,7 @@ import { UpdateService } from 'src/app/services/update.service'
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
export class Nav {
|
export class Nav {
|
||||||
private readonly service = inject(AuthService)
|
|
||||||
private readonly router = inject(Router)
|
|
||||||
protected readonly sidebars = inject(SidebarService)
|
protected readonly sidebars = inject(SidebarService)
|
||||||
protected readonly api = inject(ApiService)
|
|
||||||
private readonly loader = inject(LoadingService)
|
|
||||||
private readonly errorService = inject(ErrorService)
|
|
||||||
protected readonly update = inject(UpdateService)
|
protected readonly update = inject(UpdateService)
|
||||||
|
|
||||||
protected readonly routes = [
|
protected readonly routes = [
|
||||||
@@ -131,18 +108,4 @@ export class Nav {
|
|||||||
link: 'port-forwards',
|
link: 'port-forwards',
|
||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
protected async logout() {
|
|
||||||
const loader = this.loader.open().subscribe()
|
|
||||||
try {
|
|
||||||
await this.api.logout()
|
|
||||||
this.service.authenticated.set(false)
|
|
||||||
this.router.navigate(['.'])
|
|
||||||
} catch (e: any) {
|
|
||||||
console.error(e)
|
|
||||||
this.errorService.handleError(e)
|
|
||||||
} finally {
|
|
||||||
loader.unsubscribe()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,11 @@ import { MappedDevice, PortForwardsData } from './utils'
|
|||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: `
|
||||||
<form tuiForm [formGroup]="form">
|
<form tuiForm [formGroup]="form">
|
||||||
|
<tui-textfield>
|
||||||
|
<label tuiLabel>Label</label>
|
||||||
|
<input tuiTextfield formControlName="label" />
|
||||||
|
</tui-textfield>
|
||||||
|
<tui-error formControlName="label" [error]="[] | tuiFieldError | async" />
|
||||||
<tui-textfield tuiChevron>
|
<tui-textfield tuiChevron>
|
||||||
<label tuiLabel>External IP</label>
|
<label tuiLabel>External IP</label>
|
||||||
@if (mobile) {
|
@if (mobile) {
|
||||||
@@ -161,6 +166,7 @@ export class PortForwardsAdd {
|
|||||||
injectContext<TuiDialogContext<void, PortForwardsData>>()
|
injectContext<TuiDialogContext<void, PortForwardsData>>()
|
||||||
|
|
||||||
protected readonly form = inject(NonNullableFormBuilder).group({
|
protected readonly form = inject(NonNullableFormBuilder).group({
|
||||||
|
label: ['', Validators.required],
|
||||||
externalip: ['', Validators.required],
|
externalip: ['', Validators.required],
|
||||||
externalport: [null as number | null, Validators.required],
|
externalport: [null as number | null, Validators.required],
|
||||||
device: [null as MappedDevice | null, Validators.required],
|
device: [null as MappedDevice | null, Validators.required],
|
||||||
@@ -185,19 +191,21 @@ export class PortForwardsAdd {
|
|||||||
|
|
||||||
const loader = this.loading.open().subscribe()
|
const loader = this.loading.open().subscribe()
|
||||||
|
|
||||||
const { externalip, externalport, device, internalport, also80 } =
|
const { label, externalip, externalport, device, internalport, also80 } =
|
||||||
this.form.getRawValue()
|
this.form.getRawValue()
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.api.addForward({
|
await this.api.addForward({
|
||||||
source: `${externalip}:${externalport}`,
|
source: `${externalip}:${externalport}`,
|
||||||
target: `${device!.ip}:${internalport}`,
|
target: `${device!.ip}:${internalport}`,
|
||||||
|
label,
|
||||||
})
|
})
|
||||||
|
|
||||||
if (externalport === 443 && internalport === 443 && also80) {
|
if (externalport === 443 && internalport === 443 && also80) {
|
||||||
await this.api.addForward({
|
await this.api.addForward({
|
||||||
source: `${externalip}:80`,
|
source: `${externalip}:80`,
|
||||||
target: `${device!.ip}:443`,
|
target: `${device!.ip}:443`,
|
||||||
|
label: `${label} (HTTP redirect)`,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { AsyncPipe } from '@angular/common'
|
||||||
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
|
import {
|
||||||
|
NonNullableFormBuilder,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
Validators,
|
||||||
|
} from '@angular/forms'
|
||||||
|
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||||
|
import {
|
||||||
|
TuiButton,
|
||||||
|
TuiDialogContext,
|
||||||
|
TuiError,
|
||||||
|
TuiTextfield,
|
||||||
|
} from '@taiga-ui/core'
|
||||||
|
import { TuiFieldErrorPipe } from '@taiga-ui/kit'
|
||||||
|
import { TuiForm } from '@taiga-ui/layout'
|
||||||
|
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
|
|
||||||
|
export interface EditLabelData {
|
||||||
|
readonly source: string
|
||||||
|
readonly label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
template: `
|
||||||
|
<form tuiForm [formGroup]="form">
|
||||||
|
<tui-textfield>
|
||||||
|
<label tuiLabel>Label</label>
|
||||||
|
<input tuiTextfield formControlName="label" />
|
||||||
|
</tui-textfield>
|
||||||
|
<tui-error formControlName="label" [error]="[] | tuiFieldError | async" />
|
||||||
|
<footer>
|
||||||
|
<button tuiButton [disabled]="form.invalid" (click)="onSave()">
|
||||||
|
Save
|
||||||
|
</button>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
`,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
imports: [
|
||||||
|
AsyncPipe,
|
||||||
|
ReactiveFormsModule,
|
||||||
|
TuiButton,
|
||||||
|
TuiError,
|
||||||
|
TuiFieldErrorPipe,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiForm,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class PortForwardsEditLabel {
|
||||||
|
private readonly api = inject(ApiService)
|
||||||
|
private readonly loading = inject(LoadingService)
|
||||||
|
private readonly errorService = inject(ErrorService)
|
||||||
|
|
||||||
|
protected readonly context =
|
||||||
|
injectContext<TuiDialogContext<void, EditLabelData>>()
|
||||||
|
|
||||||
|
protected readonly form = inject(NonNullableFormBuilder).group({
|
||||||
|
label: [this.context.data.label, Validators.required],
|
||||||
|
})
|
||||||
|
|
||||||
|
protected async onSave() {
|
||||||
|
const loader = this.loading.open().subscribe()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.api.updateForwardLabel({
|
||||||
|
source: this.context.data.source,
|
||||||
|
label: this.form.getRawValue().label,
|
||||||
|
})
|
||||||
|
} catch (e: any) {
|
||||||
|
console.error(e)
|
||||||
|
this.errorService.handleError(e)
|
||||||
|
} finally {
|
||||||
|
loader.unsubscribe()
|
||||||
|
this.context.$implicit.complete()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PORT_FORWARDS_EDIT_LABEL = new PolymorpheusComponent(
|
||||||
|
PortForwardsEditLabel,
|
||||||
|
)
|
||||||
@@ -3,18 +3,26 @@ import {
|
|||||||
Component,
|
Component,
|
||||||
computed,
|
computed,
|
||||||
inject,
|
inject,
|
||||||
|
signal,
|
||||||
Signal,
|
Signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { toSignal } from '@angular/core/rxjs-interop'
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
import { ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule } from '@angular/forms'
|
||||||
import { ErrorService, LoadingService } from '@start9labs/shared'
|
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||||
import { utils } from '@start9labs/start-sdk'
|
import { utils } from '@start9labs/start-sdk'
|
||||||
import { TuiButton } from '@taiga-ui/core'
|
import {
|
||||||
|
TuiButton,
|
||||||
|
TuiDataList,
|
||||||
|
TuiDropdown,
|
||||||
|
TuiLoader,
|
||||||
|
TuiTextfield,
|
||||||
|
} from '@taiga-ui/core'
|
||||||
import { TuiDialogService } from '@taiga-ui/experimental'
|
import { TuiDialogService } from '@taiga-ui/experimental'
|
||||||
import { TUI_CONFIRM } from '@taiga-ui/kit'
|
import { TUI_CONFIRM, TuiSwitch } from '@taiga-ui/kit'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { filter, map } from 'rxjs'
|
import { filter, map } from 'rxjs'
|
||||||
import { PORT_FORWARDS_ADD } from 'src/app/routes/home/routes/port-forwards/add'
|
import { PORT_FORWARDS_ADD } from 'src/app/routes/home/routes/port-forwards/add'
|
||||||
|
import { PORT_FORWARDS_EDIT_LABEL } from 'src/app/routes/home/routes/port-forwards/edit-label'
|
||||||
import { ApiService } from 'src/app/services/api/api.service'
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
import { TunnelData } from 'src/app/services/patch-db/data-model'
|
import { TunnelData } from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
@@ -25,6 +33,8 @@ import { MappedDevice, MappedForward } from './utils'
|
|||||||
<table class="g-table">
|
<table class="g-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th></th>
|
||||||
|
<th>Label</th>
|
||||||
<th>External IP</th>
|
<th>External IP</th>
|
||||||
<th>External Port</th>
|
<th>External Port</th>
|
||||||
<th>Device</th>
|
<th>Device</th>
|
||||||
@@ -39,6 +49,23 @@ import { MappedDevice, MappedForward } from './utils'
|
|||||||
<tbody>
|
<tbody>
|
||||||
@for (forward of forwards(); track $index) {
|
@for (forward of forwards(); track $index) {
|
||||||
<tr>
|
<tr>
|
||||||
|
<td>
|
||||||
|
<tui-loader
|
||||||
|
[showLoader]="toggling() === $index"
|
||||||
|
size="xs"
|
||||||
|
[overlay]="true"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
tuiSwitch
|
||||||
|
type="checkbox"
|
||||||
|
size="s"
|
||||||
|
[showIcons]="false"
|
||||||
|
[ngModel]="forward.enabled"
|
||||||
|
(ngModelChange)="onToggle(forward, $index)"
|
||||||
|
/>
|
||||||
|
</tui-loader>
|
||||||
|
</td>
|
||||||
|
<td>{{ forward.label || '—' }}</td>
|
||||||
<td>{{ forward.externalip }}</td>
|
<td>{{ forward.externalip }}</td>
|
||||||
<td>{{ forward.externalport }}</td>
|
<td>{{ forward.externalport }}</td>
|
||||||
<td>{{ forward.device.name }}</td>
|
<td>{{ forward.device.name }}</td>
|
||||||
@@ -47,11 +74,30 @@ import { MappedDevice, MappedForward } from './utils'
|
|||||||
<button
|
<button
|
||||||
tuiIconButton
|
tuiIconButton
|
||||||
size="xs"
|
size="xs"
|
||||||
|
tuiDropdown
|
||||||
|
tuiDropdownOpen
|
||||||
appearance="flat-grayscale"
|
appearance="flat-grayscale"
|
||||||
iconStart="@tui.trash"
|
iconStart="@tui.ellipsis-vertical"
|
||||||
(click)="onDelete(forward)"
|
|
||||||
>
|
>
|
||||||
Actions
|
Actions
|
||||||
|
<tui-data-list *tuiTextfieldDropdown size="s">
|
||||||
|
<button
|
||||||
|
tuiOption
|
||||||
|
iconStart="@tui.pencil"
|
||||||
|
new
|
||||||
|
(click)="onEditLabel(forward)"
|
||||||
|
>
|
||||||
|
{{ forward.label ? 'Rename' : 'Add label' }}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
tuiOption
|
||||||
|
iconStart="@tui.trash"
|
||||||
|
new
|
||||||
|
(click)="onDelete(forward)"
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</tui-data-list>
|
||||||
</button>
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -62,7 +108,15 @@ import { MappedDevice, MappedForward } from './utils'
|
|||||||
</table>
|
</table>
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [ReactiveFormsModule, TuiButton],
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
TuiButton,
|
||||||
|
TuiDropdown,
|
||||||
|
TuiDataList,
|
||||||
|
TuiLoader,
|
||||||
|
TuiSwitch,
|
||||||
|
TuiTextfield,
|
||||||
|
],
|
||||||
})
|
})
|
||||||
export default class PortForwards {
|
export default class PortForwards {
|
||||||
private readonly dialogs = inject(TuiDialogService)
|
private readonly dialogs = inject(TuiDialogService)
|
||||||
@@ -100,19 +154,36 @@ export default class PortForwards {
|
|||||||
)
|
)
|
||||||
|
|
||||||
protected readonly forwards = computed(() =>
|
protected readonly forwards = computed(() =>
|
||||||
Object.entries(this.portForwards() || {}).map(([source, target]) => {
|
Object.entries(this.portForwards() || {}).map(([source, entry]) => {
|
||||||
const sourceSplit = source.split(':')
|
const sourceSplit = source.split(':')
|
||||||
const targetSplit = target.split(':')
|
const targetSplit = entry.target.split(':')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
externalip: sourceSplit[0]!,
|
externalip: sourceSplit[0]!,
|
||||||
externalport: sourceSplit[1]!,
|
externalport: sourceSplit[1]!,
|
||||||
device: this.devices().find(d => d.ip === targetSplit[0])!,
|
device: this.devices().find(d => d.ip === targetSplit[0])!,
|
||||||
internalport: targetSplit[1]!,
|
internalport: targetSplit[1]!,
|
||||||
|
label: entry.label,
|
||||||
|
enabled: entry.enabled,
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
protected readonly toggling = signal<number | null>(null)
|
||||||
|
|
||||||
|
protected async onToggle(forward: MappedForward, index: number) {
|
||||||
|
this.toggling.set(index)
|
||||||
|
const source = `${forward.externalip}:${forward.externalport}`
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.api.setForwardEnabled({ source, enabled: !forward.enabled })
|
||||||
|
} catch (e: any) {
|
||||||
|
this.errorService.handleError(e)
|
||||||
|
} finally {
|
||||||
|
this.toggling.set(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected onAdd(): void {
|
protected onAdd(): void {
|
||||||
this.dialogs
|
this.dialogs
|
||||||
.open(PORT_FORWARDS_ADD, {
|
.open(PORT_FORWARDS_ADD, {
|
||||||
@@ -122,6 +193,18 @@ export default class PortForwards {
|
|||||||
.subscribe()
|
.subscribe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected onEditLabel(forward: MappedForward): void {
|
||||||
|
this.dialogs
|
||||||
|
.open(PORT_FORWARDS_EDIT_LABEL, {
|
||||||
|
label: 'Edit label',
|
||||||
|
data: {
|
||||||
|
source: `${forward.externalip}:${forward.externalport}`,
|
||||||
|
label: forward.label,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
protected onDelete({ externalip, externalport }: MappedForward): void {
|
protected onDelete({ externalip, externalport }: MappedForward): void {
|
||||||
this.dialogs
|
this.dialogs
|
||||||
.open(TUI_CONFIRM, { label: 'Are you sure?' })
|
.open(TUI_CONFIRM, { label: 'Are you sure?' })
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ export interface MappedForward {
|
|||||||
readonly externalport: string
|
readonly externalport: string
|
||||||
readonly device: MappedDevice
|
readonly device: MappedDevice
|
||||||
readonly internalport: string
|
readonly internalport: string
|
||||||
|
readonly label: string
|
||||||
|
readonly enabled: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PortForwardsData {
|
export interface PortForwardsData {
|
||||||
|
|||||||
@@ -4,11 +4,14 @@ import {
|
|||||||
inject,
|
inject,
|
||||||
signal,
|
signal,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { ErrorService } from '@start9labs/shared'
|
import { Router } from '@angular/router'
|
||||||
|
import { ErrorService, LoadingService } from '@start9labs/shared'
|
||||||
import { TuiAppearance, TuiButton, TuiTitle } from '@taiga-ui/core'
|
import { TuiAppearance, TuiButton, TuiTitle } from '@taiga-ui/core'
|
||||||
import { TuiDialogService } from '@taiga-ui/experimental'
|
import { TuiDialogService } from '@taiga-ui/experimental'
|
||||||
import { TuiBadge, TuiButtonLoading } from '@taiga-ui/kit'
|
import { TuiBadge, TuiButtonLoading } from '@taiga-ui/kit'
|
||||||
import { TuiCard, TuiCell } from '@taiga-ui/layout'
|
import { TuiCard, TuiCell } from '@taiga-ui/layout'
|
||||||
|
import { ApiService } from 'src/app/services/api/api.service'
|
||||||
|
import { AuthService } from 'src/app/services/auth.service'
|
||||||
import { UpdateService } from 'src/app/services/update.service'
|
import { UpdateService } from 'src/app/services/update.service'
|
||||||
|
|
||||||
import { CHANGE_PASSWORD } from './change-password'
|
import { CHANGE_PASSWORD } from './change-password'
|
||||||
@@ -50,6 +53,20 @@ import { CHANGE_PASSWORD } from './change-password'
|
|||||||
</span>
|
</span>
|
||||||
<button tuiButton size="s" (click)="onChangePassword()">Change</button>
|
<button tuiButton size="s" (click)="onChangePassword()">Change</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div tuiCell>
|
||||||
|
<span tuiTitle>
|
||||||
|
<strong>Logout</strong>
|
||||||
|
</span>
|
||||||
|
<button
|
||||||
|
tuiButton
|
||||||
|
size="s"
|
||||||
|
appearance="secondary-destructive"
|
||||||
|
iconStart="@tui.log-out"
|
||||||
|
(click)="onLogout()"
|
||||||
|
>
|
||||||
|
Logout
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
@@ -66,6 +83,10 @@ import { CHANGE_PASSWORD } from './change-password'
|
|||||||
export default class Settings {
|
export default class Settings {
|
||||||
private readonly dialogs = inject(TuiDialogService)
|
private readonly dialogs = inject(TuiDialogService)
|
||||||
private readonly errorService = inject(ErrorService)
|
private readonly errorService = inject(ErrorService)
|
||||||
|
private readonly api = inject(ApiService)
|
||||||
|
private readonly auth = inject(AuthService)
|
||||||
|
private readonly router = inject(Router)
|
||||||
|
private readonly loading = inject(LoadingService)
|
||||||
|
|
||||||
protected readonly update = inject(UpdateService)
|
protected readonly update = inject(UpdateService)
|
||||||
protected readonly checking = signal(false)
|
protected readonly checking = signal(false)
|
||||||
@@ -98,4 +119,18 @@ export default class Settings {
|
|||||||
this.applying.set(false)
|
this.applying.set(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected async onLogout() {
|
||||||
|
const loader = this.loading.open().subscribe()
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this.api.logout()
|
||||||
|
this.auth.authenticated.set(false)
|
||||||
|
this.router.navigate(['/'])
|
||||||
|
} catch (e: any) {
|
||||||
|
this.errorService.handleError(e)
|
||||||
|
} finally {
|
||||||
|
loader.unsubscribe()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ export abstract class ApiService {
|
|||||||
// forwards
|
// forwards
|
||||||
abstract addForward(params: AddForwardReq): Promise<null> // port-forward.add
|
abstract addForward(params: AddForwardReq): Promise<null> // port-forward.add
|
||||||
abstract deleteForward(params: DeleteForwardReq): Promise<null> // port-forward.remove
|
abstract deleteForward(params: DeleteForwardReq): Promise<null> // port-forward.remove
|
||||||
|
abstract updateForwardLabel(params: UpdateForwardLabelReq): Promise<null> // port-forward.update-label
|
||||||
|
abstract setForwardEnabled(params: SetForwardEnabledReq): Promise<null> // port-forward.set-enabled
|
||||||
// update
|
// update
|
||||||
abstract checkUpdate(): Promise<TunnelUpdateResult> // update.check
|
abstract checkUpdate(): Promise<TunnelUpdateResult> // update.check
|
||||||
abstract applyUpdate(): Promise<TunnelUpdateResult> // update.apply
|
abstract applyUpdate(): Promise<TunnelUpdateResult> // update.apply
|
||||||
@@ -60,12 +62,23 @@ export type DeleteDeviceReq = {
|
|||||||
export type AddForwardReq = {
|
export type AddForwardReq = {
|
||||||
source: string // externalip:port
|
source: string // externalip:port
|
||||||
target: string // internalip:port
|
target: string // internalip:port
|
||||||
|
label: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DeleteForwardReq = {
|
export type DeleteForwardReq = {
|
||||||
source: string
|
source: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type UpdateForwardLabelReq = {
|
||||||
|
source: string
|
||||||
|
label: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type SetForwardEnabledReq = {
|
||||||
|
source: string
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type TunnelUpdateResult = {
|
export type TunnelUpdateResult = {
|
||||||
status: string
|
status: string
|
||||||
installed: string
|
installed: string
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import {
|
|||||||
LoginReq,
|
LoginReq,
|
||||||
SubscribeRes,
|
SubscribeRes,
|
||||||
TunnelUpdateResult,
|
TunnelUpdateResult,
|
||||||
|
SetForwardEnabledReq,
|
||||||
|
UpdateForwardLabelReq,
|
||||||
UpsertDeviceReq,
|
UpsertDeviceReq,
|
||||||
UpsertSubnetReq,
|
UpsertSubnetReq,
|
||||||
} from './api.service'
|
} from './api.service'
|
||||||
@@ -104,6 +106,14 @@ export class LiveApiService extends ApiService {
|
|||||||
return this.rpcRequest({ method: 'port-forward.remove', params })
|
return this.rpcRequest({ method: 'port-forward.remove', params })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async updateForwardLabel(params: UpdateForwardLabelReq): Promise<null> {
|
||||||
|
return this.rpcRequest({ method: 'port-forward.update-label', params })
|
||||||
|
}
|
||||||
|
|
||||||
|
async setForwardEnabled(params: SetForwardEnabledReq): Promise<null> {
|
||||||
|
return this.rpcRequest({ method: 'port-forward.set-enabled', params })
|
||||||
|
}
|
||||||
|
|
||||||
// update
|
// update
|
||||||
|
|
||||||
async checkUpdate(): Promise<TunnelUpdateResult> {
|
async checkUpdate(): Promise<TunnelUpdateResult> {
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ import {
|
|||||||
LoginReq,
|
LoginReq,
|
||||||
SubscribeRes,
|
SubscribeRes,
|
||||||
TunnelUpdateResult,
|
TunnelUpdateResult,
|
||||||
|
SetForwardEnabledReq,
|
||||||
|
UpdateForwardLabelReq,
|
||||||
UpsertDeviceReq,
|
UpsertDeviceReq,
|
||||||
UpsertSubnetReq,
|
UpsertSubnetReq,
|
||||||
} from './api.service'
|
} from './api.service'
|
||||||
@@ -24,7 +26,12 @@ import {
|
|||||||
Revision,
|
Revision,
|
||||||
} from 'patch-db-client'
|
} from 'patch-db-client'
|
||||||
import { toObservable } from '@angular/core/rxjs-interop'
|
import { toObservable } from '@angular/core/rxjs-interop'
|
||||||
import { mockTunnelData, WgClient, WgSubnet } from '../patch-db/data-model'
|
import {
|
||||||
|
mockTunnelData,
|
||||||
|
PortForwardEntry,
|
||||||
|
WgClient,
|
||||||
|
WgSubnet,
|
||||||
|
} from '../patch-db/data-model'
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
providedIn: 'root',
|
providedIn: 'root',
|
||||||
@@ -171,11 +178,45 @@ export class MockApiService extends ApiService {
|
|||||||
async addForward(params: AddForwardReq): Promise<null> {
|
async addForward(params: AddForwardReq): Promise<null> {
|
||||||
await pauseFor(1000)
|
await pauseFor(1000)
|
||||||
|
|
||||||
const patch: AddOperation<string>[] = [
|
const patch: AddOperation<PortForwardEntry>[] = [
|
||||||
{
|
{
|
||||||
op: PatchOp.ADD,
|
op: PatchOp.ADD,
|
||||||
path: `/portForwards/${params.source}`,
|
path: `/portForwards/${params.source}`,
|
||||||
value: params.target,
|
value: {
|
||||||
|
target: params.target,
|
||||||
|
label: params.label || '',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
this.mockRevision(patch)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateForwardLabel(params: UpdateForwardLabelReq): Promise<null> {
|
||||||
|
await pauseFor(1000)
|
||||||
|
|
||||||
|
const patch: ReplaceOperation<string>[] = [
|
||||||
|
{
|
||||||
|
op: PatchOp.REPLACE,
|
||||||
|
path: `/portForwards/${params.source}/label`,
|
||||||
|
value: params.label,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
this.mockRevision(patch)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async setForwardEnabled(params: SetForwardEnabledReq): Promise<null> {
|
||||||
|
await pauseFor(1000)
|
||||||
|
|
||||||
|
const patch: ReplaceOperation<boolean>[] = [
|
||||||
|
{
|
||||||
|
op: PatchOp.REPLACE,
|
||||||
|
path: `/portForwards/${params.source}/enabled`,
|
||||||
|
value: params.enabled,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
this.mockRevision(patch)
|
this.mockRevision(patch)
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import { T } from '@start9labs/start-sdk'
|
import { T } from '@start9labs/start-sdk'
|
||||||
|
|
||||||
|
export type PortForwardEntry = {
|
||||||
|
target: string
|
||||||
|
label: string
|
||||||
|
enabled: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export type TunnelData = {
|
export type TunnelData = {
|
||||||
wg: WgServer
|
wg: WgServer
|
||||||
portForwards: Record<string, string>
|
portForwards: Record<string, PortForwardEntry>
|
||||||
gateways: Record<string, T.NetworkInterfaceInfo>
|
gateways: Record<string, T.NetworkInterfaceInfo>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,8 +45,12 @@ export const mockTunnelData: TunnelData = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
portForwards: {
|
portForwards: {
|
||||||
'69.1.1.42:443': '10.59.0.2:443',
|
'69.1.1.42:443': { target: '10.59.0.2:443', label: 'HTTPS', enabled: true },
|
||||||
'69.1.1.42:3000': '10.59.0.2:3000',
|
'69.1.1.42:3000': {
|
||||||
|
target: '10.59.0.2:3000',
|
||||||
|
label: 'Grafana',
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
gateways: {
|
gateways: {
|
||||||
eth0: {
|
eth0: {
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import { Component, inject } from '@angular/core'
|
import { Component, inject } from '@angular/core'
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||||
|
import { RouterOutlet } from '@angular/router'
|
||||||
import { i18nService } from '@start9labs/shared'
|
import { i18nService } from '@start9labs/shared'
|
||||||
|
import { TuiRoot } from '@taiga-ui/core'
|
||||||
import { PatchDB } from 'patch-db-client'
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { merge } from 'rxjs'
|
import { merge } from 'rxjs'
|
||||||
|
import { ToastContainerComponent } from 'src/app/components/toast-container.component'
|
||||||
import { PatchDataService } from './services/patch-data.service'
|
import { PatchDataService } from './services/patch-data.service'
|
||||||
import { DataModel } from './services/patch-db/data-model'
|
import { DataModel } from './services/patch-db/data-model'
|
||||||
import { PatchMonitorService } from './services/patch-monitor.service'
|
import { PatchMonitorService } from './services/patch-monitor.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
|
imports: [TuiRoot, RouterOutlet, ToastContainerComponent],
|
||||||
template: `
|
template: `
|
||||||
<tui-root tuiTheme="dark">
|
<tui-root tuiTheme="dark">
|
||||||
<router-outlet />
|
<router-outlet />
|
||||||
@@ -26,7 +30,6 @@ import { PatchMonitorService } from './services/patch-monitor.service'
|
|||||||
font-family: 'Proxima Nova', system-ui;
|
font-family: 'Proxima Nova', system-ui;
|
||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class AppComponent {
|
export class AppComponent {
|
||||||
private readonly i18n = inject(i18nService)
|
private readonly i18n = inject(i18nService)
|
||||||
|
|||||||
199
web/projects/ui/src/app/app.config.ts
Normal file
199
web/projects/ui/src/app/app.config.ts
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
import {
|
||||||
|
provideHttpClient,
|
||||||
|
withFetch,
|
||||||
|
withInterceptorsFromDi,
|
||||||
|
} from '@angular/common/http'
|
||||||
|
import {
|
||||||
|
ApplicationConfig,
|
||||||
|
inject,
|
||||||
|
provideAppInitializer,
|
||||||
|
provideZoneChangeDetection,
|
||||||
|
} from '@angular/core'
|
||||||
|
import { UntypedFormBuilder } from '@angular/forms'
|
||||||
|
import { provideAnimations } from '@angular/platform-browser/animations'
|
||||||
|
import {
|
||||||
|
ActivationStart,
|
||||||
|
PreloadAllModules,
|
||||||
|
provideRouter,
|
||||||
|
Router,
|
||||||
|
withComponentInputBinding,
|
||||||
|
withDisabledInitialNavigation,
|
||||||
|
withInMemoryScrolling,
|
||||||
|
withPreloading,
|
||||||
|
withRouterConfig,
|
||||||
|
} from '@angular/router'
|
||||||
|
import { provideServiceWorker } from '@angular/service-worker'
|
||||||
|
import { WA_LOCATION } from '@ng-web-apis/common'
|
||||||
|
import initArgon from '@start9labs/argon2'
|
||||||
|
import {
|
||||||
|
AbstractCategoryService,
|
||||||
|
FilterPackagesPipe,
|
||||||
|
} from '@start9labs/marketplace'
|
||||||
|
import {
|
||||||
|
I18N_PROVIDERS,
|
||||||
|
I18N_STORAGE,
|
||||||
|
i18nService,
|
||||||
|
Languages,
|
||||||
|
RELATIVE_URL,
|
||||||
|
VERSION,
|
||||||
|
WorkspaceConfig,
|
||||||
|
} from '@start9labs/shared'
|
||||||
|
import { tuiObfuscateOptionsProvider } from '@taiga-ui/cdk'
|
||||||
|
import {
|
||||||
|
TUI_DATE_FORMAT,
|
||||||
|
TUI_DIALOGS_CLOSE,
|
||||||
|
TUI_MEDIA,
|
||||||
|
tuiAlertOptionsProvider,
|
||||||
|
tuiButtonOptionsProvider,
|
||||||
|
tuiDropdownOptionsProvider,
|
||||||
|
tuiNumberFormatProvider,
|
||||||
|
} from '@taiga-ui/core'
|
||||||
|
import { provideEventPlugins } from '@taiga-ui/event-plugins'
|
||||||
|
import {
|
||||||
|
TUI_DATE_TIME_VALUE_TRANSFORMER,
|
||||||
|
TUI_DATE_VALUE_TRANSFORMER,
|
||||||
|
} from '@taiga-ui/kit'
|
||||||
|
import { PatchDB } from 'patch-db-client'
|
||||||
|
import { filter, identity, merge, of, pairwise } from 'rxjs'
|
||||||
|
import { FilterUpdatesPipe } from 'src/app/routes/portal/routes/updates/filter-updates.pipe'
|
||||||
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
|
import { LiveApiService } from 'src/app/services/api/embassy-live-api.service'
|
||||||
|
import { MockApiService } from 'src/app/services/api/embassy-mock-api.service'
|
||||||
|
import { AuthService } from 'src/app/services/auth.service'
|
||||||
|
import { CategoryService } from 'src/app/services/category.service'
|
||||||
|
import { ClientStorageService } from 'src/app/services/client-storage.service'
|
||||||
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
|
import { DateTransformerService } from 'src/app/services/date-transformer.service'
|
||||||
|
import { DatetimeTransformerService } from 'src/app/services/datetime-transformer.service'
|
||||||
|
import {
|
||||||
|
PATCH_CACHE,
|
||||||
|
PatchDbSource,
|
||||||
|
} from 'src/app/services/patch-db/patch-db-source'
|
||||||
|
import { StateService } from 'src/app/services/state.service'
|
||||||
|
import { StorageService } from 'src/app/services/storage.service'
|
||||||
|
import { environment } from 'src/environments/environment'
|
||||||
|
|
||||||
|
import { ROUTES } from './app.routes'
|
||||||
|
|
||||||
|
const {
|
||||||
|
useMocks,
|
||||||
|
ui: { api },
|
||||||
|
} = require('../../../../config.json') as WorkspaceConfig
|
||||||
|
|
||||||
|
export const APP_CONFIG: ApplicationConfig = {
|
||||||
|
providers: [
|
||||||
|
provideZoneChangeDetection(),
|
||||||
|
provideAnimations(),
|
||||||
|
provideEventPlugins(),
|
||||||
|
provideHttpClient(withInterceptorsFromDi(), withFetch()),
|
||||||
|
provideRouter(
|
||||||
|
ROUTES,
|
||||||
|
withDisabledInitialNavigation(),
|
||||||
|
withComponentInputBinding(),
|
||||||
|
withPreloading(PreloadAllModules),
|
||||||
|
withInMemoryScrolling({ scrollPositionRestoration: 'enabled' }),
|
||||||
|
withRouterConfig({ paramsInheritanceStrategy: 'always' }),
|
||||||
|
),
|
||||||
|
provideServiceWorker('ngsw-worker.js', {
|
||||||
|
enabled: environment.useServiceWorker,
|
||||||
|
// Register the ServiceWorker as soon as the application is stable
|
||||||
|
// or after 30 seconds (whichever comes first).
|
||||||
|
registrationStrategy: 'registerWhenStable:30000',
|
||||||
|
}),
|
||||||
|
I18N_PROVIDERS,
|
||||||
|
FilterPackagesPipe,
|
||||||
|
FilterUpdatesPipe,
|
||||||
|
UntypedFormBuilder,
|
||||||
|
tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }),
|
||||||
|
tuiButtonOptionsProvider({ size: 'm' }),
|
||||||
|
tuiDropdownOptionsProvider({ appearance: 'start-os' }),
|
||||||
|
tuiAlertOptionsProvider({
|
||||||
|
autoClose: appearance => (appearance === 'negative' ? 0 : 3000),
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
provide: TUI_DATE_FORMAT,
|
||||||
|
useValue: of({
|
||||||
|
mode: 'MDY',
|
||||||
|
separator: '/',
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: TUI_DATE_VALUE_TRANSFORMER,
|
||||||
|
useClass: DateTransformerService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: TUI_DATE_TIME_VALUE_TRANSFORMER,
|
||||||
|
useClass: DatetimeTransformerService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ApiService,
|
||||||
|
useClass: useMocks ? MockApiService : LiveApiService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: PatchDB,
|
||||||
|
deps: [PatchDbSource, PATCH_CACHE],
|
||||||
|
useClass: PatchDB,
|
||||||
|
},
|
||||||
|
provideAppInitializer(() => {
|
||||||
|
const i18n = inject(i18nService)
|
||||||
|
const origin = inject(WA_LOCATION).origin
|
||||||
|
const module_or_path = new URL('/assets/argon2_bg.wasm', origin)
|
||||||
|
|
||||||
|
initArgon({ module_or_path })
|
||||||
|
inject(StorageService).migrate036()
|
||||||
|
inject(AuthService).init()
|
||||||
|
inject(ClientStorageService).init()
|
||||||
|
inject(Router).initialNavigation()
|
||||||
|
i18n.setLanguage(i18n.language || 'english')
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
provide: RELATIVE_URL,
|
||||||
|
useValue: `/${api.url}/${api.version}`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AbstractCategoryService,
|
||||||
|
useClass: CategoryService,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: TUI_DIALOGS_CLOSE,
|
||||||
|
useFactory: () =>
|
||||||
|
merge(
|
||||||
|
inject(Router).events.pipe(filter(e => e instanceof ActivationStart)),
|
||||||
|
inject(StateService).pipe(
|
||||||
|
pairwise(),
|
||||||
|
filter(
|
||||||
|
([prev, curr]) =>
|
||||||
|
prev === 'running' &&
|
||||||
|
(curr === 'error' || curr === 'initializing'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: I18N_STORAGE,
|
||||||
|
useFactory: () => {
|
||||||
|
const api = inject(ApiService)
|
||||||
|
|
||||||
|
return (language: Languages) => api.setLanguage({ language })
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: VERSION,
|
||||||
|
useFactory: () => inject(ConfigService).version,
|
||||||
|
},
|
||||||
|
tuiObfuscateOptionsProvider({
|
||||||
|
recipes: {
|
||||||
|
mask: ({ length }) => '•'.repeat(length),
|
||||||
|
none: identity,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
provide: TUI_MEDIA,
|
||||||
|
useValue: {
|
||||||
|
mobile: 1000,
|
||||||
|
desktopSmall: 1280,
|
||||||
|
desktopLarge: Infinity,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
import {
|
|
||||||
provideHttpClient,
|
|
||||||
withFetch,
|
|
||||||
withInterceptorsFromDi,
|
|
||||||
} from '@angular/common/http'
|
|
||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { BrowserModule } from '@angular/platform-browser'
|
|
||||||
import { ServiceWorkerModule } from '@angular/service-worker'
|
|
||||||
import { TuiRoot } from '@taiga-ui/core'
|
|
||||||
import { ToastContainerComponent } from 'src/app/components/toast-container.component'
|
|
||||||
import { environment } from '../environments/environment'
|
|
||||||
import { AppComponent } from './app.component'
|
|
||||||
import { APP_PROVIDERS } from './app.providers'
|
|
||||||
import { RoutingModule } from './routing.module'
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
declarations: [AppComponent],
|
|
||||||
imports: [
|
|
||||||
BrowserModule,
|
|
||||||
RoutingModule,
|
|
||||||
ToastContainerComponent,
|
|
||||||
TuiRoot,
|
|
||||||
ServiceWorkerModule.register('ngsw-worker.js', {
|
|
||||||
enabled: environment.useServiceWorker,
|
|
||||||
// Register the ServiceWorker as soon as the application is stable
|
|
||||||
// or after 30 seconds (whichever comes first).
|
|
||||||
registrationStrategy: 'registerWhenStable:30000',
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
providers: [
|
|
||||||
APP_PROVIDERS,
|
|
||||||
provideHttpClient(withInterceptorsFromDi(), withFetch()),
|
|
||||||
],
|
|
||||||
bootstrap: [AppComponent],
|
|
||||||
})
|
|
||||||
export class AppModule {}
|
|
||||||
@@ -1,157 +0,0 @@
|
|||||||
import { inject, provideAppInitializer } from '@angular/core'
|
|
||||||
import { UntypedFormBuilder } from '@angular/forms'
|
|
||||||
import { provideAnimations } from '@angular/platform-browser/animations'
|
|
||||||
import { ActivationStart, Router } from '@angular/router'
|
|
||||||
import { WA_LOCATION } from '@ng-web-apis/common'
|
|
||||||
import initArgon from '@start9labs/argon2'
|
|
||||||
import {
|
|
||||||
AbstractCategoryService,
|
|
||||||
FilterPackagesPipe,
|
|
||||||
} from '@start9labs/marketplace'
|
|
||||||
import {
|
|
||||||
I18N_PROVIDERS,
|
|
||||||
I18N_STORAGE,
|
|
||||||
i18nService,
|
|
||||||
Languages,
|
|
||||||
RELATIVE_URL,
|
|
||||||
VERSION,
|
|
||||||
WorkspaceConfig,
|
|
||||||
} from '@start9labs/shared'
|
|
||||||
import { tuiObfuscateOptionsProvider } from '@taiga-ui/cdk'
|
|
||||||
import {
|
|
||||||
TUI_DATE_FORMAT,
|
|
||||||
TUI_DIALOGS_CLOSE,
|
|
||||||
TUI_MEDIA,
|
|
||||||
tuiAlertOptionsProvider,
|
|
||||||
tuiButtonOptionsProvider,
|
|
||||||
tuiDropdownOptionsProvider,
|
|
||||||
tuiNumberFormatProvider,
|
|
||||||
} from '@taiga-ui/core'
|
|
||||||
import { provideEventPlugins } from '@taiga-ui/event-plugins'
|
|
||||||
import {
|
|
||||||
TUI_DATE_TIME_VALUE_TRANSFORMER,
|
|
||||||
TUI_DATE_VALUE_TRANSFORMER,
|
|
||||||
} from '@taiga-ui/kit'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
|
||||||
import { filter, identity, merge, of, pairwise } from 'rxjs'
|
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
|
||||||
import {
|
|
||||||
PATCH_CACHE,
|
|
||||||
PatchDbSource,
|
|
||||||
} from 'src/app/services/patch-db/patch-db-source'
|
|
||||||
import { StateService } from 'src/app/services/state.service'
|
|
||||||
import { FilterUpdatesPipe } from './routes/portal/routes/updates/filter-updates.pipe'
|
|
||||||
import { ApiService } from './services/api/embassy-api.service'
|
|
||||||
import { LiveApiService } from './services/api/embassy-live-api.service'
|
|
||||||
import { MockApiService } from './services/api/embassy-mock-api.service'
|
|
||||||
import { AuthService } from './services/auth.service'
|
|
||||||
import { CategoryService } from './services/category.service'
|
|
||||||
import { ClientStorageService } from './services/client-storage.service'
|
|
||||||
import { DateTransformerService } from './services/date-transformer.service'
|
|
||||||
import { DatetimeTransformerService } from './services/datetime-transformer.service'
|
|
||||||
import { StorageService } from './services/storage.service'
|
|
||||||
|
|
||||||
const {
|
|
||||||
useMocks,
|
|
||||||
ui: { api },
|
|
||||||
} = require('../../../../config.json') as WorkspaceConfig
|
|
||||||
|
|
||||||
export const APP_PROVIDERS = [
|
|
||||||
provideAnimations(),
|
|
||||||
provideEventPlugins(),
|
|
||||||
I18N_PROVIDERS,
|
|
||||||
FilterPackagesPipe,
|
|
||||||
FilterUpdatesPipe,
|
|
||||||
UntypedFormBuilder,
|
|
||||||
tuiNumberFormatProvider({ decimalSeparator: '.', thousandSeparator: '' }),
|
|
||||||
tuiButtonOptionsProvider({ size: 'm' }),
|
|
||||||
tuiDropdownOptionsProvider({ appearance: 'start-os' }),
|
|
||||||
tuiAlertOptionsProvider({
|
|
||||||
autoClose: appearance => (appearance === 'negative' ? 0 : 3000),
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
provide: TUI_DATE_FORMAT,
|
|
||||||
useValue: of({
|
|
||||||
mode: 'MDY',
|
|
||||||
separator: '/',
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: TUI_DATE_VALUE_TRANSFORMER,
|
|
||||||
useClass: DateTransformerService,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: TUI_DATE_TIME_VALUE_TRANSFORMER,
|
|
||||||
useClass: DatetimeTransformerService,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: ApiService,
|
|
||||||
useClass: useMocks ? MockApiService : LiveApiService,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: PatchDB,
|
|
||||||
deps: [PatchDbSource, PATCH_CACHE],
|
|
||||||
useClass: PatchDB,
|
|
||||||
},
|
|
||||||
provideAppInitializer(() => {
|
|
||||||
const i18n = inject(i18nService)
|
|
||||||
const origin = inject(WA_LOCATION).origin
|
|
||||||
const module_or_path = new URL('/assets/argon2_bg.wasm', origin)
|
|
||||||
|
|
||||||
initArgon({ module_or_path })
|
|
||||||
inject(StorageService).migrate036()
|
|
||||||
inject(AuthService).init()
|
|
||||||
inject(ClientStorageService).init()
|
|
||||||
inject(Router).initialNavigation()
|
|
||||||
i18n.setLanguage(i18n.language || 'english')
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
provide: RELATIVE_URL,
|
|
||||||
useValue: `/${api.url}/${api.version}`,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: AbstractCategoryService,
|
|
||||||
useClass: CategoryService,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: TUI_DIALOGS_CLOSE,
|
|
||||||
useFactory: () =>
|
|
||||||
merge(
|
|
||||||
inject(Router).events.pipe(filter(e => e instanceof ActivationStart)),
|
|
||||||
inject(StateService).pipe(
|
|
||||||
pairwise(),
|
|
||||||
filter(
|
|
||||||
([prev, curr]) =>
|
|
||||||
prev === 'running' &&
|
|
||||||
(curr === 'error' || curr === 'initializing'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: I18N_STORAGE,
|
|
||||||
useFactory: () => {
|
|
||||||
const api = inject(ApiService)
|
|
||||||
|
|
||||||
return (language: Languages) => api.setLanguage({ language })
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: VERSION,
|
|
||||||
useFactory: () => inject(ConfigService).version,
|
|
||||||
},
|
|
||||||
tuiObfuscateOptionsProvider({
|
|
||||||
recipes: {
|
|
||||||
mask: ({ length }) => '•'.repeat(length),
|
|
||||||
none: identity,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
provide: TUI_MEDIA,
|
|
||||||
useValue: {
|
|
||||||
mobile: 1000,
|
|
||||||
desktopSmall: 1280,
|
|
||||||
desktopLarge: Infinity,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
import { NgModule } from '@angular/core'
|
import { NgModule } from '@angular/core'
|
||||||
import { PreloadAllModules, RouterModule, Routes } from '@angular/router'
|
import { PreloadAllModules, RouterModule, Routes } from '@angular/router'
|
||||||
|
import { AuthGuard } from 'src/app/guards/auth.guard'
|
||||||
|
import { UnauthGuard } from 'src/app/guards/unauth.guard'
|
||||||
import { stateNot } from 'src/app/services/state.service'
|
import { stateNot } from 'src/app/services/state.service'
|
||||||
import { AuthGuard } from './guards/auth.guard'
|
|
||||||
import { UnauthGuard } from './guards/unauth.guard'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
export const ROUTES: Routes = [
|
||||||
{
|
{
|
||||||
path: 'diagnostic',
|
path: 'diagnostic',
|
||||||
canActivate: [stateNot(['initializing', 'running'])],
|
canActivate: [stateNot(['initializing', 'running'])],
|
||||||
loadChildren: () => import('./routes/diagnostic/diagnostic.module'),
|
loadChildren: () => import('./routes/diagnostic/diagnostic.routes'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'initializing',
|
path: 'initializing',
|
||||||
@@ -18,8 +18,7 @@ const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'login',
|
path: 'login',
|
||||||
canActivate: [UnauthGuard, stateNot(['error', 'initializing'])],
|
canActivate: [UnauthGuard, stateNot(['error', 'initializing'])],
|
||||||
loadChildren: () =>
|
loadComponent: () => import('./routes/login/login.page'),
|
||||||
import('./routes/login/login.module').then(m => m.LoginPageModule),
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
@@ -32,17 +31,3 @@ const routes: Routes = [
|
|||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
RouterModule.forRoot(routes, {
|
|
||||||
scrollPositionRestoration: 'enabled',
|
|
||||||
paramsInheritanceStrategy: 'always',
|
|
||||||
preloadingStrategy: PreloadAllModules,
|
|
||||||
initialNavigation: 'disabled',
|
|
||||||
bindToComponentInputs: true,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
exports: [RouterModule],
|
|
||||||
})
|
|
||||||
export class RoutingModule {}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
|
||||||
|
|
||||||
const ROUTES: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./home/home.module').then(m => m.HomePageModule),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'logs',
|
|
||||||
loadComponent: () => import('./logs.component'),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [RouterModule.forChild(ROUTES)],
|
|
||||||
})
|
|
||||||
export default class DiagnosticModule {}
|
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Routes } from '@angular/router'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
loadComponent: () => import('./home/home.page'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'logs',
|
||||||
|
loadComponent: () => import('./logs.component'),
|
||||||
|
},
|
||||||
|
] satisfies Routes
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import { TuiButton } from '@taiga-ui/core'
|
|
||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
|
||||||
import { HomePage } from './home.page'
|
|
||||||
import { i18nPipe } from '@start9labs/shared'
|
|
||||||
|
|
||||||
const ROUTES: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: HomePage,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [CommonModule, TuiButton, RouterModule.forChild(ROUTES), i18nPipe],
|
|
||||||
declarations: [HomePage],
|
|
||||||
})
|
|
||||||
export class HomePageModule {}
|
|
||||||
@@ -1,6 +1,14 @@
|
|||||||
|
import { CommonModule } from '@angular/common'
|
||||||
import { Component, Inject } from '@angular/core'
|
import { Component, Inject } from '@angular/core'
|
||||||
|
import { RouterLink } from '@angular/router'
|
||||||
import { WA_WINDOW } from '@ng-web-apis/common'
|
import { WA_WINDOW } from '@ng-web-apis/common'
|
||||||
import { DialogService, i18nKey, LoadingService } from '@start9labs/shared'
|
import {
|
||||||
|
DialogService,
|
||||||
|
i18nKey,
|
||||||
|
i18nPipe,
|
||||||
|
LoadingService,
|
||||||
|
} from '@start9labs/shared'
|
||||||
|
import { TuiButton } from '@taiga-ui/core'
|
||||||
import { filter } from 'rxjs'
|
import { filter } from 'rxjs'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
@@ -9,9 +17,9 @@ import { ConfigService } from 'src/app/services/config.service'
|
|||||||
selector: 'diagnostic-home',
|
selector: 'diagnostic-home',
|
||||||
templateUrl: 'home.component.html',
|
templateUrl: 'home.component.html',
|
||||||
styleUrls: ['home.page.scss'],
|
styleUrls: ['home.page.scss'],
|
||||||
standalone: false,
|
imports: [CommonModule, TuiButton, i18nPipe, RouterLink],
|
||||||
})
|
})
|
||||||
export class HomePage {
|
export default class HomePage {
|
||||||
restarted = false
|
restarted = false
|
||||||
error?: {
|
error?: {
|
||||||
code: number
|
code: number
|
||||||
|
|||||||
@@ -20,9 +20,7 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|||||||
import { StateService } from 'src/app/services/state.service'
|
import { StateService } from 'src/app/services/state.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
template: `
|
template: '<app-initializing [progress]="progress()" />',
|
||||||
<app-initializing [progress]="progress()" />
|
|
||||||
`,
|
|
||||||
providers: [provideSetupLogsService(ApiService)],
|
providers: [provideSetupLogsService(ApiService)],
|
||||||
styles: ':host { height: 100%; }',
|
styles: ':host { height: 100%; }',
|
||||||
imports: [InitializingComponent],
|
imports: [InitializingComponent],
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { NgModule } from '@angular/core'
|
|
||||||
import { FormsModule } from '@angular/forms'
|
|
||||||
import { RouterModule, Routes } from '@angular/router'
|
|
||||||
import { i18nPipe } from '@start9labs/shared'
|
|
||||||
import { TuiAutoFocus } from '@taiga-ui/cdk'
|
|
||||||
import { TuiButton, TuiError, TuiIcon, TuiTextfield } from '@taiga-ui/core'
|
|
||||||
import { TuiPassword } from '@taiga-ui/kit'
|
|
||||||
import { TuiCardLarge } from '@taiga-ui/layout'
|
|
||||||
import { CAWizardComponent } from './ca-wizard/ca-wizard.component'
|
|
||||||
import { LoginPage } from './login.page'
|
|
||||||
|
|
||||||
const routes: Routes = [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
component: LoginPage,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
@NgModule({
|
|
||||||
imports: [
|
|
||||||
CommonModule,
|
|
||||||
FormsModule,
|
|
||||||
CAWizardComponent,
|
|
||||||
TuiButton,
|
|
||||||
TuiCardLarge,
|
|
||||||
...TuiTextfield,
|
|
||||||
TuiIcon,
|
|
||||||
TuiPassword,
|
|
||||||
TuiAutoFocus,
|
|
||||||
TuiError,
|
|
||||||
RouterModule.forChild(routes),
|
|
||||||
i18nPipe,
|
|
||||||
],
|
|
||||||
declarations: [LoginPage],
|
|
||||||
})
|
|
||||||
export class LoginPageModule {}
|
|
||||||
@@ -1,19 +1,38 @@
|
|||||||
import { Router } from '@angular/router'
|
import { CommonModule } from '@angular/common'
|
||||||
|
import { Component, DestroyRef, DOCUMENT, inject, Inject } from '@angular/core'
|
||||||
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'
|
||||||
import { Component, Inject, DestroyRef, inject, DOCUMENT } from '@angular/core'
|
import { FormsModule } from '@angular/forms'
|
||||||
|
import { Router } from '@angular/router'
|
||||||
|
import { i18nKey, i18nPipe, LoadingService } from '@start9labs/shared'
|
||||||
|
import { TuiAutoFocus } from '@taiga-ui/cdk'
|
||||||
|
import { TuiButton, TuiError, TuiIcon, TuiTextfield } from '@taiga-ui/core'
|
||||||
|
import { TuiPassword } from '@taiga-ui/kit'
|
||||||
|
import { TuiCardLarge } from '@taiga-ui/layout'
|
||||||
|
import { CAWizardComponent } from 'src/app/routes/login/ca-wizard/ca-wizard.component'
|
||||||
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
import { ApiService } from 'src/app/services/api/embassy-api.service'
|
||||||
import { AuthService } from 'src/app/services/auth.service'
|
import { AuthService } from 'src/app/services/auth.service'
|
||||||
import { ConfigService } from 'src/app/services/config.service'
|
import { ConfigService } from 'src/app/services/config.service'
|
||||||
import { i18nKey, LoadingService } from '@start9labs/shared'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'login',
|
selector: 'login',
|
||||||
templateUrl: './login.component.html',
|
templateUrl: './login.component.html',
|
||||||
styleUrls: ['./login.page.scss'],
|
styleUrls: ['./login.page.scss'],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
CAWizardComponent,
|
||||||
|
TuiButton,
|
||||||
|
TuiCardLarge,
|
||||||
|
TuiTextfield,
|
||||||
|
TuiIcon,
|
||||||
|
TuiPassword,
|
||||||
|
TuiAutoFocus,
|
||||||
|
TuiError,
|
||||||
|
i18nPipe,
|
||||||
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
standalone: false,
|
|
||||||
})
|
})
|
||||||
export class LoginPage {
|
export default class LoginPage {
|
||||||
password = ''
|
password = ''
|
||||||
error: i18nKey | null = null
|
error: i18nKey | null = null
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { HeaderStatusComponent } from './status.component'
|
|||||||
height: 2.75rem;
|
height: 2.75rem;
|
||||||
border-radius: var(--bumper);
|
border-radius: var(--bumper);
|
||||||
margin: var(--bumper);
|
margin: var(--bumper);
|
||||||
|
clip-path: inset(0 round var(--bumper));
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
filter: grayscale(1) brightness(0.75);
|
filter: grayscale(1) brightness(0.75);
|
||||||
|
|
||||||
@@ -107,7 +108,8 @@ import { HeaderStatusComponent } from './status.component'
|
|||||||
|
|
||||||
&:has([data-status='success']) {
|
&:has([data-status='success']) {
|
||||||
--status: transparent;
|
--status: transparent;
|
||||||
filter: none;
|
// "none" breaks border radius in Firefox
|
||||||
|
filter: grayscale(0.001);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
Input,
|
Input,
|
||||||
Output,
|
Output,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { UnitConversionPipesModule } from '@start9labs/shared'
|
import { ConvertBytesPipe } from '@start9labs/shared'
|
||||||
import { TuiButton } from '@taiga-ui/core'
|
import { TuiButton } from '@taiga-ui/core'
|
||||||
import { TuiSkeleton } from '@taiga-ui/kit'
|
import { TuiSkeleton } from '@taiga-ui/kit'
|
||||||
import { UnknownDisk } from 'src/app/services/api/api.types'
|
import { UnknownDisk } from 'src/app/services/api/api.types'
|
||||||
@@ -109,7 +109,7 @@ import { UnknownDisk } from 'src/app/services/api/api.types'
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [TuiButton, UnitConversionPipesModule, TuiSkeleton],
|
imports: [TuiButton, ConvertBytesPipe, TuiSkeleton],
|
||||||
})
|
})
|
||||||
export class BackupsPhysicalComponent {
|
export class BackupsPhysicalComponent {
|
||||||
@Input()
|
@Input()
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { MarketplacePkg } from '@start9labs/marketplace'
|
|||||||
import {
|
import {
|
||||||
ErrorService,
|
ErrorService,
|
||||||
Exver,
|
Exver,
|
||||||
ExverPipesModule,
|
ExverComparesPipe,
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
i18nService,
|
i18nService,
|
||||||
LoadingService,
|
LoadingService,
|
||||||
@@ -107,7 +107,7 @@ type KEYS = 'id' | 'version' | 'alerts' | 'flavor'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
ExverPipesModule,
|
ExverComparesPipe,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
ToManifestPipe,
|
ToManifestPipe,
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
@@ -150,7 +150,11 @@ export class MarketplaceControlsComponent {
|
|||||||
const originalUrl = localPkg?.registry || null
|
const originalUrl = localPkg?.registry || null
|
||||||
|
|
||||||
if (!localPkg) {
|
if (!localPkg) {
|
||||||
if (await this.alerts.alertInstall(this.i18n.localize(this.pkg().alerts.install || ''))) {
|
if (
|
||||||
|
await this.alerts.alertInstall(
|
||||||
|
this.i18n.localize(this.pkg().alerts.install || ''),
|
||||||
|
)
|
||||||
|
) {
|
||||||
this.installOrUpload(currentUrl)
|
this.installOrUpload(currentUrl)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { MenuModule } from '@start9labs/marketplace'
|
import { MenuComponent } from '@start9labs/marketplace'
|
||||||
import { TuiIcon, TuiButton, TuiAppearance } from '@taiga-ui/core'
|
import { TuiIcon, TuiButton, TuiAppearance } from '@taiga-ui/core'
|
||||||
import { MARKETPLACE_REGISTRY } from '../modals/registry.component'
|
import { MARKETPLACE_REGISTRY } from '../modals/registry.component'
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
@@ -41,7 +41,7 @@ import { DialogService, i18nPipe } from '@start9labs/shared'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
MenuModule,
|
MenuComponent,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
TuiIcon,
|
TuiIcon,
|
||||||
TuiAppearance,
|
TuiAppearance,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
@@ -8,7 +7,7 @@ import {
|
|||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { toSignal } from '@angular/core/rxjs-interop'
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
import { ActivatedRoute, Router } from '@angular/router'
|
import { ActivatedRoute, Router } from '@angular/router'
|
||||||
import { ItemModule, MarketplacePkg } from '@start9labs/marketplace'
|
import { ItemComponent, MarketplacePkg } from '@start9labs/marketplace'
|
||||||
import { TuiAutoFocus } from '@taiga-ui/cdk'
|
import { TuiAutoFocus } from '@taiga-ui/cdk'
|
||||||
import { TuiButton, TuiDropdownService, TuiPopup } from '@taiga-ui/core'
|
import { TuiButton, TuiDropdownService, TuiPopup } from '@taiga-ui/core'
|
||||||
import { TuiDrawer } from '@taiga-ui/kit'
|
import { TuiDrawer } from '@taiga-ui/kit'
|
||||||
@@ -78,8 +77,7 @@ import { MarketplaceSidebarService } from '../services/sidebar.service'
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
ItemComponent,
|
||||||
ItemModule,
|
|
||||||
TuiAutoFocus,
|
TuiAutoFocus,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
TuiPopup,
|
TuiPopup,
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import { ActivatedRoute, Router } from '@angular/router'
|
|||||||
import {
|
import {
|
||||||
AbstractCategoryService,
|
AbstractCategoryService,
|
||||||
FilterPackagesPipe,
|
FilterPackagesPipe,
|
||||||
FilterPackagesPipeModule,
|
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { i18nPipe } from '@start9labs/shared'
|
import { i18nPipe } from '@start9labs/shared'
|
||||||
import { TuiScrollbar } from '@taiga-ui/core'
|
import { TuiScrollbar } from '@taiga-ui/core'
|
||||||
@@ -153,7 +152,7 @@ import { ConfigService } from 'src/app/services/config.service'
|
|||||||
MarketplaceMenuComponent,
|
MarketplaceMenuComponent,
|
||||||
MarketplaceNotificationComponent,
|
MarketplaceNotificationComponent,
|
||||||
TuiScrollbar,
|
TuiScrollbar,
|
||||||
FilterPackagesPipeModule,
|
FilterPackagesPipe,
|
||||||
TitleDirective,
|
TitleDirective,
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -15,12 +15,7 @@ import {
|
|||||||
MarketplaceReleaseNotesComponent,
|
MarketplaceReleaseNotesComponent,
|
||||||
MarketplaceVersionsComponent,
|
MarketplaceVersionsComponent,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import {
|
import { DialogService, EmptyPipe, Exver, MARKDOWN } from '@start9labs/shared'
|
||||||
DialogService,
|
|
||||||
Exver,
|
|
||||||
MARKDOWN,
|
|
||||||
SharedPipesModule,
|
|
||||||
} from '@start9labs/shared'
|
|
||||||
import { TuiLoader } from '@taiga-ui/core'
|
import { TuiLoader } from '@taiga-ui/core'
|
||||||
import {
|
import {
|
||||||
BehaviorSubject,
|
BehaviorSubject,
|
||||||
@@ -115,7 +110,7 @@ import { MarketplaceControlsComponent } from '../components/controls.component'
|
|||||||
CommonModule,
|
CommonModule,
|
||||||
MarketplacePackageHeroComponent,
|
MarketplacePackageHeroComponent,
|
||||||
MarketplaceDependenciesComponent,
|
MarketplaceDependenciesComponent,
|
||||||
SharedPipesModule,
|
EmptyPipe,
|
||||||
TuiLoader,
|
TuiLoader,
|
||||||
MarketplaceLinksComponent,
|
MarketplaceLinksComponent,
|
||||||
MarketplaceFlavorsComponent,
|
MarketplaceFlavorsComponent,
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
import { CommonModule } from '@angular/common'
|
||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
import { Router } from '@angular/router'
|
import { Router } from '@angular/router'
|
||||||
import {
|
import { MarketplaceRegistryComponent } from '@start9labs/marketplace'
|
||||||
MarketplaceRegistryComponent,
|
|
||||||
StoreIconComponentModule,
|
|
||||||
} from '@start9labs/marketplace'
|
|
||||||
import {
|
import {
|
||||||
DialogService,
|
DialogService,
|
||||||
ErrorService,
|
ErrorService,
|
||||||
@@ -14,6 +11,7 @@ import {
|
|||||||
sameUrl,
|
sameUrl,
|
||||||
toUrl,
|
toUrl,
|
||||||
} from '@start9labs/shared'
|
} from '@start9labs/shared'
|
||||||
|
import { IST, utils } from '@start9labs/start-sdk'
|
||||||
import { TuiButton, TuiDialogContext, TuiIcon, TuiTitle } from '@taiga-ui/core'
|
import { TuiButton, TuiDialogContext, TuiIcon, TuiTitle } from '@taiga-ui/core'
|
||||||
import { TuiCell } from '@taiga-ui/layout'
|
import { TuiCell } from '@taiga-ui/layout'
|
||||||
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
import { injectContext, PolymorpheusComponent } from '@taiga-ui/polymorpheus'
|
||||||
@@ -24,7 +22,6 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
|
|||||||
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
import { FormDialogService } from 'src/app/services/form-dialog.service'
|
||||||
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
import { MarketplaceService } from 'src/app/services/marketplace.service'
|
||||||
import { DataModel } from 'src/app/services/patch-db/data-model'
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
import { IST, utils } from '@start9labs/start-sdk'
|
|
||||||
import { StorageService } from 'src/app/services/storage.service'
|
import { StorageService } from 'src/app/services/storage.service'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@@ -80,7 +77,6 @@ import { StorageService } from 'src/app/services/storage.service'
|
|||||||
TuiTitle,
|
TuiTitle,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
MarketplaceRegistryComponent,
|
MarketplaceRegistryComponent,
|
||||||
StoreIconComponentModule,
|
|
||||||
i18nPipe,
|
i18nPipe,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { Component, inject, input } from '@angular/core'
|
import { Component, inject, input } from '@angular/core'
|
||||||
import {
|
import {
|
||||||
MarketplaceAboutComponent,
|
MarketplaceAboutComponent,
|
||||||
@@ -7,7 +6,7 @@ import {
|
|||||||
MarketplacePackageHeroComponent,
|
MarketplacePackageHeroComponent,
|
||||||
MarketplaceReleaseNotesComponent,
|
MarketplaceReleaseNotesComponent,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { DialogService, MARKDOWN, SharedPipesModule } from '@start9labs/shared'
|
import { DialogService, EmptyPipe, MARKDOWN } from '@start9labs/shared'
|
||||||
import { of } from 'rxjs'
|
import { of } from 'rxjs'
|
||||||
import { MarketplaceControlsComponent } from '../marketplace/components/controls.component'
|
import { MarketplaceControlsComponent } from '../marketplace/components/controls.component'
|
||||||
import { MarketplacePkgSideload } from './sideload.utils'
|
import { MarketplacePkgSideload } from './sideload.utils'
|
||||||
@@ -70,8 +69,7 @@ import { MarketplacePkgSideload } from './sideload.utils'
|
|||||||
}
|
}
|
||||||
`,
|
`,
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
EmptyPipe,
|
||||||
SharedPipesModule,
|
|
||||||
MarketplaceAboutComponent,
|
MarketplaceAboutComponent,
|
||||||
MarketplaceLinksComponent,
|
MarketplaceLinksComponent,
|
||||||
MarketplacePackageHeroComponent,
|
MarketplacePackageHeroComponent,
|
||||||
|
|||||||
@@ -7,12 +7,7 @@ import {
|
|||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { toSignal } from '@angular/core/rxjs-interop'
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
import { ActivatedRoute, RouterLink } from '@angular/router'
|
import { ActivatedRoute, RouterLink } from '@angular/router'
|
||||||
import {
|
import { DialogService, DocsLinkDirective, i18nPipe } from '@start9labs/shared'
|
||||||
DialogService,
|
|
||||||
DocsLinkDirective,
|
|
||||||
i18nPipe,
|
|
||||||
UnitConversionPipesModule,
|
|
||||||
} from '@start9labs/shared'
|
|
||||||
import { TuiMapperPipe } from '@taiga-ui/cdk'
|
import { TuiMapperPipe } from '@taiga-ui/cdk'
|
||||||
import {
|
import {
|
||||||
TuiButton,
|
TuiButton,
|
||||||
@@ -150,7 +145,6 @@ import { BACKUP_RESTORE } from './restore.component'
|
|||||||
TuiNotification,
|
TuiNotification,
|
||||||
TuiMapperPipe,
|
TuiMapperPipe,
|
||||||
TitleDirective,
|
TitleDirective,
|
||||||
UnitConversionPipesModule,
|
|
||||||
BackupNetworkComponent,
|
BackupNetworkComponent,
|
||||||
BackupPhysicalComponent,
|
BackupPhysicalComponent,
|
||||||
BackupProgressComponent,
|
BackupProgressComponent,
|
||||||
|
|||||||
@@ -5,11 +5,7 @@ import {
|
|||||||
output,
|
output,
|
||||||
} from '@angular/core'
|
} from '@angular/core'
|
||||||
import { ActivatedRoute } from '@angular/router'
|
import { ActivatedRoute } from '@angular/router'
|
||||||
import {
|
import { ConvertBytesPipe, DialogService, i18nPipe } from '@start9labs/shared'
|
||||||
DialogService,
|
|
||||||
i18nPipe,
|
|
||||||
UnitConversionPipesModule,
|
|
||||||
} from '@start9labs/shared'
|
|
||||||
import { TuiButton, TuiIcon } from '@taiga-ui/core'
|
import { TuiButton, TuiIcon } from '@taiga-ui/core'
|
||||||
import { TuiTooltip } from '@taiga-ui/kit'
|
import { TuiTooltip } from '@taiga-ui/kit'
|
||||||
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
import { PlaceholderComponent } from 'src/app/routes/portal/components/placeholder.component'
|
||||||
@@ -115,7 +111,7 @@ import { BackupStatusComponent } from './status.component'
|
|||||||
TuiButton,
|
TuiButton,
|
||||||
TuiIcon,
|
TuiIcon,
|
||||||
TuiTooltip,
|
TuiTooltip,
|
||||||
UnitConversionPipesModule,
|
ConvertBytesPipe,
|
||||||
PlaceholderComponent,
|
PlaceholderComponent,
|
||||||
BackupStatusComponent,
|
BackupStatusComponent,
|
||||||
TableComponent,
|
TableComponent,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, inject } from '@angular/core'
|
||||||
import { toSignal } from '@angular/core/rxjs-interop'
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
import { FormsModule, ReactiveFormsModule } from '@angular/forms'
|
||||||
@@ -94,7 +93,6 @@ const ipv6 =
|
|||||||
`,
|
`,
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
|
||||||
FormsModule,
|
FormsModule,
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
FormGroupComponent,
|
FormGroupComponent,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { CommonModule } from '@angular/common'
|
|
||||||
import {
|
import {
|
||||||
ChangeDetectionStrategy,
|
ChangeDetectionStrategy,
|
||||||
Component,
|
Component,
|
||||||
@@ -144,7 +143,6 @@ import { GatewaysTableComponent } from './table.component'
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
providers: [GatewayService],
|
providers: [GatewayService],
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
|
||||||
FormsModule,
|
FormsModule,
|
||||||
RouterLink,
|
RouterLink,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
import { toSignal } from '@angular/core/rxjs-interop'
|
import { toSignal } from '@angular/core/rxjs-interop'
|
||||||
import {
|
import {
|
||||||
Marketplace,
|
Marketplace,
|
||||||
StoreIconComponentModule,
|
StoreIconComponent,
|
||||||
StoreIdentity,
|
StoreIdentity,
|
||||||
} from '@start9labs/marketplace'
|
} from '@start9labs/marketplace'
|
||||||
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
import { TUI_IS_MOBILE } from '@taiga-ui/cdk'
|
||||||
@@ -230,7 +230,7 @@ interface UpdatesData {
|
|||||||
TuiBadgeNotification,
|
TuiBadgeNotification,
|
||||||
TuiFade,
|
TuiFade,
|
||||||
TuiButton,
|
TuiButton,
|
||||||
StoreIconComponentModule,
|
StoreIconComponent,
|
||||||
FilterUpdatesPipe,
|
FilterUpdatesPipe,
|
||||||
UpdatesItemComponent,
|
UpdatesItemComponent,
|
||||||
TitleDirective,
|
TitleDirective,
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
import { enableProdMode } from '@angular/core'
|
import { enableProdMode } from '@angular/core'
|
||||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'
|
import { bootstrapApplication } from '@angular/platform-browser'
|
||||||
import { AppModule } from './app/app.module'
|
import { AppComponent } from 'src/app/app.component'
|
||||||
import { environment } from './environments/environment'
|
import { APP_CONFIG } from 'src/app/app.config'
|
||||||
|
import { environment } from 'src/environments/environment'
|
||||||
|
|
||||||
if (environment.production) {
|
if (environment.production) {
|
||||||
enableProdMode()
|
enableProdMode()
|
||||||
}
|
}
|
||||||
|
|
||||||
platformBrowserDynamic()
|
bootstrapApplication(AppComponent, APP_CONFIG).catch(console.error)
|
||||||
.bootstrapModule(AppModule)
|
|
||||||
.catch(err => console.error(err))
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
"importHelpers": true,
|
"importHelpers": true,
|
||||||
"target": "es2022",
|
"target": "es2022",
|
||||||
"module": "es2020",
|
"module": "es2020",
|
||||||
"lib": ["es2020", "dom"],
|
|
||||||
"useDefineForClassFields": false,
|
"useDefineForClassFields": false,
|
||||||
"paths": {
|
"paths": {
|
||||||
/* These paths are relative to each app base folder */
|
/* These paths are relative to each app base folder */
|
||||||
|
|||||||
Reference in New Issue
Block a user