mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
UI/feature/actions (#195)
* ui: actions page * rework actions page * add warning to Actions Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
This commit is contained in:
committed by
Aiden McClelland
parent
5cf7d1ff88
commit
02ab63da81
@@ -70,7 +70,6 @@
|
||||
<ion-icon name="arrow-forward"></ion-icon>
|
||||
<ion-icon name="arrow-up"></ion-icon>
|
||||
<ion-icon name="bookmark-outline"></ion-icon>
|
||||
<ion-icon name="cart-outline"></ion-icon>
|
||||
<ion-icon name="chevron-down"></ion-icon>
|
||||
<ion-icon name="chevron-up"></ion-icon>
|
||||
<ion-icon name="close"></ion-icon>
|
||||
@@ -86,6 +85,7 @@
|
||||
<ion-icon name="eye-off-outline"></ion-icon>
|
||||
<ion-icon name="eye-outline"></ion-icon>
|
||||
<ion-icon name="file-tray-stacked-outline"></ion-icon>
|
||||
<ion-icon name="flash-outline"></ion-icon>
|
||||
<ion-icon name="grid-outline"></ion-icon>
|
||||
<ion-icon name="help-circle-outline"></ion-icon>
|
||||
<ion-icon name="home-outline"></ion-icon>
|
||||
@@ -100,6 +100,7 @@
|
||||
<ion-icon name="reload-outline"></ion-icon>
|
||||
<ion-icon name="refresh-outline"></ion-icon>
|
||||
<ion-icon name="save-outline"></ion-icon>
|
||||
<ion-icon name="storefront-outline"></ion-icon>
|
||||
<ion-icon name="terminal-outline"></ion-icon>
|
||||
<ion-icon name="trash-outline"></ion-icon>
|
||||
<ion-icon name="warning-outline"></ion-icon>
|
||||
|
||||
@@ -42,7 +42,7 @@ export class AppComponent {
|
||||
{
|
||||
title: 'Marketplace',
|
||||
url: '/services/marketplace',
|
||||
icon: 'cart-outline',
|
||||
icon: 'storefront-outline',
|
||||
},
|
||||
{
|
||||
title: 'Notifications',
|
||||
|
||||
@@ -149,6 +149,7 @@ function emptyAppInstalledFull (): Omit<AppInstalledFull, keyof AppInstalledPrev
|
||||
lastBackup: null,
|
||||
configuredRequirements: null,
|
||||
hasFetchedFull: false,
|
||||
actions: [],
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,9 +48,17 @@ export interface AppInstalledFull extends AppInstalledPreview {
|
||||
hasFetchedFull: boolean
|
||||
uninstallAlert?: string
|
||||
restoreAlert?: string
|
||||
actions: Actions
|
||||
}
|
||||
// dependencies
|
||||
|
||||
export type Actions = ServiceAction[]
|
||||
export interface ServiceAction {
|
||||
id: string,
|
||||
name: string,
|
||||
description: string,
|
||||
warning?: string
|
||||
allowedStatuses: AppStatus[]
|
||||
}
|
||||
export interface AppDependency extends InstalledAppDependency {
|
||||
// explanation of why it *is* optional. null represents it is required.
|
||||
optional: string | null
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
|
||||
import { AppActionsPage } from './app-actions.page'
|
||||
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
|
||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
||||
import { QRComponentModule } from 'src/app/components/qr/qr.component.module'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: AppActionsPage,
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
RouterModule.forChild(routes),
|
||||
PwaBackComponentModule,
|
||||
BadgeMenuComponentModule,
|
||||
QRComponentModule,
|
||||
SharingModule,
|
||||
],
|
||||
declarations: [AppActionsPage],
|
||||
})
|
||||
export class AppActionsPageModule { }
|
||||
@@ -0,0 +1,44 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<pwa-back-button></pwa-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Actions</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<badge-menu-button></badge-menu-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-padding-top">
|
||||
<ion-spinner *ngIf="$loading$ | async" class="center" name="lines" color="warning"></ion-spinner>
|
||||
|
||||
<ng-container *ngIf="!($loading$ | async) && {
|
||||
title: app.title | async,
|
||||
versionInstalled: app.versionInstalled | async,
|
||||
status: app.status | async,
|
||||
actions: app.actions | async
|
||||
} as vars">
|
||||
<ion-item *ngIf="error" style="margin-bottom: 16px;">
|
||||
<ion-text class="ion-text-wrap" color="danger">{{ error }}</ion-text>
|
||||
</ion-item>
|
||||
|
||||
<!-- no metrics -->
|
||||
<ion-item *ngIf="!vars.actions.length">
|
||||
<ion-label class="ion-text-wrap">
|
||||
<p>No Actions for {{ vars.title }} {{ vars.versionInstalled }}.</p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
|
||||
<!-- actions -->
|
||||
<ion-item-group>
|
||||
<ion-item button *ngFor="let action of vars.actions" (click)="handleAction(action)" >
|
||||
<ion-label class="ion-text-wrap">
|
||||
<h2><ion-text color="primary">{{ action.name }}</ion-text><ion-icon *ngIf="!(action.allowedStatuses | includes: vars.status)" color="danger" name="close-outline"></ion-icon></h2>
|
||||
<p><ion-text color="dark">{{ action.description }}</ion-text></p>
|
||||
</ion-label>
|
||||
</ion-item>
|
||||
</ion-item-group>
|
||||
|
||||
</ng-container>
|
||||
</ion-content>
|
||||
94
ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts
Normal file
94
ui/src/app/pages/apps-routes/app-actions/app-actions.page.ts
Normal file
@@ -0,0 +1,94 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ApiService, isRpcFailure, isRpcSuccess } from 'src/app/services/api/api.service'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { AlertController } from '@ionic/angular'
|
||||
import { ModelPreload } from 'src/app/models/model-preload'
|
||||
import { LoaderService, markAsLoadingDuring$ } from 'src/app/services/loader.service'
|
||||
import { ServiceAction, AppInstalledFull } from 'src/app/models/app-types'
|
||||
import { PropertySubject } from 'src/app/util/property-subject.util'
|
||||
import { map } from 'rxjs/operators'
|
||||
import { Cleanup } from 'src/app/util/cleanup'
|
||||
|
||||
@Component({
|
||||
selector: 'app-actions',
|
||||
templateUrl: './app-actions.page.html',
|
||||
styleUrls: ['./app-actions.page.scss'],
|
||||
})
|
||||
export class AppActionsPage extends Cleanup {
|
||||
error = ''
|
||||
$loading$ = new BehaviorSubject(true)
|
||||
appId: string
|
||||
app: PropertySubject<AppInstalledFull>
|
||||
|
||||
constructor (
|
||||
private readonly route: ActivatedRoute,
|
||||
private readonly apiService: ApiService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
private readonly preload: ModelPreload,
|
||||
private readonly loaderService: LoaderService,
|
||||
) { super() }
|
||||
|
||||
ngOnInit () {
|
||||
this.appId = this.route.snapshot.paramMap.get('appId')
|
||||
|
||||
markAsLoadingDuring$(this.$loading$, this.preload.appFull(this.appId)).pipe(
|
||||
map(app => this.app = app),
|
||||
).subscribe( { error: e => this.error = e.message } )
|
||||
}
|
||||
|
||||
async handleAction (action: ServiceAction) {
|
||||
if (action.allowedStatuses.includes(this.app.status.getValue())) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Confirm',
|
||||
message: `Are you sure you want to execute action "${action.name}"? ${action.warning}`,
|
||||
buttons: [
|
||||
{
|
||||
text: 'Cancel',
|
||||
role: 'cancel',
|
||||
},
|
||||
{
|
||||
text: 'Execute',
|
||||
handler: () => {
|
||||
this.executeAction(action)
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
await alert.present()
|
||||
} else {
|
||||
const alert = await this.alertCtrl.create({
|
||||
header: 'Forbidden',
|
||||
message: `Action "${action.name}" can only be executed when service is ${action.allowedStatuses.join(', ')}`,
|
||||
buttons: ['OK'],
|
||||
cssClass: 'alert-error-message',
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
}
|
||||
|
||||
private async executeAction (action: ServiceAction) {
|
||||
const res = await this.loaderService.displayDuringP(
|
||||
this.apiService.serviceAction(this.appId, action),
|
||||
)
|
||||
if (isRpcFailure(res)) {
|
||||
const successAlert = await this.alertCtrl.create({
|
||||
header: 'Execution Failed',
|
||||
message: `Error code ${res.error.code}. ${res.error.message}`,
|
||||
buttons: ['OK'],
|
||||
cssClass: 'alert-error-message',
|
||||
})
|
||||
return await successAlert.present()
|
||||
}
|
||||
|
||||
if (isRpcSuccess(res)) {
|
||||
const successAlert = await this.alertCtrl.create({
|
||||
header: 'Execution Complete',
|
||||
message: res.result,
|
||||
buttons: ['OK'],
|
||||
cssClass: 'alert-success-message',
|
||||
})
|
||||
return await successAlert.present()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@
|
||||
<p class="ion-text-wrap">Get started by installing your first service.</p>
|
||||
</div>
|
||||
<ion-button [routerLink]="['/services','marketplace']" style="width: 50%;" fill="outline">
|
||||
<ion-icon slot="start" name="cart-outline"></ion-icon>
|
||||
<ion-icon slot="start" name="storefront-outline"></ion-icon>
|
||||
Marketplace
|
||||
</ion-button>
|
||||
</div>
|
||||
|
||||
@@ -150,6 +150,11 @@
|
||||
<ion-icon slot="start" name="information-circle-outline" color="primary"></ion-icon>
|
||||
<ion-label><ion-text color="primary">Properties</ion-text></ion-label>
|
||||
</ion-item>
|
||||
<!-- actions -->
|
||||
<ion-item [routerLink]="['actions']">
|
||||
<ion-icon slot="start" name="flash-outline" color="primary"></ion-icon>
|
||||
<ion-label><ion-text color="primary">Actions</ion-text></ion-label>
|
||||
</ion-item>
|
||||
<!-- logs -->
|
||||
<ion-item [disabled]="vars.status === AppStatus.INSTALLING" [routerLink]="['logs']">
|
||||
<ion-icon slot="start" name="newspaper-outline" color="primary"></ion-icon>
|
||||
@@ -157,7 +162,7 @@
|
||||
</ion-item>
|
||||
<!-- marketplace -->
|
||||
<ion-item lines="none" [routerLink]="['/services', 'marketplace', appId]">
|
||||
<ion-icon slot="start" name="cart-outline" color="primary"></ion-icon>
|
||||
<ion-icon slot="start" name="storefront-outline" color="primary"></ion-icon>
|
||||
<ion-label><ion-text color="primary">Marketplace Listing</ion-text></ion-label>
|
||||
</ion-item>
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import { LoaderService, markAsLoadingDuring$, markAsLoadingDuringP } from 'src/a
|
||||
import { BehaviorSubject, combineLatest, from, merge, Observable, of, Subject } from 'rxjs'
|
||||
import { wizardModal } from 'src/app/components/install-wizard/install-wizard.component'
|
||||
import { WizardBaker } from 'src/app/components/install-wizard/prebaked-wizards'
|
||||
import { catchError, concatMap, delay, filter, map, retryWhen, switchMap, take, tap } from 'rxjs/operators'
|
||||
import { catchError, concatMap, delay, distinctUntilChanged, filter, map, retryWhen, switchMap, take, tap } from 'rxjs/operators'
|
||||
import { Cleanup } from 'src/app/util/cleanup'
|
||||
import { InformationPopoverComponent } from 'src/app/components/information-popover/information-popover.component'
|
||||
import { Emver } from 'src/app/services/emver.service'
|
||||
@@ -79,6 +79,7 @@ export class AppInstalledShowPage extends Cleanup {
|
||||
concatMap(app =>
|
||||
merge(
|
||||
this.syncWhenDependencyInstalls(),
|
||||
// new lan info from sync daemon
|
||||
combineLatest([app.lanEnabled, this.$lanConnected$, app.status, this.$testingLanConnection$]).pipe(
|
||||
filter(([_, __, s, alreadyConnecting]) => s === AppStatus.RUNNING && !alreadyConnecting),
|
||||
concatMap(([enabled, connected]) => {
|
||||
@@ -87,6 +88,22 @@ export class AppInstalledShowPage extends Cleanup {
|
||||
return of()
|
||||
}),
|
||||
),
|
||||
// toggle lan
|
||||
combineLatest([this.$lanToggled$, app.lanEnabled, this.$testingLanConnection$]).pipe(
|
||||
distinctUntilChanged(([toggled1], [toggled2]) => toggled1 !== toggled2),
|
||||
filter(([_, __, alreadyLoading]) => !alreadyLoading),
|
||||
map(([e, _]) => [(e as any).detail.checked, _]),
|
||||
// if the app is already in the desired state, we bail
|
||||
// this can happen because ionChange triggers when the [checked] value changes
|
||||
filter(([uiEnabled, appEnabled]) => (uiEnabled && !appEnabled) || (!uiEnabled && appEnabled)),
|
||||
map(([enabled]) => enabled
|
||||
? this.enableLan().pipe(concatMap(() => this.testLanConnection()))
|
||||
: this.disableLan(),
|
||||
),
|
||||
concatMap(o => markAsLoadingDuring$(this.$testingLanConnection$, o).pipe(
|
||||
catchError(e => this.setError(e)),
|
||||
)),
|
||||
),
|
||||
),
|
||||
), //must be final in stack
|
||||
catchError(e => this.setError(e)),
|
||||
@@ -121,22 +138,6 @@ export class AppInstalledShowPage extends Cleanup {
|
||||
$lanToggled$ = new Subject()
|
||||
ionViewDidEnter () {
|
||||
markAsLoadingDuringP(this.$loadingDependencies$, this.getApp())
|
||||
this.cleanup(
|
||||
combineLatest([this.$lanToggled$, this.app.lanEnabled, this.$testingLanConnection$]).pipe(
|
||||
filter(([_, __, alreadyLoading]) => !alreadyLoading),
|
||||
map(([e, _]) => [(e as any).detail.checked, _]),
|
||||
// if the app is already in the desired state, we bail
|
||||
// this can happen because ionChange triggers when the [checked] value changes
|
||||
filter(([uiEnabled, appEnabled]) => (uiEnabled && !appEnabled) || (!uiEnabled && appEnabled)),
|
||||
map(([enabled]) => enabled
|
||||
? this.enableLan().pipe(concatMap(() => this.testLanConnection()))
|
||||
: this.disableLan(),
|
||||
),
|
||||
concatMap(o => markAsLoadingDuring$(this.$testingLanConnection$, o).pipe(
|
||||
catchError(e => this.setError(e)),
|
||||
)),
|
||||
).subscribe({ error: e => console.error(e) }),
|
||||
)
|
||||
}
|
||||
|
||||
async doRefresh (event: any) {
|
||||
|
||||
@@ -43,6 +43,10 @@ const routes: Routes = [
|
||||
path: 'installed/:appId/metrics',
|
||||
loadChildren: () => import('./app-metrics/app-metrics.module').then(m => m.AppMetricsPageModule),
|
||||
},
|
||||
{
|
||||
path: 'installed/:appId/actions',
|
||||
loadChildren: () => import('./app-actions/app-actions.module').then(m => m.AppActionsPageModule),
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Rules } from '../../models/app-model'
|
||||
import { AppAvailablePreview, AppAvailableFull, AppInstalledPreview, AppInstalledFull, DependentBreakage, AppAvailableVersionSpecificInfo } from '../../models/app-types'
|
||||
import { AppAvailablePreview, AppAvailableFull, AppInstalledPreview, AppInstalledFull, DependentBreakage, AppAvailableVersionSpecificInfo, ServiceAction } from '../../models/app-types'
|
||||
import { S9Notification, SSHFingerprint, ServerMetrics, DiskInfo } from '../../models/server-model'
|
||||
import { Subject, Observable } from 'rxjs'
|
||||
import { Unit, ApiServer, ApiAppInstalledFull, ApiAppConfig, ApiAppAvailableFull } from './api-types'
|
||||
@@ -51,7 +51,7 @@ export abstract class ApiService {
|
||||
abstract restoreAppBackup (appId: string, logicalname: string, password?: string): Promise<Unit>
|
||||
abstract stopAppBackup (appId: string): Promise<Unit>
|
||||
abstract patchAppConfig (app: AppInstalledPreview, config: object, dryRun?: boolean): Promise<{ breakages: DependentBreakage[] }>
|
||||
abstract postConfigureDependency (dependencyId: string, dependentId: string, dryRun?: boolean): Promise< {config: object, breakages: DependentBreakage[] }>
|
||||
abstract postConfigureDependency (dependencyId: string, dependentId: string, dryRun?: boolean): Promise< { config: object, breakages: DependentBreakage[] }>
|
||||
abstract patchServerConfig (attr: string, value: any): Promise<Unit>
|
||||
abstract wipeAppData (app: AppInstalledPreview): Promise<Unit>
|
||||
abstract addSSHKey (sshKey: string): Promise<Unit>
|
||||
@@ -62,12 +62,30 @@ export abstract class ApiService {
|
||||
abstract restartServer (): Promise<Unit>
|
||||
abstract shutdownServer (): Promise<Unit>
|
||||
abstract ejectExternalDisk (logicalName: string): Promise<Unit>
|
||||
abstract serviceAction (appId: string, serviceAction: ServiceAction): Promise<ReqRes.ServiceActionResponse>
|
||||
}
|
||||
|
||||
export function isRpcFailure<Error, Result> (arg: { error: Error } | { result: Result}): arg is { error: Error } {
|
||||
return !!(arg as any).error
|
||||
}
|
||||
|
||||
export function isRpcSuccess<Error, Result> (arg: { error: Error } | { result: Result}): arg is { result: Result } {
|
||||
return !!(arg as any).result
|
||||
}
|
||||
|
||||
export module ReqRes {
|
||||
export type GetVersionRes = { version: string }
|
||||
export type PostLoginReq = { password: string }
|
||||
export type PostLoginRes = Unit
|
||||
export type ServiceActionRequest = {
|
||||
jsonrpc: '2.0',
|
||||
id: string,
|
||||
method: string
|
||||
}
|
||||
export type ServiceActionResponse = {
|
||||
jsonrpc: '2.0',
|
||||
id: string
|
||||
} & ({ error: { code: number, message: string } } | { result : string })
|
||||
export type GetCheckAuthRes = { }
|
||||
export type GetServerRes = ApiServer
|
||||
export type GetVersionLatestRes = { versionLatest: string, releaseNotes: string }
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { HttpService, Method, HttpOptions } from '../http.service'
|
||||
import { AppModel, AppStatus } from '../../models/app-model'
|
||||
import { AppAvailablePreview, AppAvailableFull, AppInstalledFull, AppInstalledPreview, DependentBreakage, AppAvailableVersionSpecificInfo } from '../../models/app-types'
|
||||
import { AppAvailablePreview, AppAvailableFull, AppInstalledFull, AppInstalledPreview, DependentBreakage, AppAvailableVersionSpecificInfo, ServiceAction } from '../../models/app-types'
|
||||
import { S9Notification, SSHFingerprint, ServerModel, DiskInfo } from '../../models/server-model'
|
||||
import { ApiService, ReqRes } from './api.service'
|
||||
import { ApiServer, Unit } from './api-types'
|
||||
@@ -12,6 +12,7 @@ import { AppMetrics, parseMetricsPermissive } from 'src/app/util/metrics.util'
|
||||
import { modulateTime } from 'src/app/util/misc.util'
|
||||
import { Observable, of, throwError } from 'rxjs'
|
||||
import { catchError, mapTo } from 'rxjs/operators'
|
||||
import * as uuid from 'uuid'
|
||||
|
||||
@Injectable()
|
||||
export class LiveApiService extends ApiService {
|
||||
@@ -266,6 +267,15 @@ export class LiveApiService extends ApiService {
|
||||
return this.authRequest({ method: Method.POST, url: '/shutdown', readTimeout: 60000 })
|
||||
}
|
||||
|
||||
async serviceAction (appId: string, s: ServiceAction): Promise<ReqRes.ServiceActionResponse> {
|
||||
const data: ReqRes.ServiceActionRequest = {
|
||||
jsonrpc: '2.0',
|
||||
id: uuid.v4(),
|
||||
method: s.id,
|
||||
}
|
||||
return this.authRequest({ method: Method.POST, url: `apps/${appId}/actions`, data })
|
||||
}
|
||||
|
||||
private async authRequest<T> (opts: HttpOptions, overrides: Partial<{ version: string }> = { }): Promise<T> {
|
||||
if (!this.authenticatedRequestsEnabled) throw new Error(`Authenticated requests are not enabled. Do you need to login?`)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Injectable } from '@angular/core'
|
||||
import { AppStatus, AppModel } from '../../models/app-model'
|
||||
import { AppAvailablePreview, AppAvailableFull, AppInstalledPreview, AppInstalledFull, DependentBreakage, AppAvailableVersionSpecificInfo } from '../../models/app-types'
|
||||
import { AppAvailablePreview, AppAvailableFull, AppInstalledPreview, AppInstalledFull, DependentBreakage, AppAvailableVersionSpecificInfo, ServiceAction } from '../../models/app-types'
|
||||
import { S9Notification, SSHFingerprint, ServerStatus, ServerModel, DiskInfo } from '../../models/server-model'
|
||||
import { pauseFor } from '../../util/misc.util'
|
||||
import { ApiService, ReqRes } from './api.service'
|
||||
@@ -228,6 +228,20 @@ export class MockApiService extends ApiService {
|
||||
async shutdownServer (): Promise<EmptyResponse> {
|
||||
return mockShutdownServer()
|
||||
}
|
||||
|
||||
async serviceAction (appId: string, action: ServiceAction): Promise<ReqRes.ServiceActionResponse> {
|
||||
console.log('service action', appId, action)
|
||||
await pauseFor(1000)
|
||||
return {
|
||||
jsonrpc: '2.0',
|
||||
id: '0',
|
||||
// result: 'Congrats! you did ' + action.name,
|
||||
error: {
|
||||
code: 1,
|
||||
message: 'woooo that was bad bad bad',
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function mockGetServer (): Promise<ReqRes.GetServerRes> {
|
||||
|
||||
@@ -60,7 +60,11 @@ export const bitcoinI: AppInstalledFull = {
|
||||
configuredRequirements: [],
|
||||
hasFetchedFull: true,
|
||||
ui: false,
|
||||
restoreAlert: 'if you restore this app horrible things will happen to the people you love.'
|
||||
restoreAlert: 'if you restore this app horrible things will happen to the people you love.',
|
||||
actions: [
|
||||
{ id: 'sync-chain', name: 'Sync Chain', description: 'this will sync with the chain like from Avatar', allowedStatuses: [ AppStatus.RUNNING, AppStatus.RUNNING, AppStatus.RUNNING, AppStatus.RUNNING ]},
|
||||
{ id: 'off-sync-chain', name: 'Off Sync Chain', description: 'this will off sync with the chain like from Avatar', allowedStatuses: [ AppStatus.STOPPED ]}
|
||||
],
|
||||
}
|
||||
|
||||
export const lightningI: AppInstalledFull = {
|
||||
@@ -86,6 +90,7 @@ export const lightningI: AppInstalledFull = {
|
||||
],
|
||||
hasFetchedFull: true,
|
||||
ui: true,
|
||||
actions: [],
|
||||
}
|
||||
|
||||
export const cupsI: AppInstalledFull = {
|
||||
@@ -132,6 +137,7 @@ export const cupsI: AppInstalledFull = {
|
||||
}),
|
||||
],
|
||||
hasFetchedFull: true,
|
||||
actions: [],
|
||||
}
|
||||
|
||||
export const bitcoinA: AppAvailableFull = {
|
||||
|
||||
@@ -221,11 +221,17 @@ ion-popover {
|
||||
}
|
||||
|
||||
.alert-error-message {
|
||||
.alert-message {
|
||||
.alert-title {
|
||||
color: var(--ion-color-danger);
|
||||
}
|
||||
}
|
||||
|
||||
.alert-success-message {
|
||||
.alert-title {
|
||||
color: var(--ion-color-success);
|
||||
}
|
||||
}
|
||||
|
||||
ion-slides {
|
||||
.slider-wrapper {
|
||||
height: 100%;
|
||||
|
||||
Reference in New Issue
Block a user