Merge branch 'next' of github.com:Start9Labs/start-os into rebase/integration/refactors

This commit is contained in:
Aiden McClelland
2023-10-18 17:55:09 -06:00
174 changed files with 5736 additions and 4682 deletions

View File

@@ -1,12 +1,12 @@
{
"name": "startos-ui",
"version": "0.3.4.4",
"lockfileVersion": 2,
"version": "0.3.5",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "startos-ui",
"version": "0.3.4.4",
"version": "0.3.5",
"dependencies": {
"@angular/animations": "^14.1.0",
"@angular/common": "^14.1.0",
@@ -39,6 +39,7 @@
"cron": "^2.2.0",
"cronstrue": "^2.21.0",
"dompurify": "^2.3.6",
"fast-deep-equal": "^3.1.3",
"fast-json-patch": "^3.1.1",
"fuse.js": "^6.4.6",
"jose": "^4.9.0",
@@ -6782,55 +6783,37 @@
"url": "https://github.com/sindresorhus/execa?sponsor=1"
}
},
"node_modules/default-gateway/node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==",
"dev": true,
"engines": {
"node": ">=10.17.0"
}
},
"node_modules/default-gateway/node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"dev": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/default-gateway/node_modules/mimic-fn": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
"integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/default-gateway/node_modules/npm-run-path": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
"integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
"node_modules/define-data-property": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.0.tgz",
"integrity": "sha512-UzGwzcjyv3OtAvolTj1GoyNYzfFR+iqbGjcnBEENZVCpM4/Ng1yhGNvS3lR/xDS74Tb2wGG9WzNSNIOS9UVb2g==",
"dev": true,
"dependencies": {
"path-key": "^3.0.0"
"get-intrinsic": "^1.2.1",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"devOptional": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/default-gateway/node_modules/onetime": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
"integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"dev": true,
"dependencies": {
"mimic-fn": "^2.1.0"
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">=6"
@@ -8476,6 +8459,18 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
"integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==",
"dev": true,
"dependencies": {
"get-intrinsic": "^1.1.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
@@ -8536,6 +8531,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz",
"integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==",
"dev": true,
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",

View File

@@ -1,6 +1,6 @@
{
"name": "startos-ui",
"version": "0.3.4.4",
"version": "0.3.5",
"author": "Start9 Labs, Inc",
"homepage": "https://start9.com/",
"scripts": {
@@ -63,6 +63,7 @@
"cron": "^2.2.0",
"cronstrue": "^2.21.0",
"dompurify": "^2.3.6",
"fast-deep-equal": "^3.1.3",
"fast-json-patch": "^3.1.1",
"fuse.js": "^6.4.6",
"jose": "^4.9.0",

View File

@@ -2,8 +2,8 @@
<ion-grid>
<ion-row>
<ion-col class="ion-text-center">
<div style="padding-bottom: 32px">
<img src="assets/img/logo.png" style="max-width: 240px" />
<div style="padding: 64px 0 32px 0">
<img src="assets/img/icon.png" style="max-width: 100px" />
</div>
<ion-card color="dark">

View File

@@ -4,13 +4,13 @@
<ion-col class="ion-text-center">
<div style="padding-bottom: 32px">
<img
src="assets/img/logo.png"
src="assets/img/icon.png"
class="pb-1"
style="max-width: 220px"
style="max-width: 100px"
/>
</div>
<ion-card color="dark">
<ion-card-header>
<ion-card-header style="padding-bottom: 0">
<ion-button
*ngIf="swiper?.activeIndex === 1"
class="back-button"

View File

@@ -11,7 +11,3 @@
text-align: center;
justify-content: center;
}
ion-card-title {
font-variant-caps: all-small-caps;
}

View File

@@ -44,14 +44,14 @@ export class MockApiService extends ApiService {
await pauseFor(1000)
return [
{
logicalname: 'abcd',
vendor: 'Samsung',
model: 'T5',
logicalname: '/dev/nvme0n1p3',
vendor: 'Unknown Vendor',
model: 'Samsung SSD - 970 EVO Plus 2TB',
partitions: [
{
logicalname: 'pabcd',
label: null,
capacity: 73264762332,
capacity: 1979120929996,
used: null,
'embassy-os': {
version: '0.2.17',
@@ -63,13 +63,13 @@ export class MockApiService extends ApiService {
guid: null,
},
],
capacity: 123456789123,
capacity: 1979120929996,
guid: 'uuid-uuid-uuid-uuid',
},
{
logicalname: 'dcba',
vendor: 'Crucial',
model: 'MX500',
vendor: 'CT1000MX',
model: '500SSD1',
partitions: [
{
logicalname: 'pbcba',
@@ -86,13 +86,13 @@ export class MockApiService extends ApiService {
guid: null,
},
],
capacity: 124456789123,
capacity: 1000190509056,
guid: null,
},
{
logicalname: 'wxyz',
vendor: 'SanDisk',
model: 'Specialness',
logicalname: '/dev/sda',
vendor: 'ASMT',
model: '2115',
partitions: [
{
logicalname: 'pbcba',
@@ -109,7 +109,7 @@ export class MockApiService extends ApiService {
guid: 'guid-guid-guid-guid',
},
],
capacity: 123459789123,
capacity: 1000190509056,
guid: null,
},
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 74 KiB

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -1,10 +1,5 @@
<a class="logo ion-padding" routerLink="/home">
<img
alt="Start9"
src="assets/img/{{
(theme$ | async) === 'Dark' ? 'logo' : 'logo_dark'
}}.png"
/>
<a class="logo" routerLink="/home">
<img alt="StartOS" src="assets/img/icon.png" />
</a>
<ion-item-group class="menu">
<ion-menu-toggle *ngFor="let page of pages" auto-hide="false">

View File

@@ -4,8 +4,9 @@
.logo {
display: block;
width: 60%;
width: 36%;
margin: 0 auto;
padding: 16px 16px 0 16px;
}
.menu {

View File

@@ -60,11 +60,7 @@
<ion-toolbar></ion-toolbar>
<!-- images -->
<img src="assets/img/icons/bitcoin.svg" />
<img src="assets/img/icon.png" />
<img src="assets/img/logo.png" />
<img src="assets/img/icon.png" />
<img src="assets/img/icon_transparent.png" />
<img src="assets/img/community-store.png" />
<img src="assets/img/icons/snek.png" />
<img src="assets/img/icons/wifi-1.png" />

View File

@@ -1,5 +1,5 @@
<ion-grid class="grid-wiz">
<img width="60px" height="60px" src="/assets/img/icon_transparent.png" />
<img width="60px" height="60px" src="/assets/img/icon.png" alt="StartOS" />
<ion-row>
<ion-col class="ion-text-center">
<ion-icon name="lock-closed-outline" class="wiz-icon"></ion-icon>
@@ -103,6 +103,8 @@
</ion-grid>
<a
id="install-cert"
href="/public/eos/local.crt"
[download]="document.location.hostname"
href="/eos/local.crt"
[download]="
config.isLocal() ? document.location.hostname + '.crt' : 'startos.crt'
"
></a>

View File

@@ -39,7 +39,7 @@ export class CAWizardComponent {
instructions() {
this.windowRef.open(
'https://docs.start9.com/getting-started/trust-ca/#trust-your-root-ca',
'https://docs.start9.com/0.3.5.x/getting-started/trust-ca/#trust-root-ca',
'_blank',
'noreferrer',
)

View File

@@ -1,6 +1,6 @@
<ion-content class="content">
<!-- Local HTTP -->
<ng-container *ngIf="config.isLocalHttp(); else notLanHttp">
<ng-container *ngIf="config.isLanHttp(); else notLanHttp">
<ca-wizard></ca-wizard>
</ng-container>
@@ -23,45 +23,51 @@
<ion-grid class="grid">
<ion-row class="row">
<ion-col>
<img src="assets/img/logo.png" alt="Start9" class="logo" />
<ion-card class="card">
<ion-card>
<img
alt="StartOS Icon"
class="header-icon"
src="assets/img/icon.png"
/>
<ion-card-header>
<ion-card-title class="title">StartOS Login</ion-card-title>
<ion-card-title class="title">Login to StartOS</ion-card-title>
</ion-card-header>
<ion-card-content class="ion-margin">
<form class="form" (submit)="submit()">
<ion-item-group>
<ion-item color="dark" class="login-item">
<form (submit)="submit()">
<ion-item color="dark" fill="solid">
<ion-icon
slot="start"
size="small"
color="base"
name="key-outline"
style="margin-right: 16px"
></ion-icon>
<ion-input
name="password"
placeholder="Password"
[type]="unmasked ? 'text' : 'password'"
[(ngModel)]="password"
(ionChange)="error = ''"
maxlength="64"
></ion-input>
<ion-button
slot="end"
fill="clear"
color="dark"
(click)="unmasked = !unmasked"
>
<ion-icon
slot="start"
name="key-outline"
style="margin-right: 16px"
slot="icon-only"
size="small"
[name]="unmasked ? 'eye-off-outline' : 'eye-outline'"
></ion-icon>
<ion-input
name="password"
placeholder="Password"
[type]="unmasked ? 'text' : 'password'"
[(ngModel)]="password"
(ionChange)="error = ''"
maxlength="64"
></ion-input>
<ion-button
fill="clear"
color="light"
(click)="unmasked = !unmasked"
>
<ion-icon
slot="icon-only"
size="small"
[name]="unmasked ? 'eye-off-outline' : 'eye-outline'"
></ion-icon>
</ion-button>
</ion-item>
</ion-item-group>
</ion-button>
</ion-item>
<p class="error ion-text-center">
<ion-text color="danger">{{ error }}</ion-text>
</p>
<ion-button
class="login-button side-button"
class="login-button"
type="submit"
expand="block"
color="tertiary"
@@ -69,9 +75,6 @@
Login
</ion-button>
</form>
<p class="error">
<ion-text color="danger">{{ error }}</ion-text>
</p>
</ion-card-content>
</ion-card>
</ion-col>

View File

@@ -1,15 +1,5 @@
.content {
--background: #222428;
}
.card {
background: #414141;
}
.title {
margin: 24px 0 16px;
color: #e0e0e0;
text-transform: uppercase;
--background: #333333;
}
.grid {
@@ -23,17 +13,6 @@
text-align: center;
}
.logo {
max-width: 240px;
padding-bottom: 16px;
}
.error {
display: block;
text-align: left;
padding-top: 4px;
}
.banner {
position: absolute;
padding: 20px;
@@ -46,58 +25,52 @@
}
}
.side-button {
--border-radius: 0 4px 4px 0;
}
.login-item {
--border-style: solid;
--border-color: var(--ion-color-light);
--border-radius: 4px 0 0 4px;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, 0.2), 0 2px 2px 0 rgba(0, 0, 0, 0.14),
0 1px 5px 0 rgba(0, 0, 0, 0.12);
.side-button {
--border-radius: 4px;
}
}
ion-card {
background: var(--ion-color-step-200);
box-shadow: 0 4px 4px rgba(0, 0, 0, 0.25);
border-radius: 44px;
background: #414141;
box-shadow: 0 4px 4px rgba(17, 17, 17, 0.144);
border-radius: 35px;
min-height: 16rem;
contain: unset;
overflow: unset;
position: relative;
}
ion-item {
--background: transparent;
--border-radius: 0px;
}
.title {
padding-top: 55px;
color: #e0e0e0;
font-size: 1.3rem;
}
.header {
&-icon {
width: 100px;
position: absolute;
left: 50%;
margin-left: -50px;
top: -17%;
z-index: 100;
}
}
.login-button {
margin-inline-start: 0;
margin-inline-end: 0;
height: 49px;
font-size: larger;
font-weight: bold;
}
.form {
margin-bottom: 12px;
* {
display: inline-block;
vertical-align: middle;
}
height: 45px;
width: 120px;
--border-radius: 50px;
margin: 0 auto;
margin-top: 27px;
margin-bottom: 10px;
}
.item-interactive {
--highlight-background: #5260ff !important;
}
@media (max-width: 500px) {
.side-button {
--border-radius: 4px;
margin-top: 0.7rem;
}
.login-item {
--border-radius: 4px;
}
.error {
display: block;
padding-top: 4px;
}

View File

@@ -4,7 +4,6 @@ import { ApiService } from 'src/app/services/api/embassy-api.service'
import { AuthService } from 'src/app/services/auth.service'
import { Router } from '@angular/router'
import { ConfigService } from 'src/app/services/config.service'
import { pauseFor, RELATIVE_URL } from '@start9labs/shared'
import { DOCUMENT } from '@angular/common'
import { WINDOW } from '@ng-web-apis/common'
@@ -18,58 +17,16 @@ export class LoginPage {
unmasked = false
error = ''
downloadClicked = false
instructionsClicked = false
polling = false
caTrusted = false
constructor(
private readonly router: Router,
private readonly authService: AuthService,
private readonly loadingCtrl: LoadingController,
private readonly api: ApiService,
public readonly config: ConfigService,
@Inject(RELATIVE_URL) private readonly relativeUrl: string,
@Inject(DOCUMENT) public readonly document: Document,
@Inject(WINDOW) private readonly windowRef: Window,
) {}
async ngOnInit() {
if (!this.config.isSecure()) {
await this.testHttps().catch(e =>
console.warn('Failed Https connection attempt'),
)
}
}
download() {
this.downloadClicked = true
this.document.getElementById('install-cert')?.click()
}
instructions() {
this.windowRef.open(
'https://docs.start9.com/getting-started/trust-ca/#trust-your-server-s-root-ca',
'_blank',
'noreferrer',
)
this.instructionsClicked = true
this.startDaemon()
}
private async startDaemon(): Promise<void> {
this.polling = true
while (this.polling) {
try {
await this.testHttps()
this.polling = false
} catch (e) {
console.warn('Failed Https connection attempt')
await pauseFor(2000)
}
}
}
launchHttps() {
const host = this.config.getHost()
this.windowRef.open(`https://${host}`, '_blank', 'noreferrer')
@@ -104,13 +61,4 @@ export class LoginPage {
loader.dismiss()
}
}
private async testHttps() {
const url = `https://${this.document.location.host}${this.relativeUrl}`
await this.api.echo({ message: 'ping' }, url).then(() => {
this.downloadClicked = true
this.instructionsClicked = true
this.caTrusted = true
})
}
}

View File

@@ -14,7 +14,7 @@
<div class="welcome-header">
<h1>Welcome to StartOS</h1>
</div>
<!-- <widget-list></widget-list> -->
<widget-list></widget-list>
</ng-container>
<ng-template #list>
@@ -27,7 +27,7 @@
sizeMd="6"
>
<app-list-pkg
*ngIf="pkg | packageInfo | async as info"
*ngIf="pkg.manifest.id | packageInfo | async as info"
[pkg]="info"
></app-list-pkg>
</ion-col>

View File

@@ -1,10 +1,7 @@
import { Pipe, PipeTransform } from '@angular/core'
import { Observable, combineLatest } from 'rxjs'
import { filter, map, startWith } from 'rxjs/operators'
import {
DataModel,
PackageDataEntry,
} from 'src/app/services/patch-db/data-model'
import { filter, map } from 'rxjs/operators'
import { DataModel } from 'src/app/services/patch-db/data-model'
import { getPackageInfo } from 'src/app/util/get-package-info'
import { PkgInfo } from 'src/app/types/pkg-info'
import { PatchDB } from 'patch-db-client'
@@ -19,12 +16,10 @@ export class PackageInfoPipe implements PipeTransform {
private readonly depErrorService: DepErrorService,
) {}
transform(pkg: PackageDataEntry): Observable<PkgInfo> {
transform(pkgId: string): Observable<PkgInfo> {
return combineLatest([
this.patch
.watch$('package-data', pkg.manifest.id)
.pipe(filter(Boolean), startWith(pkg)),
this.depErrorService.depErrors$,
this.patch.watch$('package-data', pkgId).pipe(filter(Boolean)),
this.depErrorService.getPkgDepErrors$(pkgId),
]).pipe(map(([pkg, depErrors]) => getPackageInfo(pkg, depErrors)))
}
}

View File

@@ -19,7 +19,7 @@ import { DependentInfo } from 'src/app/types/dependent-info'
import {
DepErrorService,
DependencyErrorType,
PackageDependencyErrors,
PkgDependencyErrors,
} from 'src/app/services/dep-error.service'
import { combineLatest } from 'rxjs'
import { Manifest } from '@start9labs/marketplace'
@@ -54,15 +54,14 @@ export class AppShowPage {
readonly pkgId = getPkgId(this.route)
readonly pkgPlus$ = combineLatest([
this.patch.watch$('package-data'),
this.depErrorService.depErrors$,
this.patch.watch$('package-data', this.pkgId),
this.depErrorService.getPkgDepErrors$(this.pkgId),
]).pipe(
tap(([pkgs, _]) => {
tap(([pkg, _]) => {
// if package disappears, navigate to list page
if (!pkgs[this.pkgId]) this.navCtrl.navigateRoot('/services')
if (!pkg) this.navCtrl.navigateRoot('/services')
}),
map(([pkgs, depErrors]) => {
const pkg = pkgs[this.pkgId]
map(([pkg, depErrors]) => {
return {
pkg,
dependencies: this.getDepInfo(pkg, depErrors),
@@ -97,7 +96,7 @@ export class AppShowPage {
private getDepInfo(
pkg: PackageDataEntry,
depErrors: PackageDependencyErrors,
depErrors: PkgDependencyErrors,
): DependencyInfo[] {
const pkgInstalled = pkg.installed
@@ -116,7 +115,7 @@ export class AppShowPage {
pkgInstalled: InstalledPackageInfo,
pkgManifest: Manifest,
depId: string,
depErrors: PackageDependencyErrors,
depErrors: PkgDependencyErrors,
): DependencyInfo {
const { errorText, fixText, fixAction } = this.getDepErrors(
pkgManifest,
@@ -143,9 +142,9 @@ export class AppShowPage {
private getDepErrors(
pkgManifest: Manifest,
depId: string,
depErrors: PackageDependencyErrors,
depErrors: PkgDependencyErrors,
) {
const depError = depErrors[pkgManifest.id][depId]
const depError = (depErrors[pkgManifest.id] as any)?.[depId] // @TODO fix
let errorText: string | null = null
let fixText: string | null = null
@@ -168,7 +167,7 @@ export class AppShowPage {
errorText = 'Not running'
fixText = 'Start'
} else if (depError.type === DependencyErrorType.HealthChecksFailed) {
errorText = 'Health check failed'
errorText = 'Required health check not passing'
} else if (depError.type === DependencyErrorType.Transitive) {
errorText = 'Dependency has a dependency issue'
}

View File

@@ -14,11 +14,16 @@
<ion-grid>
<ion-row style="padding-left: 12px">
<ion-col>
<ion-button
*ngIf="canStop"
class="action-button"
color="danger"
(click)="tryStop()"
>
<ion-icon slot="start" name="stop-outline"></ion-icon>
Stop
</ion-button>
<ng-container *ngIf="isRunning">
<ion-button class="action-button" color="danger" (click)="tryStop()">
<ion-icon slot="start" name="stop-outline"></ion-icon>
Stop
</ion-button>
<ion-button
class="action-button"
color="tertiary"

View File

@@ -78,6 +78,14 @@ export class AppShowStatusComponent {
return this.status.primary === PrimaryStatus.Running
}
get canStop(): boolean {
return [
PrimaryStatus.Running,
PrimaryStatus.Starting,
PrimaryStatus.Restarting,
].includes(this.status.primary)
}
get isStopped(): boolean {
return this.status.primary === PrimaryStatus.Stopped
}

View File

@@ -15,7 +15,7 @@
<h2>
For a secure local connection and faster Tor experience,
<a
href="https://docs.start9.com/latest/user-manual/connecting/connecting-lan"
href="https://docs.start9.com/0.3.5.x/getting-started/connecting-lan"
target="_blank"
rel="noreferrer"
>

View File

@@ -607,7 +607,7 @@ export class ServerShowPage {
icon: 'map-outline',
action: () =>
window.open(
'https://docs.start9.com/latest/user-manual',
'https://docs.start9.com/0.3.5.x/user-manual',
'_blank',
'noreferrer',
),

View File

@@ -19,7 +19,7 @@ import {
styleUrls: ['ssh-keys.page.scss'],
})
export class SSHKeysPage {
readonly docsUrl = 'https://docs.start9.com/latest/user-manual/ssh'
readonly docsUrl = 'https://docs.start9.com/0.3.5.x/user-manual/ssh'
sshKeys: SSHKey[] = []
loading$ = new BehaviorSubject(true)

View File

@@ -8,6 +8,8 @@ import {
import { PrimaryStatus } from 'src/app/services/pkg-status-rendering.service'
import { getPackageInfo } from 'src/app/util/get-package-info'
import { PkgInfo } from 'src/app/types/pkg-info'
import { combineLatest } from 'rxjs'
import { DepErrorService } from 'src/app/services/dep-error.service'
@Component({
selector: 'widget-health',
@@ -24,31 +26,32 @@ export class HealthComponent {
'Transitioning',
] as const
readonly data$ = inject(PatchDB<DataModel>)
.watch$('package-data')
.pipe(
map(data => {
const pkgs = Object.values<PackageDataEntry>(data).map(
pkg => getPackageInfo(pkg, {}), // @TODO hack because not currently using widget
)
const result = this.labels.reduce<Record<string, number>>(
(acc, label) => ({
...acc,
[label]: this.getCount(label, pkgs),
}),
{},
)
readonly data$ = combineLatest([
inject(PatchDB<DataModel>).watch$('package-data'),
inject(DepErrorService).depErrors$,
]).pipe(
map(([data, depErrors]) => {
const pkgs = Object.values<PackageDataEntry>(data).map(pkg =>
getPackageInfo(pkg, depErrors[pkg.manifest.id]),
)
const result = this.labels.reduce<Record<string, number>>(
(acc, label) => ({
...acc,
[label]: this.getCount(label, pkgs),
}),
{},
)
result['Healthy'] =
pkgs.length -
result['Error'] -
result['Needs Attention'] -
result['Stopped'] -
result['Transitioning']
result['Healthy'] =
pkgs.length -
result['Error'] -
result['Needs Attention'] -
result['Stopped'] -
result['Transitioning']
return this.labels.map(label => result[label])
}),
)
return this.labels.map(label => result[label])
}),
)
private getCount(label: string, pkgs: PkgInfo[]): number {
switch (label) {

View File

@@ -12,11 +12,11 @@
<ion-content class="ion-padding">
<h2>This Release</h2>
<h4>0.3.4.4</h4>
<h4>0.3.5</h4>
<p class="note-padding">
View the complete
<a
href="https://github.com/Start9Labs/start-os/releases/tag/v0.3.4.4"
href="https://github.com/Start9Labs/start-os/releases/tag/v0.3.5"
target="_blank"
noreferrer
>
@@ -26,98 +26,20 @@
</p>
<h6>Highlights</h6>
<ul class="spaced-list">
<li>Https over Tor for faster UI loading times</li>
<li>Change password through UI</li>
<li>Use IP address for Network Folder backups</li>
<li>
Multiple bug fixes, performance enhancements, and other small features
This release contains significant under-the-hood improvements to
performance and reliability
</li>
<li>Ditch Docker, replace with Podman</li>
<li>Remove locking behavior from PatchDB and optimize</li>
<li>Boost efficiency of service manager</li>
<li>Require HTTPS on LAN, and improve setup flow for trusting Root CA</li>
<li>Better default privacy settings for Firefox kiosk mode</li>
<li>Eliminate memory leak from Javascript runtime</li>
<li>Other small bug fixes</li>
<li>Update license to MIT</li>
</ul>
<h2>Previous Releases</h2>
<h4>0.3.4.3</h4>
<p class="note-padding">
View the complete
<a
href="https://github.com/Start9Labs/start-os/releases/tag/v0.3.4.3"
target="_blank"
noreferrer
>
release notes
</a>
for more details.
</p>
<h6>Highlights</h6>
<ul class="spaced-list">
<li>Improved Tor reliability</li>
<li>Experimental features tab</li>
<li>Multiple bugfixes and general performance enhancements</li>
<li>Update branding</li>
</ul>
<h4>0.3.4.2</h4>
<p class="note-padding">
View the complete
<a
href="https://github.com/Start9Labs/start-os/releases/tag/v0.3.4.2"
target="_blank"
noreferrer
>
release notes
</a>
for more details.
</p>
<h6>Highlights</h6>
<ul class="spaced-list">
<li>Update build system for Server Lite and NUC-based Server One</li>
<li>Rename embassyOS to StartOS</li>
<li>
PWA support for StartOS web interface. You can now save StartOS to your
phone as an app!
</li>
</ul>
<h4>0.3.4.1</h4>
<p class="note-padding">
View the complete
<a
href="https://github.com/Start9Labs/start-os/releases/tag/v0.3.4.1"
target="_blank"
noreferrer
>
release notes
</a>
for more details.
</p>
<h6>Highlights</h6>
<ul class="spaced-list">
<li>0.3.4 bug fixes</li>
</ul>
<h4>0.3.4</h4>
<p class="note-padding">
View the complete
<a
href="https://github.com/Start9Labs/start-os/releases/tag/v0.3.4"
target="_blank"
noreferrer
>
release notes
</a>
for more details.
</p>
<h6>Highlights</h6>
<ul class="spaced-list">
<li>Security patches</li>
<li>Bug fixes</li>
<li>Breakout services to Community Registry</li>
<li>SSL support for IP access</li>
<li>UI display improvements</li>
<li>Better logs</li>
<li>New system metrics</li>
<li>EFI support</li>
</ul>
<div class="ion-text-center ion-padding">
<ion-button
fill="solid"

View File

@@ -31,7 +31,7 @@ export class GetIconPipe implements PipeTransform {
const { start9, community } = this.config.marketplace
if (sameUrl(url, start9)) {
return 'assets/img/icon_transparent.png'
return 'assets/img/icon.png'
} else if (sameUrl(url, community)) {
return 'assets/img/community-store.png'
}

View File

@@ -14,8 +14,8 @@ import { Card, Dimension } from './widget-card/widget-card.component'
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class WidgetListComponent {
@ViewChild('gridContent') gridContent: ElementRef<HTMLElement> =
{} as ElementRef<HTMLElement>
@ViewChild('gridContent')
gridContent: ElementRef<HTMLElement> = {} as ElementRef<HTMLElement>
@HostListener('window:resize', ['$event'])
onResize() {
this.setContainerDimensions()
@@ -71,7 +71,7 @@ export class WidgetListComponent {
icon: 'map-outline',
color: 'var(--alt-yellow)',
description: 'Discover what StartOS can do',
link: 'https://docs.start9.com/latest/user-manual/index',
link: 'https://docs.start9.com/0.3.5.x/user-manual/index',
},
{
title: 'Contact Support',

View File

@@ -34,9 +34,10 @@ export module Mock {
'shutting-down': false,
}
export const MarketplaceEos: RR.GetMarketplaceEosRes = {
version: '0.3.4.4',
version: '0.3.5',
headline: 'Our biggest release ever.',
'release-notes': {
'0.3.5': 'Some **Markdown** release _notes_ for 0.3.5',
'0.3.4.4': 'Some **Markdown** release _notes_ for 0.3.4.4',
'0.3.4.3': 'Some **Markdown** release _notes_ for 0.3.4.3',
'0.3.4.2': 'Some **Markdown** release _notes_ for 0.3.4.2',
@@ -844,7 +845,7 @@ export module Mock {
integer: false,
}),
}),
displayAs: 'I\'m {{last-name}}, {{first-name}} {{last-name}}',
displayAs: "I'm {{last-name}}, {{first-name}} {{last-name}}",
uniqueBy: 'last-name',
},
),
@@ -1355,7 +1356,7 @@ export module Mock {
},
'dependency-info': {
bitcoind: {
title: 'Bitcoin Core',
title: Mock.MockManifestBitcoind.title,
icon: 'assets/img/service-icons/bitcoind.svg',
},
},
@@ -1415,11 +1416,11 @@ export module Mock {
'current-dependents': {},
'dependency-info': {
bitcoind: {
title: 'Bitcoin Core',
title: Mock.MockManifestBitcoind.title,
icon: 'assets/img/service-icons/bitcoind.svg',
},
'btc-rpc-proxy': {
title: 'Bitcoin Proxy',
title: Mock.MockManifestBitcoinProxy.title,
icon: 'assets/img/service-icons/btc-rpc-proxy.png',
},
},

View File

@@ -37,7 +37,7 @@ export module RR {
// server
export type EchoReq = { message: string } // server.echo
export type EchoReq = { message: string; timeout?: number } // server.echo
export type EchoRes = string
export type GetSystemTimeReq = {} // server.time

View File

@@ -1,4 +1,4 @@
import { BehaviorSubject, Observable } from 'rxjs'
import { Observable } from 'rxjs'
import { Update } from 'patch-db-client'
import { RR, BackupTargetType, Metrics } from './api.types'
import { DataModel } from 'src/app/services/patch-db/data-model'
@@ -6,8 +6,6 @@ import { Log, SetupStatus } from '@start9labs/shared'
import { WebSocketSubjectConfig } from 'rxjs/webSocket'
export abstract class ApiService {
readonly patchStream$ = new BehaviorSubject<Update<DataModel>[]>([])
// http
// for getting static files: ex icons, instructions, licenses

View File

@@ -1,6 +1,5 @@
import { Inject, Injectable } from '@angular/core'
import {
decodeBase64,
HttpOptions,
HttpService,
isRpcError,
@@ -14,7 +13,7 @@ import { ApiService } from './embassy-api.service'
import { BackupTargetType, Metrics, RR } from './api.types'
import { ConfigService } from '../config.service'
import { webSocket, WebSocketSubjectConfig } from 'rxjs/webSocket'
import { Observable } from 'rxjs'
import { Observable, filter, firstValueFrom } from 'rxjs'
import { AuthService } from '../auth.service'
import { DOCUMENT } from '@angular/common'
import { DataModel } from '../patch-db/data-model'
@@ -78,7 +77,7 @@ export class LiveApiService extends ApiService {
// auth
async login(params: RR.LoginReq): Promise<RR.loginRes> {
return this.rpcRequest({ method: 'auth.login', params }, false)
return this.rpcRequest({ method: 'auth.login', params })
}
async logout(params: RR.LogoutReq): Promise<RR.LogoutRes> {
@@ -102,7 +101,7 @@ export class LiveApiService extends ApiService {
// server
async echo(params: RR.EchoReq, urlOverride?: string): Promise<RR.EchoRes> {
return this.rpcRequest({ method: 'echo', params }, false, urlOverride)
return this.rpcRequest({ method: 'echo', params }, urlOverride)
}
openPatchWebsocket$(): Observable<Update<DataModel>> {
@@ -502,42 +501,28 @@ export class LiveApiService extends ApiService {
private async rpcRequest<T>(
options: RPCOptions,
addHeader = true,
urlOverride?: string,
): Promise<T> {
if (addHeader) {
options.headers = {
'x-patch-sequence': String(this.patch.cache$.value.sequence),
...(options.headers || {}),
}
}
const res = await this.http.rpcRequest<T>(options, urlOverride)
const encodedUpdates = res.headers.get('x-patch-updates')
const encodedError = res.headers.get('x-patch-error')
const body = res.body
if (encodedUpdates) {
const decoded = decodeBase64(encodedUpdates)
const updates: Update<DataModel>[] = JSON.parse(decoded)
this.patchStream$.next(updates)
}
if (encodedError) {
const error = decodeBase64(encodedError)
console.error(error)
}
const rpcRes = res.body
if (isRpcError(rpcRes)) {
if (rpcRes.error.code === 34) {
if (isRpcError(body)) {
if (body.error.code === 34) {
console.error('Unauthenticated, logging out')
this.auth.setUnverified()
}
throw new RpcError(rpcRes.error)
throw new RpcError(body.error)
}
return rpcRes.result
const patchSequence = res.headers.get('x-patch-sequence')
if (patchSequence)
await firstValueFrom(
this.patch.cache$.pipe(
filter(({ sequence }) => sequence >= Number(patchSequence)),
),
)
return body.result
}
private async httpRequest<T>(opts: HttpOptions): Promise<T> {

View File

@@ -24,7 +24,8 @@ import {
interval,
map,
Observable,
ReplaySubject,
shareReplay,
Subject,
switchMap,
tap,
timer,
@@ -48,8 +49,8 @@ const PROGRESS: InstallProgress = {
@Injectable()
export class MockApiService extends ApiService {
readonly mockWsSource$ = new ReplaySubject<Update<DataModel>>()
private readonly revertTime = 2000
readonly mockWsSource$ = new Subject<Update<DataModel>>()
private readonly revertTime = 1800
sequence = 0
constructor(
@@ -62,7 +63,6 @@ export class MockApiService extends ApiService {
.pipe(
tap(() => {
this.sequence = 0
this.patchStream$.next([])
}),
switchMap(verified =>
iif(
@@ -109,7 +109,9 @@ export class MockApiService extends ApiService {
value: params.value,
},
]
return this.withRevision(patch)
this.mockRevision(patch)
return null
}
// auth
@@ -151,7 +153,6 @@ export class MockApiService extends ApiService {
async echo(params: RR.EchoReq, url?: string): Promise<RR.EchoRes> {
if (url) {
const num = Math.floor(Math.random() * 10) + 1
console.warn(num)
if (num > 8) return params.message
throw new Error()
}
@@ -160,7 +161,9 @@ export class MockApiService extends ApiService {
}
openPatchWebsocket$(): Observable<Update<DataModel>> {
return this.mockWsSource$
return this.mockWsSource$.pipe(
shareReplay({ bufferSize: 1, refCount: true }),
)
}
openLogsWebsocket$(config: WebSocketSubjectConfig<Log>): Observable<Log> {
@@ -298,7 +301,9 @@ export class MockApiService extends ApiService {
value: initialProgress,
},
]
return this.withRevision(patch, 'updating')
this.mockRevision(patch)
return 'updating'
}
async restartServer(
@@ -341,7 +346,9 @@ export class MockApiService extends ApiService {
value: params.enable,
},
]
return this.withRevision(patch, null)
this.mockRevision(patch)
return null
}
// marketplace URLs
@@ -394,7 +401,9 @@ export class MockApiService extends ApiService {
value: 0,
},
]
return this.withRevision(patch, Mock.Notifications)
this.mockRevision(patch)
return Mock.Notifications
}
async deleteNotification(
@@ -648,7 +657,9 @@ export class MockApiService extends ApiService {
},
]
return this.withRevision(originalPatch)
this.mockRevision(originalPatch)
return null
}
// package
@@ -715,7 +726,9 @@ export class MockApiService extends ApiService {
},
},
]
return this.withRevision(patch)
this.mockRevision(patch)
return null
}
async getPackageConfig(
@@ -746,7 +759,9 @@ export class MockApiService extends ApiService {
value: true,
},
]
return this.withRevision(patch)
this.mockRevision(patch)
return null
}
async restorePackages(
@@ -770,7 +785,9 @@ export class MockApiService extends ApiService {
}
})
return this.withRevision(patch)
this.mockRevision(patch)
return null
}
async executePackageAction(
@@ -820,7 +837,9 @@ export class MockApiService extends ApiService {
},
]
return this.withRevision(originalPatch)
this.mockRevision(originalPatch)
return null
}
async restartPackage(
@@ -897,7 +916,9 @@ export class MockApiService extends ApiService {
},
]
return this.withRevision(patch)
this.mockRevision(patch)
return null
}
async stopPackage(params: RR.StopPackageReq): Promise<RR.StopPackageRes> {
@@ -923,7 +944,9 @@ export class MockApiService extends ApiService {
},
]
return this.withRevision(patch)
this.mockRevision(patch)
return null
}
async uninstallPackage(
@@ -949,7 +972,9 @@ export class MockApiService extends ApiService {
},
]
return this.withRevision(patch)
this.mockRevision(patch)
return null
}
async dryConfigureDependency(
@@ -1103,23 +1128,4 @@ export class MockApiService extends ApiService {
}
this.mockWsSource$.next(revision)
}
private async withRevision<T>(
patch: Operation<unknown>[],
response: T | null = null,
): Promise<T> {
if (!this.sequence) {
const { sequence } = this.bootstrapper.init()
this.sequence = sequence
}
this.patchStream$.next([
{
id: ++this.sequence,
patch,
},
])
return response as T
}
}

View File

@@ -35,7 +35,7 @@ export const mockPatchData: DataModel = {
},
'server-info': {
id: 'abcdefgh',
version: '0.3.4',
version: '0.3.5',
country: 'us',
'last-backup': new Date(new Date().valueOf() - 604800001).toISOString(),
'lan-address': 'https://adjective-noun.local',

View File

@@ -44,23 +44,12 @@ export class ConfigService {
: this.hostname.endsWith('.local')
}
isLocalhost(): boolean {
return useMocks
? mocks.maskAs === 'localhost'
: this.hostname === 'localhost'
}
isLan(): boolean {
// @TODO will not work once clearnet arrives
return !this.isTor()
}
isTorHttp(): boolean {
return this.isTor() && !this.isHttps()
}
isLocalHttp(): boolean {
return this.isLocal() && !this.isHttps()
isLanHttp(): boolean {
return !this.isTor() && !this.isLocalhost() && !this.isHttps()
}
isSecure(): boolean {
@@ -71,6 +60,12 @@ export class ConfigService {
return this.host
}
private isLocalhost(): boolean {
return useMocks
? mocks.maskAs === 'localhost'
: this.hostname === 'localhost'
}
private isHttps(): boolean {
return useMocks ? mocks.maskAsHttps : this.protocol === 'https:'
}

View File

@@ -1,17 +1,18 @@
import { Injectable } from '@angular/core'
import { Emver } from '@start9labs/shared'
import { map, shareReplay } from 'rxjs/operators'
import { distinctUntilChanged, map, shareReplay } from 'rxjs/operators'
import { PatchDB } from 'patch-db-client'
import {
DataModel,
HealthCheckResult,
HealthResult,
PackageDataEntry,
InstalledPackageDataEntry,
PackageMainStatus,
} from './patch-db/data-model'
import * as deepEqual from 'fast-deep-equal'
export type PackageDependencyErrors = Record<string, DependencyErrors>
export type DependencyErrors = Record<string, DependencyError | null>
export type AllDependencyErrors = Record<string, PkgDependencyErrors>
export type PkgDependencyErrors = Record<string, DependencyError | null>
@Injectable({
providedIn: 'root',
@@ -26,14 +27,15 @@ export class DepErrorService {
}))
.sort((a, b) => (b.depth > a.depth ? -1 : 1))
.reduce(
(errors, { id }): PackageDependencyErrors => ({
(errors, { id }): AllDependencyErrors => ({
...errors,
[id]: this.getDepErrors(pkgs, id, errors),
}),
{} as PackageDependencyErrors,
{} as AllDependencyErrors,
),
),
shareReplay(1),
distinctUntilChanged(deepEqual),
shareReplay({ bufferSize: 1, refCount: true }),
)
constructor(
@@ -41,37 +43,38 @@ export class DepErrorService {
private readonly patch: PatchDB<DataModel>,
) {}
getPkgDepErrors$(pkgId: string) {
return this.depErrors$.pipe(
map(depErrors => depErrors[pkgId]),
distinctUntilChanged(deepEqual),
)
}
private getDepErrors(
pkgs: DataModel['package-data'],
pkgId: string,
outerErrors: PackageDependencyErrors,
): DependencyErrors {
const pkg = pkgs[pkgId]
outerErrors: AllDependencyErrors,
): PkgDependencyErrors {
const pkgInstalled = pkgs[pkgId].installed
if (!pkg.installed) return {}
if (!pkgInstalled) return {}
return currentDeps(pkgs, pkgId).reduce(
(innerErrors, depId): DependencyErrors => ({
(innerErrors, depId): PkgDependencyErrors => ({
...innerErrors,
[depId]: this.getDepError(pkgs, pkg, depId, outerErrors),
[depId]: this.getDepError(pkgs, pkgInstalled, depId, outerErrors),
}),
{} as DependencyErrors,
{} as PkgDependencyErrors,
)
}
private getDepError(
pkgs: DataModel['package-data'],
pkg: PackageDataEntry,
pkgInstalled: InstalledPackageDataEntry,
depId: string,
outerErrors: PackageDependencyErrors,
outerErrors: AllDependencyErrors,
): DependencyError | null {
console.warn(depId)
console.warn(pkgs)
const dep = pkgs[depId]
const pkgInstalled = pkg.installed!
const depInstalled = dep?.installed
const depInstalled = pkgs[depId]?.installed
// not installed
if (!depInstalled) {
@@ -80,17 +83,8 @@ export class DepErrorService {
}
}
const depStatus = depInstalled.status.main.status
// backing up
if (depStatus === PackageMainStatus.BackingUp) {
return {
type: DependencyErrorType.NotRunning,
}
}
const pkgManifest = pkg.manifest
const depManifest = dep.manifest
const pkgManifest = pkgInstalled.manifest
const depManifest = depInstalled.manifest
// incorrect version
if (
@@ -117,6 +111,8 @@ export class DepErrorService {
}
}
const depStatus = depInstalled.status.main.status
// not running
if (
depStatus !== PackageMainStatus.Running &&
@@ -133,11 +129,10 @@ export class DepErrorService {
'health-checks'
]) {
if (
depInstalled.status.main.health[id].result !== HealthResult.Success
depInstalled.status.main.health[id]?.result !== HealthResult.Success
) {
return {
type: DependencyErrorType.HealthChecksFailed,
check: depInstalled.status.main.health[id],
}
}
}
@@ -185,7 +180,6 @@ export type DependencyError =
export enum DependencyErrorType {
NotInstalled = 'notInstalled',
BackingUp = 'backingUp',
NotRunning = 'notRunning',
IncorrectVersion = 'incorrectVersion',
ConfigUnsatisfied = 'configUnsatisfied',
@@ -213,7 +207,6 @@ export interface DependencyErrorConfigUnsatisfied {
export interface DependencyErrorHealthChecksFailed {
type: DependencyErrorType.HealthChecksFailed
check: HealthCheckResult
}
export interface DependencyErrorTransitive {

View File

@@ -75,7 +75,7 @@ export class MarketplaceService implements AbstractMarketplaceService {
map(({ 'selected-url': url, 'known-hosts': hosts }) =>
toStoreIdentity(url, hosts[url]),
),
shareReplay(1),
shareReplay({ bufferSize: 1, refCount: true }),
)
private readonly marketplace$ = this.knownHosts$.pipe(
@@ -103,7 +103,7 @@ export class MarketplaceService implements AbstractMarketplaceService {
},
{},
),
shareReplay(1),
shareReplay({ bufferSize: 1, refCount: true }),
)
private readonly filteredMarketplace$ = combineLatest([

View File

@@ -11,13 +11,13 @@ import {
EMPTY,
from,
interval,
merge,
Observable,
} from 'rxjs'
import { DataModel } from './data-model'
import { AuthService } from '../auth.service'
import { ConnectionService } from '../connection.service'
import { ApiService } from '../api/embassy-api.service'
import { ConfigService } from '../config.service'
export const PATCH_SOURCE = new InjectionToken<Observable<Update<DataModel>[]>>(
'',
@@ -31,6 +31,9 @@ export function sourceFactory(
const api = injector.get(ApiService)
const authService = injector.get(AuthService)
const connectionService = injector.get(ConnectionService)
const configService = injector.get(ConfigService)
const isTor = configService.isTor()
const timeout = isTor ? 16000 : 4000
const websocket$ = api.openPatchWebsocket$().pipe(
bufferTime(250),
@@ -38,9 +41,11 @@ export function sourceFactory(
catchError((_, watch$) => {
connectionService.websocketConnected$.next(false)
return interval(4000).pipe(
return interval(timeout).pipe(
switchMap(() =>
from(api.echo({ message: 'ping' })).pipe(catchError(() => EMPTY)),
from(api.echo({ message: 'ping', timeout })).pipe(
catchError(() => EMPTY),
),
),
take(1),
switchMap(() => watch$),
@@ -50,9 +55,7 @@ export function sourceFactory(
)
return authService.isVerified$.pipe(
switchMap(verified =>
verified ? merge(websocket$, api.patchStream$) : EMPTY,
),
switchMap(verified => (verified ? websocket$ : EMPTY)),
)
})
}

View File

@@ -12,13 +12,9 @@ import { LocalStorageBootstrap } from './patch-db/local-storage-bootstrap'
export class PatchMonitorService extends Observable<any> {
// @TODO not happy with Observable<void>
private readonly stream$ = this.authService.isVerified$.pipe(
tap(verified => {
if (verified) {
this.patch.start(this.bootstrapper)
} else {
this.patch.stop()
}
}),
tap(verified =>
verified ? this.patch.start(this.bootstrapper) : this.patch.stop(),
),
)
constructor(

View File

@@ -4,8 +4,7 @@ import {
PackageState,
Status,
} from 'src/app/services/patch-db/data-model'
import { PackageDependencyErrors } from './dep-error.service'
import { Manifest } from '../../../../marketplace/src/types'
import { PkgDependencyErrors } from './dep-error.service'
export interface PackageStatus {
primary: PrimaryStatus | PackageState | PackageMainStatus
@@ -15,7 +14,7 @@ export interface PackageStatus {
export function renderPkgStatus(
pkg: PackageDataEntry,
depErrors: PackageDependencyErrors,
depErrors: PkgDependencyErrors,
): PackageStatus {
let primary: PrimaryStatus | PackageState | PackageMainStatus
let dependency: DependencyStatus | null = null
@@ -23,7 +22,7 @@ export function renderPkgStatus(
if (pkg.state === PackageState.Installed && pkg.installed) {
primary = getPrimaryStatus(pkg.installed.status)
dependency = getDependencyStatus(pkg.manifest, depErrors)
dependency = getDependencyStatus(depErrors)
health = getHealthStatus(pkg.installed.status)
} else {
primary = pkg.state
@@ -40,11 +39,8 @@ function getPrimaryStatus(status: Status): PrimaryStatus | PackageMainStatus {
}
}
function getDependencyStatus(
manifest: Manifest,
depErrors: PackageDependencyErrors,
): DependencyStatus {
return Object.values(depErrors[manifest.id]).some(err => !!err)
function getDependencyStatus(depErrors: PkgDependencyErrors): DependencyStatus {
return Object.values(depErrors).some(err => !!err)
? DependencyStatus.Warning
: DependencyStatus.Satisfied
}

View File

@@ -8,11 +8,11 @@ import {
} from '../services/pkg-status-rendering.service'
import { PkgInfo } from '../types/pkg-info'
import { packageLoadingProgress } from './package-loading-progress'
import { PackageDependencyErrors } from '../services/dep-error.service'
import { PkgDependencyErrors } from '../services/dep-error.service'
export function getPackageInfo(
entry: PackageDataEntry,
depErrors: PackageDependencyErrors,
depErrors: PkgDependencyErrors,
): PkgInfo {
const statuses = renderPkgStatus(entry, depErrors)
const primaryRendering = PrimaryRendering[statuses.primary]

View File

@@ -5,11 +5,11 @@
"background_color": "#1e1e1e",
"display": "standalone",
"scope": ".",
"start_url": "/?version=0344",
"id": "/?version=0344",
"start_url": "/?version=035",
"id": "/?version=035",
"icons": [
{
"src": "assets/img/icon_pwa.png",
"src": "assets/img/icon.png",
"sizes": "256x256",
"type": "image/png",
"purpose": "any"