sdk tweaks (#2760)

* sdk tweaks

* update action result types

* accommodate new action response types

* fix: show action value labels

* Feature/get status effect (#2765)

* wip: get status

* feat: Add the get_status for effects

* feat: Do a callback

---------

Co-authored-by: J H <dragondef@gmail.com>

---------

Co-authored-by: Matt Hill <mattnine@protonmail.com>
Co-authored-by: waterplea <alexander@inkin.ru>
Co-authored-by: J H <dragondef@gmail.com>
This commit is contained in:
Aiden McClelland
2024-10-28 12:12:36 -06:00
committed by GitHub
parent 42cfd69463
commit 26ae0bf207
28 changed files with 871 additions and 456 deletions

View File

@@ -284,6 +284,9 @@ export function makeEffects(context: EffectContext): Effects {
>
},
getStatus(...[o]: Parameters<T.Effects["getStatus"]>) {
return rpcRound("get-status", o) as ReturnType<T.Effects["getStatus"]>
},
setMainStatus(o: { status: "running" | "stopped" }): Promise<null> {
return rpcRound("set-main-status", o) as ReturnType<
T.Effects["setHealth"]

View File

@@ -245,10 +245,10 @@ const matchProperties = object({
function convertProperties(
name: string,
value: PropertiesValue,
): T.ActionResultV1 {
): T.ActionResultMember {
if (value.type === "string") {
return {
type: "string",
type: "single",
name,
description: value.description,
copyable: value.copyable || false,
@@ -258,9 +258,9 @@ function convertProperties(
}
}
return {
type: "object",
type: "group",
name,
description: value.description || undefined,
description: value.description,
value: Object.entries(value.value).map(([name, value]) =>
convertProperties(name, value),
),
@@ -459,13 +459,14 @@ export class SystemForEmbassy implements System {
} else if (actionId === "properties") {
return {
version: "1",
type: "object",
name: "Properties",
description:
"Runtime information, credentials, and other values of interest",
value: Object.entries(await this.properties(effects, timeoutMs)).map(
([name, value]) => convertProperties(name, value),
),
title: "Properties",
message: null,
result: {
type: "group",
value: Object.entries(await this.properties(effects, timeoutMs)).map(
([name, value]) => convertProperties(name, value),
),
},
}
} else {
return this.action(effects, actionId, input, timeoutMs)
@@ -814,13 +815,13 @@ export class SystemForEmbassy implements System {
const actionProcedure = this.manifest.actions?.[actionId]?.implementation
const toActionResult = ({
message,
value = "",
value,
copyable,
qr,
}: U.ActionResult): T.ActionResult => ({
version: "0",
message,
value,
value: value ?? null,
copyable,
qr,
})

View File

@@ -1,3 +1,4 @@
use std::collections::BTreeMap;
use std::fmt;
use clap::{CommandFactory, FromArgMatches, Parser};
@@ -6,7 +7,6 @@ use models::PackageId;
use qrcode::QrCode;
use rpc_toolkit::{from_fn_async, Context, HandlerExt, ParentHandler};
use serde::{Deserialize, Serialize};
use std::collections::BTreeMap;
use tracing::instrument;
use ts_rs::TS;
@@ -24,7 +24,7 @@ pub fn action_api<C: Context>() -> ParentHandler<C> {
from_fn_async(get_action_input)
.with_display_serializable()
.with_about("Get action input spec")
.with_call_remote::<CliContext>()
.with_call_remote::<CliContext>(),
)
.subcommand(
"run",
@@ -37,7 +37,7 @@ pub fn action_api<C: Context>() -> ParentHandler<C> {
Ok(())
})
.with_about("Run service action")
.with_call_remote::<CliContext>()
.with_call_remote::<CliContext>(),
)
}
@@ -121,83 +121,78 @@ impl fmt::Display for ActionResultV0 {
}
}
#[derive(Debug, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
pub struct ActionResultV1 {
pub title: String,
pub message: Option<String>,
pub result: Option<ActionResultValue>,
}
#[derive(Debug, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
pub struct ActionResultMember {
pub name: String,
pub description: Option<String>,
#[serde(flatten)]
#[ts(flatten)]
pub value: ActionResultValue,
}
#[derive(Debug, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[serde(rename_all_fields = "camelCase")]
#[serde(tag = "type")]
pub enum ActionResultV1 {
String {
name: String,
pub enum ActionResultValue {
Single {
value: String,
description: Option<String>,
copyable: bool,
qr: bool,
masked: bool,
},
Object {
name: String,
value: Vec<ActionResultV1>,
#[ts(optional)]
description: Option<String>,
Group {
value: Vec<ActionResultMember>,
},
}
impl ActionResultV1 {
impl ActionResultValue {
fn fmt_rec(&self, f: &mut fmt::Formatter<'_>, indent: usize) -> fmt::Result {
match self {
Self::String {
name,
value,
description,
qr,
..
} => {
for i in 0..indent {
Self::Single { value, qr, .. } => {
for _ in 0..indent {
write!(f, " ")?;
}
write!(f, "{name}")?;
if let Some(description) = description {
write!(f, ": {description}")?;
}
if !value.is_empty() {
write!(f, ":\n")?;
for i in 0..indent {
write!(f, "{value}")?;
if *qr {
use qrcode::render::unicode;
writeln!(f)?;
for _ in 0..indent {
write!(f, " ")?;
}
write!(f, "{value}")?;
if *qr {
use qrcode::render::unicode;
write!(f, "\n")?;
for i in 0..indent {
write!(f, " ")?;
}
write!(
f,
"{}",
QrCode::new(value.as_bytes())
.unwrap()
.render::<unicode::Dense1x2>()
.build()
)?;
}
write!(
f,
"{}",
QrCode::new(value.as_bytes())
.unwrap()
.render::<unicode::Dense1x2>()
.build()
)?;
}
}
Self::Object {
name,
value,
description,
} => {
for i in 0..indent {
write!(f, " ")?;
}
write!(f, "{name}")?;
if let Some(description) = description {
write!(f, ": {description}")?;
}
for value in value {
write!(f, ":\n")?;
for i in 0..indent {
Self::Group { value } => {
for ActionResultMember {
name,
description,
value,
} in value
{
for _ in 0..indent {
write!(f, " ")?;
}
write!(f, "{name}")?;
if let Some(description) = description {
write!(f, ": {description}")?;
}
writeln!(f, ":")?;
value.fmt_rec(f, indent + 1)?;
}
}
@@ -207,7 +202,14 @@ impl ActionResultV1 {
}
impl fmt::Display for ActionResultV1 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.fmt_rec(f, 0)
writeln!(f, "{}:", self.title)?;
if let Some(message) = &self.message {
writeln!(f, "{message}")?;
}
if let Some(result) = &self.result {
result.fmt_rec(f, 1)?;
}
Ok(())
}
}

View File

@@ -36,6 +36,7 @@ struct ServiceCallbackMap {
(NonDetachingJoinHandle<()>, Vec<CallbackHandler>),
>,
get_store: BTreeMap<PackageId, BTreeMap<JsonPointer, Vec<CallbackHandler>>>,
get_status: BTreeMap<PackageId, Vec<CallbackHandler>>,
}
impl ServiceCallbacks {
@@ -71,6 +72,10 @@ impl ServiceCallbacks {
});
!v.is_empty()
});
this.get_status.retain(|_, v| {
v.retain(|h| h.handle.is_active() && h.seed.strong_count() > 0);
!v.is_empty()
});
})
}
@@ -220,6 +225,20 @@ impl ServiceCallbacks {
.push(handler);
})
}
pub(super) fn add_get_status(&self, package_id: PackageId, handler: CallbackHandler) {
self.mutate(|this| this.get_status.entry(package_id).or_default().push(handler))
}
#[must_use]
pub fn get_status(&self, package_id: &PackageId) -> Option<CallbackHandlers> {
self.mutate(|this| {
if let Some(watched) = this.get_status.remove(package_id) {
Some(CallbackHandlers(watched))
} else {
None
}
.filter(|cb| !cb.0.is_empty())
})
}
pub(super) fn add_get_store(
&self,

View File

@@ -1,9 +1,11 @@
use std::str::FromStr;
use clap::builder::ValueParserFactory;
use models::FromStrParser;
use models::{FromStrParser, PackageId};
use crate::service::effects::prelude::*;
use crate::service::rpc::CallbackId;
use crate::status::MainStatus;
pub async fn restart(
context: EffectContext,
@@ -23,6 +25,46 @@ pub async fn shutdown(
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize, TS, Parser)]
#[serde(rename_all = "camelCase")]
#[ts(export)]
pub struct GetStatusParams {
#[ts(optional)]
pub package_id: Option<PackageId>,
#[ts(optional)]
#[arg(skip)]
pub callback: Option<CallbackId>,
}
pub async fn get_status(
context: EffectContext,
GetStatusParams {
package_id,
callback,
}: GetStatusParams,
) -> Result<MainStatus, Error> {
let context = context.deref()?;
let id = package_id.unwrap_or_else(|| context.seed.id.clone());
let db = context.seed.ctx.db.peek().await;
let status = db
.as_public()
.as_package_data()
.as_idx(&id)
.or_not_found(&id)?
.as_status()
.de()?;
if let Some(callback) = callback {
let callback = callback.register(&context.seed.persistent_container);
context.seed.ctx.callbacks.add_get_status(
id,
super::callbacks::CallbackHandler::new(&context, callback),
);
}
Ok(status)
}
#[derive(Debug, Clone, Serialize, Deserialize, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export)]

View File

@@ -50,6 +50,12 @@ pub fn handler<C: Context>() -> ParentHandler<C> {
.no_display()
.with_call_remote::<ContainerCliContext>(),
)
.subcommand(
"get-status",
from_fn_async(control::get_status)
.no_display()
.with_call_remote::<ContainerCliContext>(),
)
// dependency
.subcommand(
"set-dependencies",

View File

@@ -1,6 +1,8 @@
use std::sync::Arc;
use std::time::Duration;
use imbl::vector;
use super::start_stop::StartStop;
use super::ServiceActorSeed;
use crate::prelude::*;
@@ -45,7 +47,8 @@ async fn service_actor_loop(
let id = &seed.id;
let kinds = current.borrow().kinds();
if let Err(e) = async {
seed.ctx
let major_changes_state = seed
.ctx
.db
.mutate(|d| {
if let Some(i) = d.as_public_mut().as_package_data_mut().as_idx_mut(&id) {
@@ -89,11 +92,22 @@ async fn service_actor_loop(
..
} => MainStatus::Stopped,
};
let previous = i.as_status().de()?;
i.as_status_mut().ser(&main_status)?;
return Ok(previous
.major_changes(&main_status)
.then_some((previous, main_status)));
}
Ok(())
Ok(None)
})
.await?;
if let Some((previous, new_state)) = major_changes_state {
if let Some(callbacks) = seed.ctx.callbacks.get_status(id) {
callbacks
.call(vector![to_value(&previous)?, to_value(&new_state)?])
.await?;
}
}
seed.synchronized.notify_waiters();
match kinds {

View File

@@ -66,6 +66,20 @@ impl MainStatus {
}
}
pub fn major_changes(&self, other: &Self) -> bool {
match (self, other) {
(MainStatus::Running { .. }, MainStatus::Running { .. }) => false,
(MainStatus::Starting { .. }, MainStatus::Starting { .. }) => false,
(MainStatus::Stopping, MainStatus::Stopping) => false,
(MainStatus::Stopped, MainStatus::Stopped) => false,
(MainStatus::Restarting, MainStatus::Restarting) => false,
(MainStatus::Restoring, MainStatus::Restoring) => false,
(MainStatus::BackingUp { .. }, MainStatus::BackingUp { .. }) => false,
(MainStatus::Error { .. }, MainStatus::Error { .. }) => false,
_ => true,
}
}
pub fn backing_up(self) -> Self {
MainStatus::BackingUp {
on_complete: if self.running() {

View File

@@ -14,6 +14,7 @@ import {
ServiceInterface,
ActionRequest,
RequestActionParams,
MainStatus,
} from "./osBindings"
import { StorePath } from "./util/PathBuilder"
import {
@@ -61,6 +62,11 @@ export type Effects = {
restart(): Promise<null>
/** stop this service's main function */
shutdown(): Promise<null>
/** ask the host os what the service's current status is */
getStatus(options: {
packageId?: PackageId
callback?: () => void
}): Promise<MainStatus>
/** indicate to the host os what runstate the service is in */
setMainStatus(options: SetMainStatus): Promise<null>

View File

@@ -0,0 +1,15 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ActionResultMember = {
name: string
description: string | null
} & (
| {
type: "single"
value: string
copyable: boolean
qr: boolean
masked: boolean
}
| { type: "group"; value: Array<ActionResultMember> }
)

View File

@@ -1,18 +1,8 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionResultValue } from "./ActionResultValue"
export type ActionResultV1 =
| {
type: "string"
name: string
value: string
description: string | null
copyable: boolean
qr: boolean
masked: boolean
}
| {
type: "object"
name: string
value: Array<ActionResultV1>
description?: string
}
export type ActionResultV1 = {
title: string
message: string | null
result: ActionResultValue | null
}

View File

@@ -0,0 +1,12 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ActionResultMember } from "./ActionResultMember"
export type ActionResultValue =
| {
type: "single"
value: string
copyable: boolean
qr: boolean
masked: boolean
}
| { type: "group"; value: Array<ActionResultMember> }

View File

@@ -0,0 +1,5 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { CallbackId } from "./CallbackId"
import type { PackageId } from "./PackageId"
export type GetStatusParams = { packageId?: PackageId; callback?: CallbackId }

View File

@@ -7,9 +7,11 @@ export { ActionRequestEntry } from "./ActionRequestEntry"
export { ActionRequestInput } from "./ActionRequestInput"
export { ActionRequestTrigger } from "./ActionRequestTrigger"
export { ActionRequest } from "./ActionRequest"
export { ActionResultMember } from "./ActionResultMember"
export { ActionResult } from "./ActionResult"
export { ActionResultV0 } from "./ActionResultV0"
export { ActionResultV1 } from "./ActionResultV1"
export { ActionResultValue } from "./ActionResultValue"
export { ActionSeverity } from "./ActionSeverity"
export { ActionVisibility } from "./ActionVisibility"
export { AddAdminParams } from "./AddAdminParams"
@@ -79,6 +81,7 @@ export { GetServiceInterfaceParams } from "./GetServiceInterfaceParams"
export { GetServicePortForwardParams } from "./GetServicePortForwardParams"
export { GetSslCertificateParams } from "./GetSslCertificateParams"
export { GetSslKeyParams } from "./GetSslKeyParams"
export { GetStatusParams } from "./GetStatusParams"
export { GetStoreParams } from "./GetStoreParams"
export { GetSystemSmtpParams } from "./GetSystemSmtpParams"
export { Governor } from "./Governor"

View File

@@ -7,6 +7,7 @@ import {
ClearCallbacksParams,
ClearServiceInterfacesParams,
GetActionInputParams,
GetStatusParams,
GetStoreParams,
RequestActionParams,
RunActionParams,
@@ -89,6 +90,7 @@ describe("startosTypeValidation ", () => {
mount: {} as MountParams,
checkDependencies: {} as CheckDependenciesParam,
getDependencies: undefined,
getStatus: {} as WithCallback<GetStatusParams>,
setMainStatus: {} as SetMainStatus,
})
})

View File

@@ -134,6 +134,7 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
getDataVersion: (effects, ...args) => effects.getDataVersion(...args),
shutdown: (effects, ...args) => effects.shutdown(...args),
getDependencies: (effects, ...args) => effects.getDependencies(...args),
getStatus: (effects, ...args) => effects.getStatus(...args),
}
return {
@@ -387,8 +388,8 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
algorithm?: T.Algorithm,
) => new GetSslCertificate(effects, hostnames, algorithm),
HealthCheck: {
of(o: HealthCheckParams) {
return healthCheck(o)
of(effects: T.Effects, o: Omit<HealthCheckParams, "effects">) {
return healthCheck({ effects, ...o })
},
},
healthCheck: {
@@ -636,12 +637,12 @@ export class StartSdk<Manifest extends T.SDKManifest, Store> {
) => InputSpec.of<Spec, Store>(spec),
},
Daemons: {
of(options: {
effects: Effects
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>
healthReceipts: HealthReceipt[]
}) {
return Daemons.of<Manifest>(options)
of(
effects: Effects,
started: (onTerm: () => PromiseLike<void>) => PromiseLike<null>,
healthReceipts: HealthReceipt[],
) {
return Daemons.of<Manifest>({ effects, started, healthReceipts })
},
},
List: {

View File

@@ -15,18 +15,14 @@ import { VersionGraph } from "../version/VersionGraph"
*/
export function setupManifest<
Id extends string,
Dependencies extends Record<string, unknown>,
VolumesTypes extends VolumeId,
AssetTypes extends VolumeId,
ImagesTypes extends ImageId,
Manifest extends {
dependencies: Dependencies
id: Id
assets: AssetTypes[]
images: Record<ImagesTypes, SDKImageInputSpec>
volumes: VolumesTypes[]
},
>(manifest: SDKManifest & Manifest): SDKManifest & Manifest {
} & SDKManifest,
>(manifest: Manifest): Manifest {
return manifest
}

View File

@@ -86,7 +86,7 @@ export class FileHelper<A> {
/**
* Accepts structured data and overwrites the existing file on disk.
*/
async write(data: A): Promise<null> {
private async writeFile(data: A): Promise<null> {
const parent = previousPath.exec(this.path)
if (parent) {
await fs.mkdir(parent[1], { recursive: true })
@@ -153,13 +153,27 @@ export class FileHelper<A> {
}
/**
* Accepts structured data and performs a merge with the existing file on disk.
* Accepts full structured data and performs a merge with the existing file on disk if it exists.
*/
async merge(data: A) {
const fileData = (await this.readOnce().catch(() => ({}))) || {}
async write(data: A) {
const fileData = (await this.readOnce()) || {}
const mergeData = merge({}, fileData, data)
return await this.write(mergeData)
return await this.writeFile(mergeData)
}
/**
* Accepts partial structured data and performs a merge with the existing file on disk.
*/
async merge(data: Partial<A>) {
const fileData =
(await this.readOnce()) ||
(() => {
throw new Error(`${this.path}: does not exist`)
})()
const mergeData = merge({}, fileData, data)
return await this.writeFile(mergeData)
}
/**
* Create a File Helper for an arbitrary file type.
*

484
web/package-lock.json generated
View File

@@ -21,20 +21,20 @@
"@angular/service-worker": "^14.2.2",
"@ionic/angular": "^6.1.15",
"@materia-ui/ngx-monaco-editor": "^6.0.0",
"@ng-web-apis/common": "^3.0.6",
"@ng-web-apis/mutation-observer": "^3.2.1",
"@ng-web-apis/resize-observer": "^3.2.1",
"@ng-web-apis/common": "^3.2.3",
"@ng-web-apis/mutation-observer": "^3.2.3",
"@ng-web-apis/resize-observer": "^3.2.3",
"@noble/curves": "^1.4.0",
"@noble/hashes": "^1.4.0",
"@start9labs/argon2": "^0.2.2",
"@start9labs/emver": "^0.1.5",
"@start9labs/start-sdk": "file:../sdk/baseDist",
"@taiga-ui/addon-charts": "3.86.0",
"@taiga-ui/cdk": "3.86.0",
"@taiga-ui/core": "3.86.0",
"@taiga-ui/experimental": "3.86.0",
"@taiga-ui/icons": "3.86.0",
"@taiga-ui/kit": "3.86.0",
"@taiga-ui/addon-charts": "3.96.0",
"@taiga-ui/cdk": "3.96.0",
"@taiga-ui/core": "3.96.0",
"@taiga-ui/experimental": "3.96.0",
"@taiga-ui/icons": "3.96.0",
"@taiga-ui/kit": "3.96.0",
"@tinkoff/ng-dompurify": "4.0.0",
"@tinkoff/ng-event-plugins": "3.2.0",
"angular-svg-round-progressbar": "^9.0.0",
@@ -3761,10 +3761,11 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
"integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
"devOptional": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.25",
@@ -3786,6 +3787,7 @@
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@maskito/angular/-/angular-1.9.0.tgz",
"integrity": "sha512-Wa/9nM9Nv0oieVZ6yxQNXfDRA4obFDR15xO16o1GKF8i9W1IdQQn+tuMRjkmx6HhJDN9+x3k8OTJ1f80BIrhjA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "2.6.2"
},
@@ -3800,17 +3802,20 @@
"node_modules/@maskito/angular/node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
"license": "0BSD"
},
"node_modules/@maskito/core": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@maskito/core/-/core-1.9.0.tgz",
"integrity": "sha512-WQIUrwkdIUg6PzAb4Apa0RjTPHB0EqZLc9/7kWCKVIixhkITRFXFg2BhiDVSRv0mIKVlAEJcOvvjHK5T3UaIig=="
"integrity": "sha512-WQIUrwkdIUg6PzAb4Apa0RjTPHB0EqZLc9/7kWCKVIixhkITRFXFg2BhiDVSRv0mIKVlAEJcOvvjHK5T3UaIig==",
"license": "Apache-2.0"
},
"node_modules/@maskito/kit": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/@maskito/kit/-/kit-1.9.0.tgz",
"integrity": "sha512-LNNgOJ0tAfrPoPehvoP+ZyYF9giOYL02sOMKyDC3IcqDNA8BAU0PARmS7TNsVEBpvSuJhU6xVt40nxJaONgUdw==",
"license": "Apache-2.0",
"peerDependencies": {
"@maskito/core": "^1.9.0"
}
@@ -3828,9 +3833,10 @@
}
},
"node_modules/@ng-web-apis/common": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@ng-web-apis/common/-/common-3.0.6.tgz",
"integrity": "sha512-ral+lzGpFS3aOCFB5DcHOI4lZhhp8GH4BnjSbngH2Xk8J0FKYdxRzvcPQVy7hS+TPUu0tW9uFVp6cC7odu3iyQ==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/@ng-web-apis/common/-/common-3.2.3.tgz",
"integrity": "sha512-1ts2FkLRw6dE/uTuYFMf9VTbLJ9CS8dpfIXTpxFsPArs13mEuz0Yvpe0rl0tMAhfNoeN4e7V8wVSyqDNgfzgmw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.2.0"
},
@@ -3841,9 +3847,10 @@
}
},
"node_modules/@ng-web-apis/intersection-observer": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-3.2.0.tgz",
"integrity": "sha512-EhwqEZJFKR9pz55TWp82qyWTXdg8TZeMP6bUw26bVHz8CTkgrpzaXdtxurMTvJ/+gwuFy4JSJLjBeV9nfZ/SXA==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/@ng-web-apis/intersection-observer/-/intersection-observer-3.2.3.tgz",
"integrity": "sha512-0yp+rr6ZEyF2vz4zYlMZ1GNtTHQziKajCurqAycZkSXVUdon7MhfiY/PiTr8xklTr40DjrF4anXV7oUAaA0szQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.2.0"
},
@@ -3853,9 +3860,10 @@
}
},
"node_modules/@ng-web-apis/mutation-observer": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-3.2.1.tgz",
"integrity": "sha512-a7krkMx0e9cfnutClwDylWjbTQVRHUP3oUik/nBvUdKlk/Q4anNww9aIKJ64VgiXR+1ZF8OmHGl0+XUzN6xP9Q==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-3.2.3.tgz",
"integrity": "sha512-iFwxut1cw94lTXnloDMBRanqNTvEjbnigWkTowlPH3QY16ysTKuC51JeonRuARW4K3bbDGbXiLUYYZWKQaDfKw==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.2.0"
},
@@ -3865,9 +3873,10 @@
}
},
"node_modules/@ng-web-apis/resize-observer": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/@ng-web-apis/resize-observer/-/resize-observer-3.2.1.tgz",
"integrity": "sha512-r1YaZUo6DIDeR+4/C/pM4Ar0eTQBxjK0FUhYYJ512EnN8RqAn8d3a0wM8ZYunucwDICwW1sx4IIV6PZ2G77xsg==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/@ng-web-apis/resize-observer/-/resize-observer-3.2.3.tgz",
"integrity": "sha512-x3KxBZSragzdQlkbY9tiHY0lVlkOFD7Y34rzXdBJ7PTnIvlq43X/tN31Mmb3R0Np4vsarWqnhO6Y42ljudsWdA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.2.0"
},
@@ -4161,60 +4170,63 @@
}
},
"node_modules/@taiga-ui/addon-charts": {
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.86.0.tgz",
"integrity": "sha512-Du/85qqaj8hpFSI6hPuFeIhtE93Z6WSkYZLt0gvnsaCb2qSAg8D4oHSogrtF1rsWGGoM+fvXjD7UEUw9GzFIPg==",
"version": "3.96.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-charts/-/addon-charts-3.96.0.tgz",
"integrity": "sha512-vU8fZhwdg+sWOPJNnIrEtuuVkoFjQKeMhvk4f68oHBXUa0XUESS+qNeAfstvow6xnAZJrhxYtMoDuH/cgC7kNg==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
"tslib": ">=2.7.0"
},
"peerDependencies": {
"@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0",
"@ng-web-apis/common": "^3.0.6",
"@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/core": "^3.86.0",
"@tinkoff/ng-polymorpheus": "^4.3.0"
"@ng-web-apis/common": ">=3.2.3 <4",
"@taiga-ui/cdk": ">=3.96.0 <4",
"@taiga-ui/core": ">=3.96.0 <4",
"@tinkoff/ng-polymorpheus": ">=4.3.0"
}
},
"node_modules/@taiga-ui/addon-commerce": {
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.86.0.tgz",
"integrity": "sha512-8QSB490ckI4jnU+1sQ3x8os2GVE162hbvzPVYIZ0TruoeXl076dAz6PT2WRaFwjcaCAIGsuaQgQ4Cv02NjkiYQ==",
"version": "3.96.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/addon-commerce/-/addon-commerce-3.96.0.tgz",
"integrity": "sha512-Y1MACB6KrQVnNjgeKQrNfbz51jMXbU7j83sL28+6q8DS9LNv4DVcv/r/UvK2VZ1PhESkBU85NJAIHf5iy9GN4g==",
"license": "Apache-2.0",
"peer": true,
"dependencies": {
"tslib": "^2.6.2"
"tslib": ">=2.7.0"
},
"peerDependencies": {
"@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0",
"@angular/forms": ">=12.0.0",
"@maskito/angular": "^1.9.0",
"@maskito/core": "^1.9.0",
"@maskito/kit": "^1.9.0",
"@ng-web-apis/common": "^3.0.6",
"@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/core": "^3.86.0",
"@taiga-ui/i18n": "^3.86.0",
"@taiga-ui/kit": "^3.86.0",
"@tinkoff/ng-polymorpheus": "^4.3.0",
"@maskito/angular": ">=1.9.0 <2",
"@maskito/core": ">=1.9.0 <2",
"@maskito/kit": ">=1.9.0 <2",
"@ng-web-apis/common": ">=3.2.3 <4",
"@taiga-ui/cdk": ">=3.96.0 <4",
"@taiga-ui/core": ">=3.96.0 <4",
"@taiga-ui/i18n": ">=3.96.0 <4",
"@taiga-ui/kit": ">=3.96.0 <4",
"@tinkoff/ng-polymorpheus": ">=4.3.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/cdk": {
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.86.0.tgz",
"integrity": "sha512-aVbnW01Oh0Er1sHKVGHP8W05mOSKxjSzFE3Qx4iF4T6KW7Rlz9HZoNx5ADMg0TATYChtWh9Kwjo8I4LSVj2ZUw==",
"version": "3.96.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/cdk/-/cdk-3.96.0.tgz",
"integrity": "sha512-y1T4+Olhys370ePT8SvZpMlOConDG6bLxjo6jOvFI6D6w0nEgqRxxFDBcdoHxgMWJZdAg7lRLEtN9dHEwKaABA==",
"license": "Apache-2.0",
"dependencies": {
"@ng-web-apis/common": "3.0.6",
"@ng-web-apis/mutation-observer": "3.1.0",
"@ng-web-apis/resize-observer": "3.0.6",
"@tinkoff/ng-event-plugins": "3.2.0",
"@tinkoff/ng-polymorpheus": "4.3.0",
"tslib": "2.6.2"
"@ng-web-apis/common": "^3.2.3",
"@ng-web-apis/mutation-observer": "^3.2.3",
"@ng-web-apis/resize-observer": "^3.2.3",
"@tinkoff/ng-event-plugins": "^3.2.0",
"@tinkoff/ng-polymorpheus": "^4.3.0",
"tslib": "^2.7.0"
},
"optionalDependencies": {
"ng-morph": "4.0.5",
"parse5": "6.0.1"
"ng-morph": "^4.8.2",
"parse5": "^6.0.1"
},
"peerDependencies": {
"@angular/animations": ">=12.0.0",
@@ -4224,42 +4236,166 @@
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/cdk/node_modules/@ng-web-apis/mutation-observer": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@ng-web-apis/mutation-observer/-/mutation-observer-3.1.0.tgz",
"integrity": "sha512-MFN0TLLBMFJJPpXkGFe9ChRCSOKvMHZRRtBq5jHWS7tv5/CtdUkqW5CU7RC9KTzZjGeMzYe0cXO4JRkjL5aZ9g==",
"node_modules/@taiga-ui/cdk/node_modules/@angular-devkit/core": {
"version": "18.2.9",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-18.2.9.tgz",
"integrity": "sha512-bsVt//5E0ua7FZfO0dCF/qGGY6KQD34/bNGyRu5B6HedimpdU2/0PGDptksU5v3yKEc9gNw0xC6mT0UsY/R9pA==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"tslib": "^2.2.0"
"ajv": "8.17.1",
"ajv-formats": "3.0.1",
"jsonc-parser": "3.3.1",
"picomatch": "4.0.2",
"rxjs": "7.8.1",
"source-map": "0.7.4"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
},
"peerDependencies": {
"@angular/core": ">=12.0.0",
"@ng-web-apis/common": ">=2.0.0"
"chokidar": "^3.5.2"
},
"peerDependenciesMeta": {
"chokidar": {
"optional": true
}
}
},
"node_modules/@taiga-ui/cdk/node_modules/@ng-web-apis/resize-observer": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/@ng-web-apis/resize-observer/-/resize-observer-3.0.6.tgz",
"integrity": "sha512-QdGYdEdC0AzFonLfNOnyYyeCwnvK9jlskoeefvJN3Yyvds3ivBrrTjpeDOdiLsQpCPBp9/673imgq7355vkQow==",
"node_modules/@taiga-ui/cdk/node_modules/@angular-devkit/schematics": {
"version": "18.2.9",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-18.2.9.tgz",
"integrity": "sha512-aIY5/IomDOINGCtFYi77uo0acDpdQNNCighfBBUGEBNMQ1eE3oGNGpLAH/qWeuxJndgmxrdKsvws9DdT46kLig==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"tslib": "^2.2.0"
"@angular-devkit/core": "18.2.9",
"jsonc-parser": "3.3.1",
"magic-string": "0.30.11",
"ora": "5.4.1",
"rxjs": "7.8.1"
},
"engines": {
"node": "^18.19.1 || ^20.11.1 || >=22.0.0",
"npm": "^6.11.0 || ^7.5.6 || >=8.0.0",
"yarn": ">= 1.13.0"
}
},
"node_modules/@taiga-ui/cdk/node_modules/ajv": {
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz",
"integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"fast-deep-equal": "^3.1.3",
"fast-uri": "^3.0.1",
"json-schema-traverse": "^1.0.0",
"require-from-string": "^2.0.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
}
},
"node_modules/@taiga-ui/cdk/node_modules/ajv-formats": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz",
"integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"ajv": "^8.0.0"
},
"peerDependencies": {
"@angular/core": ">=12.0.0",
"@ng-web-apis/common": ">=2.0.0"
"ajv": "^8.0.0"
},
"peerDependenciesMeta": {
"ajv": {
"optional": true
}
}
},
"node_modules/@taiga-ui/cdk/node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
"node_modules/@taiga-ui/cdk/node_modules/jsonc-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz",
"integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==",
"license": "MIT",
"optional": true
},
"node_modules/@taiga-ui/cdk/node_modules/magic-string": {
"version": "0.30.11",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz",
"integrity": "sha512-+Wri9p0QHMy+545hKww7YAu5NyzF8iomPL/RQazugQ9+Ez4Ic3mERMd8ZTX5rfK944j+560ZJi8iAwgak1Ac7A==",
"license": "MIT",
"optional": true,
"peer": true,
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0"
}
},
"node_modules/@taiga-ui/cdk/node_modules/minimatch": {
"version": "10.0.1",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz",
"integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==",
"license": "ISC",
"optional": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": "20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@taiga-ui/cdk/node_modules/ng-morph": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/ng-morph/-/ng-morph-4.8.4.tgz",
"integrity": "sha512-XwL53wCOhyaAxvoekN74ONbWUK30huzp+GpZYyC01RfaG2AX9l7YlC1mGG/l7Rx7YXtFAk85VFnNJqn2e46K8g==",
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"jsonc-parser": "3.3.1",
"minimatch": "10.0.1",
"multimatch": "5.0.0",
"ts-morph": "23.0.0"
},
"peerDependencies": {
"@angular-devkit/core": ">=16.0.0",
"@angular-devkit/schematics": ">=16.0.0",
"tslib": "^2.7.0"
}
},
"node_modules/@taiga-ui/cdk/node_modules/picomatch": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
"integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
"license": "MIT",
"optional": true,
"peer": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/@taiga-ui/core": {
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.86.0.tgz",
"integrity": "sha512-diQKOnPtDDfxPOMk6wLRq8nyDVfNSPSNy+1TeyqzUgOvJ6XAjfaBXGsL3iuR7AN8+sz/b3rJmBce+vdw6FjMLQ==",
"version": "3.96.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/core/-/core-3.96.0.tgz",
"integrity": "sha512-0w2Jpesb2oM+5aMTfSKFzzYdfPDHXq0fhE6TN4Eutc9LO4Lyh6Hg0KPfoHmw8Tj6wg4KxBcdeAj/opNehWwrVw==",
"license": "Apache-2.0",
"dependencies": {
"@taiga-ui/i18n": "^3.86.0",
"tslib": "^2.6.2"
"@taiga-ui/i18n": "^3.96.0",
"tslib": ">=2.7.0"
},
"peerDependencies": {
"@angular/animations": ">=12.0.0",
@@ -4268,81 +4404,85 @@
"@angular/forms": ">=12.0.0",
"@angular/platform-browser": ">=12.0.0",
"@angular/router": ">=12.0.0",
"@ng-web-apis/common": "^3.0.6",
"@ng-web-apis/mutation-observer": "^3.1.0",
"@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/i18n": "^3.86.0",
"@tinkoff/ng-event-plugins": "^3.2.0",
"@tinkoff/ng-polymorpheus": "^4.3.0",
"@ng-web-apis/common": ">=3.2.3 <4",
"@ng-web-apis/mutation-observer": ">=3.2.3 <4",
"@taiga-ui/cdk": ">=3.96.0 <4",
"@taiga-ui/i18n": ">=3.96.0 <4",
"@tinkoff/ng-event-plugins": ">=3.2.0 <4",
"@tinkoff/ng-polymorpheus": ">=4.3.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/experimental": {
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.86.0.tgz",
"integrity": "sha512-ACjoRVeX5MgsNJsiu2ukliXLD2mfEWm8Vtmk78vqcnkyPUmy1ZWK4sG3p5ybFN8AdIMHkblVq0l+x2qAwr/+LQ==",
"version": "3.96.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/experimental/-/experimental-3.96.0.tgz",
"integrity": "sha512-AxEaYieouK3NzkGzOVTojnHBAd/eRw5D2sVAK+SQdBFdBjQiRGk/FKzZhOWpDOQOcIN+lhpDGpth3KV30+AgFQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
"tslib": ">=2.7.0"
},
"peerDependencies": {
"@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0",
"@taiga-ui/addon-commerce": "^3.86.0",
"@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/core": "^3.86.0",
"@taiga-ui/kit": "^3.86.0",
"@tinkoff/ng-polymorpheus": "^4.3.0",
"@taiga-ui/addon-commerce": ">=3.96.0 <4",
"@taiga-ui/cdk": ">=3.96.0 <4",
"@taiga-ui/core": ">=3.96.0 <4",
"@taiga-ui/kit": ">=3.96.0 <4",
"@tinkoff/ng-polymorpheus": ">=4.3.0",
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/i18n": {
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.86.0.tgz",
"integrity": "sha512-8zkNhMo/QtxZ2Zp6EP/nxo4SOLwaIrX+P3X/Wt+1cjFNZUYWWfdvfHLLdNviKFPVl4RAOxvkhDfza/wkrwv+iQ==",
"version": "3.96.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/i18n/-/i18n-3.96.0.tgz",
"integrity": "sha512-SGO89mRhmdD3BldQJiSqDCftEjjaOKXLJFFEZWBtrhelxKX5hWJ2lL0O+WzVAMegbQqHoT60HoHrWZIlSy9tVQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
"tslib": ">=2.7.0"
},
"peerDependencies": {
"@angular/core": ">=12.0.0",
"@ng-web-apis/common": "^3.0.6",
"@ng-web-apis/common": ">=3.2.3 <4",
"rxjs": ">=6.0.0"
}
},
"node_modules/@taiga-ui/icons": {
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.86.0.tgz",
"integrity": "sha512-jVBEbvE/r9JG+knmXMTn/l/js3JjYi8nSGbrLCryJZZoS2izRnQARN2txABieUJm8H463CoF0rcdXlHKRuA4Ew==",
"version": "3.96.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/icons/-/icons-3.96.0.tgz",
"integrity": "sha512-bBuX8RGAqr2+9TRbTA4RAmq3v4pnE9ObDwoq/5CrUtE3Wr7idcl7hgfW3yeyhpdOdJJMGfbTauPBONLz2uFRCQ==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.6.2"
"tslib": ">=2.7.0"
},
"peerDependencies": {
"@taiga-ui/cdk": "^3.86.0"
"@taiga-ui/cdk": ">=3.96.0 <4"
}
},
"node_modules/@taiga-ui/kit": {
"version": "3.86.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.86.0.tgz",
"integrity": "sha512-naAy4pyhCaQ9+vWxqSMjbV+9KwnMxT5ybrw+MAJgMn2evzRq0FjqzyFZFog7oiRbRvgVdoWPQfBNKaaLhJcpsw==",
"version": "3.96.0",
"resolved": "https://registry.npmjs.org/@taiga-ui/kit/-/kit-3.96.0.tgz",
"integrity": "sha512-gHn0AZU1kiNZU2T/LnnxLGkHC3/XNroBWCGmhxupAmL/JcsmOXsl3L8aK1rZhjS4QgkmgaC5ueVWNwdXINRnXw==",
"license": "Apache-2.0",
"dependencies": {
"@maskito/angular": "1.9.0",
"@maskito/core": "1.9.0",
"@maskito/kit": "1.9.0",
"@ng-web-apis/intersection-observer": "3.2.0",
"text-mask-core": "5.1.2",
"tslib": "^2.6.2"
"@maskito/angular": "^1.9.0",
"@maskito/core": "^1.9.0",
"@maskito/kit": "^1.9.0",
"@ng-web-apis/intersection-observer": "^3.2.3",
"text-mask-core": "^5.1.2",
"tslib": ">=2.7.0"
},
"peerDependencies": {
"@angular/common": ">=12.0.0",
"@angular/core": ">=12.0.0",
"@angular/forms": ">=12.0.0",
"@angular/router": ">=12.0.0",
"@ng-web-apis/common": "3.0.6",
"@ng-web-apis/mutation-observer": "^3.1.0",
"@ng-web-apis/resize-observer": "^3.0.6",
"@taiga-ui/cdk": "^3.86.0",
"@taiga-ui/core": "^3.86.0",
"@taiga-ui/i18n": "^3.86.0",
"@tinkoff/ng-polymorpheus": "^4.3.0",
"@ng-web-apis/common": ">=3.2.3 <4",
"@ng-web-apis/mutation-observer": ">=3.2.3 <4",
"@ng-web-apis/resize-observer": ">=3.2.3 <4",
"@taiga-ui/cdk": ">=3.96.0 <4",
"@taiga-ui/core": ">=3.96.0 <4",
"@taiga-ui/i18n": ">=3.96.0 <4",
"@tinkoff/ng-polymorpheus": ">=4.3.0",
"rxjs": ">=6.0.0"
}
},
@@ -4400,13 +4540,14 @@
}
},
"node_modules/@ts-morph/common": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.22.0.tgz",
"integrity": "sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==",
"version": "0.24.0",
"resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.24.0.tgz",
"integrity": "sha512-c1xMmNHWpNselmpIqursHeOHHBTIsJLbB+NuovbTTRCNiTLEr/U9dbJ8qy0jd/O2x5pc3seWuOUN5R2IoOTp8A==",
"license": "MIT",
"optional": true,
"dependencies": {
"fast-glob": "^3.3.2",
"minimatch": "^9.0.3",
"minimatch": "^9.0.4",
"mkdirp": "^3.0.1",
"path-browserify": "^1.0.1"
}
@@ -4415,6 +4556,7 @@
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"license": "ISC",
"optional": true,
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -4430,6 +4572,7 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz",
"integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==",
"license": "MIT",
"optional": true,
"bin": {
"mkdirp": "dist/cjs/src/bin.js"
@@ -4613,6 +4756,7 @@
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
"integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
"license": "MIT",
"optional": true
},
"node_modules/@types/mustache": {
@@ -5237,6 +5381,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz",
"integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
@@ -5252,6 +5397,7 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
@@ -5261,6 +5407,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz",
"integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
@@ -6088,9 +6235,10 @@
}
},
"node_modules/code-block-writer": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-12.0.0.tgz",
"integrity": "sha512-q4dMFMlXtKR3XNBHyMHt/3pwYNA69EDk00lloMOaaUMKPUXBw6lpXtbu3MMVG6/uOihGnRDOlkyqsONEUj60+w==",
"version": "13.0.3",
"resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz",
"integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==",
"license": "MIT",
"optional": true
},
"node_modules/color-convert": {
@@ -7868,6 +8016,14 @@
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"dev": true
},
"node_modules/fast-uri": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz",
"integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==",
"license": "BSD-3-Clause",
"optional": true,
"peer": true
},
"node_modules/fastq": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
@@ -10651,6 +10807,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/multimatch/-/multimatch-5.0.0.tgz",
"integrity": "sha512-ypMKuglUrZUD99Tk2bUQ+xNQj43lPEfAeX2o9cTteAmShXy2VHDJpuwu1o0xqoKCt9jLVAvwyFKdLTPXKAfJyA==",
"license": "MIT",
"optional": true,
"dependencies": {
"@types/minimatch": "^3.0.3",
@@ -10670,6 +10827,7 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"license": "MIT",
"optional": true,
"dependencies": {
"balanced-match": "^1.0.0",
@@ -10680,6 +10838,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"license": "ISC",
"optional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
@@ -10781,50 +10940,6 @@
"node": ">= 0.4.0"
}
},
"node_modules/ng-morph": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/ng-morph/-/ng-morph-4.0.5.tgz",
"integrity": "sha512-5tnlb5WrGKeo2E7VRcV7ZHhScyNgliYqpbXqt103kynmfj6Ic8kzhJAhHu9iLkF1yRnKv2kyCE+O7UGZx5RraQ==",
"optional": true,
"dependencies": {
"jsonc-parser": "3.2.0",
"minimatch": "9.0.3",
"multimatch": "5.0.0",
"ts-morph": "21.0.1",
"tslib": "2.6.2"
},
"peerDependencies": {
"@angular-devkit/core": ">=11.0.0",
"@angular-devkit/schematics": ">=11.0.0"
}
},
"node_modules/ng-morph/node_modules/jsonc-parser": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
"integrity": "sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==",
"optional": true
},
"node_modules/ng-morph/node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"optional": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/ng-morph/node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
"optional": true
},
"node_modules/ng-packagr": {
"version": "14.2.2",
"resolved": "https://registry.npmjs.org/ng-packagr/-/ng-packagr-14.2.2.tgz",
@@ -14794,7 +14909,8 @@
"node_modules/text-mask-core": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/text-mask-core/-/text-mask-core-5.1.2.tgz",
"integrity": "sha512-VfkCMdmRRZqXgQZFlDMiavm3hzsMzBM23CxHZsaeAYg66ZhXCNJWrFmnJwNy8KF9f74YvAUAuQenxsMCfuvhUw=="
"integrity": "sha512-VfkCMdmRRZqXgQZFlDMiavm3hzsMzBM23CxHZsaeAYg66ZhXCNJWrFmnJwNy8KF9f74YvAUAuQenxsMCfuvhUw==",
"license": "Unlicense"
},
"node_modules/text-table": {
"version": "0.2.0",
@@ -14880,13 +14996,14 @@
"integrity": "sha512-UFYaKgfqlg9FROK7bdpYqFwG1CJvP4kOJdjXuWoqxo9jCmANoDw1GxkSCpJgoTeIiSTaTH5Qr1klSspb8c+ydg=="
},
"node_modules/ts-morph": {
"version": "21.0.1",
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-21.0.1.tgz",
"integrity": "sha512-dbDtVdEAncKctzrVZ+Nr7kHpHkv+0JDJb2MjjpBaj8bFeCkePU9rHfMklmhuLFnpeq/EJZk2IhStY6NzqgjOkg==",
"version": "23.0.0",
"resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-23.0.0.tgz",
"integrity": "sha512-FcvFx7a9E8TUe6T3ShihXJLiJOiqyafzFKUO4aqIHDUCIvADdGNShcbc2W5PMr3LerXRv7mafvFZ9lRENxJmug==",
"license": "MIT",
"optional": true,
"dependencies": {
"@ts-morph/common": "~0.22.0",
"code-block-writer": "^12.0.0"
"@ts-morph/common": "~0.24.0",
"code-block-writer": "^13.0.1"
}
},
"node_modules/ts-node": {
@@ -14933,9 +15050,10 @@
}
},
"node_modules/tslib": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",
"integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ=="
"version": "2.8.0",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.0.tgz",
"integrity": "sha512-jWVzBLplnCmoaTr13V9dYbiQ99wvZRd0vNWaDRg+aVYRcjDF3nDksxFDE/+fkXnKhpnUUkmx5pK/v8mCtLVqZA==",
"license": "0BSD"
},
"node_modules/tslint": {
"version": "6.1.3",

View File

@@ -43,20 +43,20 @@
"@angular/service-worker": "^14.2.2",
"@ionic/angular": "^6.1.15",
"@materia-ui/ngx-monaco-editor": "^6.0.0",
"@ng-web-apis/common": "^3.0.6",
"@ng-web-apis/mutation-observer": "^3.2.1",
"@ng-web-apis/resize-observer": "^3.2.1",
"@ng-web-apis/common": "^3.2.3",
"@ng-web-apis/mutation-observer": "^3.2.3",
"@ng-web-apis/resize-observer": "^3.2.3",
"@noble/curves": "^1.4.0",
"@noble/hashes": "^1.4.0",
"@start9labs/argon2": "^0.2.2",
"@start9labs/emver": "^0.1.5",
"@start9labs/start-sdk": "file:../sdk/baseDist",
"@taiga-ui/addon-charts": "3.86.0",
"@taiga-ui/cdk": "3.86.0",
"@taiga-ui/core": "3.86.0",
"@taiga-ui/experimental": "3.86.0",
"@taiga-ui/icons": "3.86.0",
"@taiga-ui/kit": "3.86.0",
"@taiga-ui/addon-charts": "3.96.0",
"@taiga-ui/cdk": "3.96.0",
"@taiga-ui/core": "3.96.0",
"@taiga-ui/experimental": "3.96.0",
"@taiga-ui/icons": "3.96.0",
"@taiga-ui/kit": "3.96.0",
"@tinkoff/ng-dompurify": "4.0.0",
"@tinkoff/ng-event-plugins": "3.2.0",
"angular-svg-round-progressbar": "^9.0.0",

View File

@@ -1,23 +1,23 @@
import { CommonModule } from '@angular/common'
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { T } from '@start9labs/start-sdk'
import { TuiFadeModule, TuiTitleModule } from '@taiga-ui/experimental'
import { TuiAccordionModule } from '@taiga-ui/kit'
import { ActionSuccessItemComponent } from './action-success-item.component'
import { ActionSuccessMemberComponent } from './action-success-member.component'
import { GroupResult } from './types'
@Component({
standalone: true,
selector: 'app-action-success-group',
template: `
<p *ngFor="let item of value?.value">
<app-action-success-item
*ngIf="isSingle(item)"
[value]="item"
></app-action-success-item>
<tui-accordion-item *ngIf="!isSingle(item)" size="s">
<div tuiFade>{{ item.name }}</div>
<p *ngFor="let member of group.value">
<app-action-success-member
*ngIf="member.type === 'single'"
[member]="member"
></app-action-success-member>
<tui-accordion-item *ngIf="member.type === 'group'">
<div tuiFade>{{ member.name }}</div>
<ng-template tuiAccordionItemContent>
<app-action-success-group [value]="item"></app-action-success-group>
<app-action-success-group [group]="member"></app-action-success-group>
</ng-template>
</tui-accordion-item>
</p>
@@ -37,18 +37,12 @@ import { ActionSuccessItemComponent } from './action-success-item.component'
imports: [
CommonModule,
TuiTitleModule,
ActionSuccessItemComponent,
ActionSuccessMemberComponent,
TuiAccordionModule,
TuiFadeModule,
],
})
export class ActionSuccessGroupComponent {
@Input()
value?: T.ActionResultV1 & { type: 'object' }
isSingle(
value: T.ActionResultV1,
): value is T.ActionResultV1 & { type: 'string' } {
return value.type === 'string'
}
group!: GroupResult
}

View File

@@ -9,41 +9,38 @@ import {
ViewChild,
} from '@angular/core'
import { FormsModule } from '@angular/forms'
import { T } from '@start9labs/start-sdk'
import {
TuiDialogService,
TuiLabelModule,
TuiTextfieldComponent,
TuiTextfieldControllerModule,
} from '@taiga-ui/core'
import { TuiButtonModule } from '@taiga-ui/experimental'
import { TuiButtonModule, TuiTitleModule } from '@taiga-ui/experimental'
import { TuiInputModule } from '@taiga-ui/kit'
import { QrCodeModule } from 'ng-qrcode'
import { ActionSuccessGroupComponent } from './action-success-group.component'
import { T } from '@start9labs/start-sdk'
@Component({
standalone: true,
selector: 'app-action-success-item',
selector: 'app-action-success-member',
template: `
<p *ngIf="!parent" class="qr">
<ng-container *ngTemplateOutlet="qr"></ng-container>
</p>
<label [tuiLabel]="value.description">
<tui-input
[readOnly]="true"
[ngModel]="value.value"
[tuiTextfieldCustomContent]="actions"
>
<input
tuiTextfield
[style.border-inline-end-width.rem]="border"
[type]="value.masked && masked ? 'password' : 'text'"
/>
</tui-input>
<tui-input
[readOnly]="true"
[ngModel]="member.value"
[tuiTextfieldCustomContent]="actions"
>
{{ member.name }}
<input
tuiTextfield
[style.border-inline-end-width.rem]="border"
[type]="member.masked && masked ? 'password' : 'text'"
/>
</tui-input>
<label *ngIf="member.description" [style.padding-top.rem]="0.25" tuiTitle>
<span tuiSubtitle [style.opacity]="0.8">{{ member.description }}</span>
</label>
<ng-template #actions>
<button
*ngIf="value.masked"
*ngIf="member.masked"
tuiIconButton
appearance="icon"
size="s"
@@ -56,7 +53,7 @@ import { ActionSuccessGroupComponent } from './action-success-group.component'
Reveal/Hide
</button>
<button
*ngIf="value.copyable"
*ngIf="member.copyable"
tuiIconButton
appearance="icon"
size="s"
@@ -69,7 +66,7 @@ import { ActionSuccessGroupComponent } from './action-success-group.component'
Copy
</button>
<button
*ngIf="value.qr && parent"
*ngIf="member.qr"
tuiIconButton
appearance="icon"
size="s"
@@ -84,12 +81,12 @@ import { ActionSuccessGroupComponent } from './action-success-group.component'
</ng-template>
<ng-template #qr>
<qr-code
[value]="value.value"
[style.filter]="value.masked && masked ? 'blur(0.5rem)' : null"
[value]="member.value"
[style.filter]="member.masked && masked ? 'blur(0.5rem)' : null"
size="350"
></qr-code>
<button
*ngIf="value.masked && masked"
*ngIf="member.masked && masked"
tuiIconButton
class="reveal"
iconLeft="tuiIconEye"
@@ -122,37 +119,25 @@ import { ActionSuccessGroupComponent } from './action-success-group.component'
TuiTextfieldControllerModule,
TuiButtonModule,
QrCodeModule,
TuiLabelModule,
TuiTitleModule,
],
})
export class ActionSuccessItemComponent {
export class ActionSuccessMemberComponent {
@ViewChild(TuiTextfieldComponent, { read: ElementRef })
private readonly input!: ElementRef<HTMLInputElement>
private readonly dialogs = inject(TuiDialogService)
readonly parent = inject(ActionSuccessGroupComponent, {
optional: true,
})
@Input()
value!: T.ActionResultV1 & { type: 'string' }
member!: T.ActionResultMember & { type: 'single' }
masked = true
get border(): number {
let border = 0
if (this.value.masked) {
border += 2
}
if (this.value.copyable) {
border += 2
}
if (this.value.qr && this.parent) {
border += 2
}
if (this.member.masked) border += 2
if (this.member.copyable) border += 2
if (this.member.qr) border += 2
return border
}
@@ -160,7 +145,7 @@ export class ActionSuccessItemComponent {
show(template: TemplateRef<any>) {
const masked = this.masked
this.masked = this.value.masked
this.masked = this.member.masked
this.dialogs
.open(template, { label: 'Scan this QR', size: 's' })
.subscribe({
@@ -179,6 +164,6 @@ export class ActionSuccessItemComponent {
el.focus()
el.select()
el.ownerDocument.execCommand('copy')
el.type = this.masked && this.value.masked ? 'password' : 'text'
el.type = this.masked && this.member.masked ? 'password' : 'text'
}
}

View File

@@ -0,0 +1,145 @@
import { CommonModule } from '@angular/common'
import {
ChangeDetectionStrategy,
Component,
ElementRef,
inject,
Input,
TemplateRef,
ViewChild,
} from '@angular/core'
import { FormsModule } from '@angular/forms'
import {
TuiDialogService,
TuiLabelModule,
TuiTextfieldComponent,
TuiTextfieldControllerModule,
} from '@taiga-ui/core'
import { TuiButtonModule } from '@taiga-ui/experimental'
import { TuiInputModule } from '@taiga-ui/kit'
import { QrCodeModule } from 'ng-qrcode'
import { SingleResult } from './types'
@Component({
standalone: true,
selector: 'app-action-success-single',
template: `
<p class="qr">
<ng-container *ngTemplateOutlet="qr"></ng-container>
</p>
<tui-input
[readOnly]="true"
[ngModel]="single.value"
[tuiTextfieldLabelOutside]="true"
[tuiTextfieldCustomContent]="actions"
>
<input
tuiTextfield
[style.border-inline-end-width.rem]="border"
[type]="single.masked && masked ? 'password' : 'text'"
/>
</tui-input>
<ng-template #actions>
<button
*ngIf="single.masked"
tuiIconButton
appearance="icon"
size="s"
type="button"
tabindex="-1"
[iconLeft]="masked ? 'tuiIconEye' : 'tuiIconEyeOff'"
[style.pointer-events]="'auto'"
(click)="masked = !masked"
>
Reveal/Hide
</button>
<button
*ngIf="single.copyable"
tuiIconButton
appearance="icon"
size="s"
type="button"
tabindex="-1"
iconLeft="tuiIconCopy"
[style.pointer-events]="'auto'"
(click)="copy()"
>
Copy
</button>
</ng-template>
<ng-template #qr>
<qr-code
[value]="single.value"
[style.filter]="single.masked && masked ? 'blur(0.5rem)' : null"
size="350"
></qr-code>
<button
*ngIf="single.masked && masked"
tuiIconButton
class="reveal"
iconLeft="tuiIconEye"
[style.border-radius.%]="100"
(click)="masked = false"
>
Reveal
</button>
</ng-template>
`,
changeDetection: ChangeDetectionStrategy.OnPush,
styles: [
`
@import '@taiga-ui/core/styles/taiga-ui-local';
.reveal {
@include center-all();
}
.qr {
position: relative;
text-align: center;
}
`,
],
imports: [
CommonModule,
FormsModule,
TuiInputModule,
TuiTextfieldControllerModule,
TuiButtonModule,
QrCodeModule,
TuiLabelModule,
],
})
export class ActionSuccessSingleComponent {
@ViewChild(TuiTextfieldComponent, { read: ElementRef })
private readonly input!: ElementRef<HTMLInputElement>
private readonly dialogs = inject(TuiDialogService)
@Input()
single!: SingleResult
masked = true
get border(): number {
let border = 0
if (this.single.masked) border += 2
if (this.single.copyable) border += 2
return border
}
copy() {
const el = this.input.nativeElement
if (!el) {
return
}
el.type = 'text'
el.focus()
el.select()
el.ownerDocument.execCommand('copy')
el.type = this.masked && this.single.masked ? 'password' : 'text'
}
}

View File

@@ -1,36 +1,36 @@
import { CommonModule } from '@angular/common'
import { Component, inject } from '@angular/core'
import { TuiDialogContext, TuiTextfieldControllerModule } from '@taiga-ui/core'
import { TuiDialogContext } from '@taiga-ui/core'
import { POLYMORPHEUS_CONTEXT } from '@tinkoff/ng-polymorpheus'
import { RR } from 'src/app/services/api/api.types'
import { ActionSuccessGroupComponent } from './action-success-group.component'
import { ActionSuccessItemComponent } from './action-success-item.component'
import { ActionSuccessSingleComponent } from './action-success-single.component'
import { ActionResponseWithResult } from './types'
@Component({
standalone: true,
template: `
<ng-container tuiTextfieldSize="m" [tuiTextfieldLabelOutside]="true">
<app-action-success-item
*ngIf="item"
[value]="item"
></app-action-success-item>
<app-action-success-group
*ngIf="group"
[value]="group"
></app-action-success-group>
</ng-container>
<p *ngIf="data.message">{{ data.message }}</p>
<app-action-success-single
*ngIf="single"
[single]="single"
></app-action-success-single>
<app-action-success-group
*ngIf="group"
[group]="group"
></app-action-success-group>
`,
imports: [
CommonModule,
ActionSuccessGroupComponent,
ActionSuccessItemComponent,
TuiTextfieldControllerModule,
ActionSuccessSingleComponent,
],
})
export class ActionSuccessPage {
readonly data =
inject<TuiDialogContext<void, RR.ActionRes>>(POLYMORPHEUS_CONTEXT).data
inject<TuiDialogContext<void, ActionResponseWithResult>>(
POLYMORPHEUS_CONTEXT,
).data
readonly item = this.data?.type === 'string' ? this.data : null
readonly group = this.data?.type === 'object' ? this.data : null
readonly single = this.data.result.type === 'single' ? this.data.result : null
readonly group = this.data.result.type === 'group' ? this.data.result : null
}

View File

@@ -0,0 +1,7 @@
import { RR } from 'src/app/services/api/api.types'
type ActionResponse = NonNullable<RR.ActionRes>
type ActionResult = NonNullable<ActionResponse['result']>
export type ActionResponseWithResult = ActionResponse & { result: ActionResult }
export type SingleResult = ActionResult & { type: 'single' }
export type GroupResult = ActionResult & { type: 'group' }

View File

@@ -118,13 +118,17 @@ export class ActionService {
input: input || null,
})
if (res) {
if (!res) return true
if (res.result) {
this.dialogs
.open(new PolymorpheusComponent(ActionSuccessPage), {
label: res.name,
label: res.title,
data: res,
})
.subscribe()
} else if (res.message) {
this.dialogs.open(res.message, { label: res.title }).subscribe()
}
return true // needed to dismiss original modal/alert
} catch (e: any) {

View File

@@ -1049,77 +1049,91 @@ export module Mock {
},
}
export const ActionRes: RR.ActionRes = {
export const ActionResMessage: RR.ActionRes = {
version: '1',
type: 'string',
name: 'New Password',
description:
'Action was run successfully Action was run successfully Action was run successfully Action was run successfully Action was run successfully',
copyable: true,
qr: true,
masked: true,
value: 'iwejdoiewdhbew',
title: 'New Password',
message:
'Action was run successfully and smoothly and fully and all is good on the western front.',
result: null,
}
export const ActionProperties: RR.ActionRes = {
export const ActionResSingle: RR.ActionRes = {
version: '1',
type: 'object',
name: 'Properties',
value: [
{
type: 'string',
name: 'LND Connect',
description: 'This is some information about the thing.',
copyable: true,
qr: true,
masked: true,
value:
'lndconnect://udlyfq2mxa4355pt7cqlrdipnvk2tsl4jtsdw7zaeekenufwcev2wlad.onion:10009?cert=MIICJTCCAcugAwIBAgIRAOyq85fqAiA3U3xOnwhH678wCgYIKoZIzj0EAwIwODEfMB0GAkUEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMNTc0OTkwMzIyYzZlMB4XDTIwMTAyNjA3MzEyN1oXDTIxMTIyMTA3MzEyN1owODEfMB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMNTc0OTkwMzIyYzZlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKqfhAMMZdY-eFnU5P4bGrQTSx0lo7m8u4V0yYkzUM6jlql_u31_mU2ovLTj56wnZApkEjoPl6fL2yasZA2wiy6OBtTCBsjAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH_BAUwAwEB_zAdBgNVHQ4EFgQUYQ9uIO6spltnVCx4rLFL5BvBF9IwWwYDVR0RBFQwUoIMNTc0OTkwMzIyYzZlgglsb2NhbGhvc3SCBHVuaXiCCnVuaXhwYWNrZXSCB2J1ZmNvbm6HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHBKwSAAswCgYIKoZIzj0EAwIDSAAwRQIgVZH2Z2KlyAVY2Q2aIQl0nsvN-OEN49wreFwiBqlxNj4CIQD5_JbpuBFJuf81I5J0FQPtXY-4RppWOPZBb-y6-rkIUQ&macaroon=AgEDbG5kAusBAwoQuA8OUMeQ8Fr2h-f65OdXdRIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaFAoIbWFjYXJvb24SCGdlbmVyYXRlGhYKB21lc3NhZ2USBHJlYWQSBXdyaXRlGhcKCG9mZmNoYWluEgRyZWFkEgV3cml0ZRoWCgdvbmNoYWluEgRyZWFkEgV3cml0ZRoUCgVwZWVycxIEcmVhZBIFd3JpdGUaGAoGc2lnbmVyEghnZW5lcmF0ZRIEcmVhZAAABiCYsRUoUWuAHAiCSLbBR7b_qULDSl64R8LIU2aqNIyQfA',
},
{
type: 'object',
name: 'Nested Stuff',
description: 'This is a nested thing metric',
value: [
{
type: 'string',
name: 'Last Name',
description: 'The last name of the user',
copyable: true,
qr: true,
masked: false,
value: 'Hill',
},
{
type: 'string',
name: 'Age',
description: 'The age of the user',
copyable: false,
qr: false,
masked: false,
value: '35',
},
{
type: 'string',
name: 'Password',
description: 'A secret password',
copyable: true,
qr: false,
masked: true,
value: 'password123',
},
],
},
{
type: 'string',
name: 'Another Value',
description: 'Some more information about the service.',
copyable: false,
qr: true,
masked: false,
value: 'https://guessagain.com',
},
],
title: 'New Password',
message:
'Action was run successfully and smoothly and fully and all is good on the western front.',
result: {
type: 'single',
copyable: true,
qr: true,
masked: true,
value: 'iwejdoiewdhbew',
},
}
export const ActionResGroup: RR.ActionRes = {
version: '1',
title: 'Properties',
message:
'Successfully retrieved properties. Here is a bunch of useful information about this service.',
result: {
type: 'group',
value: [
{
type: 'single',
name: 'LND Connect',
description: 'This is some information about the thing.',
copyable: true,
qr: true,
masked: true,
value:
'lndconnect://udlyfq2mxa4355pt7cqlrdipnvk2tsl4jtsdw7zaeekenufwcev2wlad.onion:10009?cert=MIICJTCCAcugAwIBAgIRAOyq85fqAiA3U3xOnwhH678wCgYIKoZIzj0EAwIwODEfMB0GAkUEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMNTc0OTkwMzIyYzZlMB4XDTIwMTAyNjA3MzEyN1oXDTIxMTIyMTA3MzEyN1owODEfMB0GA1UEChMWbG5kIGF1dG9nZW5lcmF0ZWQgY2VydDEVMBMGA1UEAxMMNTc0OTkwMzIyYzZlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEKqfhAMMZdY-eFnU5P4bGrQTSx0lo7m8u4V0yYkzUM6jlql_u31_mU2ovLTj56wnZApkEjoPl6fL2yasZA2wiy6OBtTCBsjAOBgNVHQ8BAf8EBAMCAqQwEwYDVR0lBAwwCgYIKwYBBQUHAwEwDwYDVR0TAQH_BAUwAwEB_zAdBgNVHQ4EFgQUYQ9uIO6spltnVCx4rLFL5BvBF9IwWwYDVR0RBFQwUoIMNTc0OTkwMzIyYzZlgglsb2NhbGhvc3SCBHVuaXiCCnVuaXhwYWNrZXSCB2J1ZmNvbm6HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAGHBKwSAAswCgYIKoZIzj0EAwIDSAAwRQIgVZH2Z2KlyAVY2Q2aIQl0nsvN-OEN49wreFwiBqlxNj4CIQD5_JbpuBFJuf81I5J0FQPtXY-4RppWOPZBb-y6-rkIUQ&macaroon=AgEDbG5kAusBAwoQuA8OUMeQ8Fr2h-f65OdXdRIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaFAoIbWFjYXJvb24SCGdlbmVyYXRlGhYKB21lc3NhZ2USBHJlYWQSBXdyaXRlGhcKCG9mZmNoYWluEgRyZWFkEgV3cml0ZRoWCgdvbmNoYWluEgRyZWFkEgV3cml0ZRoUCgVwZWVycxIEcmVhZBIFd3JpdGUaGAoGc2lnbmVyEghnZW5lcmF0ZRIEcmVhZAAABiCYsRUoUWuAHAiCSLbBR7b_qULDSl64R8LIU2aqNIyQfA',
},
{
type: 'group',
name: 'Nested Stuff',
description: 'This is a nested thing metric',
value: [
{
type: 'single',
name: 'Last Name',
description: 'The last name of the user',
copyable: true,
qr: true,
masked: false,
value: 'Hill',
},
{
type: 'single',
name: 'Age',
description: 'The age of the user',
copyable: false,
qr: false,
masked: false,
value: '35',
},
{
type: 'single',
name: 'Password',
description: 'A secret password',
copyable: true,
qr: false,
masked: true,
value: 'password123',
},
],
},
{
type: 'single',
name: 'Another Value',
description: 'Some more information about the service.',
copyable: false,
qr: true,
masked: false,
value: 'https://guessagain.com',
},
],
},
}
export const getActionInputSpec = async (): Promise<IST.InputSpec> =>

View File

@@ -784,7 +784,9 @@ export class MockApiService extends ApiService {
await pauseFor(2000)
if (params.actionId === 'properties') {
return Mock.ActionProperties
// return Mock.ActionResGroup
return Mock.ActionResMessage
// return Mock.ActionResSingle
} else if (params.actionId === 'config') {
const patch: RemoveOperation[] = [
{
@@ -795,7 +797,8 @@ export class MockApiService extends ApiService {
this.mockRevision(patch)
return null
} else {
return Mock.ActionRes
return Mock.ActionResMessage
// return Mock.ActionResSingle
}
}