mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-31 04:23:40 +00:00
Merge branch 'next' of github.com:Start9Labs/start-os into rebase/integration/refactors
This commit is contained in:
93
frontend/package-lock.json
generated
93
frontend/package-lock.json
generated
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -11,7 +11,3 @@
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
ion-card-title {
|
||||
font-variant-caps: all-small-caps;
|
||||
}
|
||||
@@ -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 |
@@ -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">
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
|
||||
.logo {
|
||||
display: block;
|
||||
width: 60%;
|
||||
width: 36%;
|
||||
margin: 0 auto;
|
||||
padding: 16px 16px 0 16px;
|
||||
}
|
||||
|
||||
.menu {
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
>
|
||||
|
||||
@@ -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',
|
||||
),
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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:'
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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([
|
||||
|
||||
@@ -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)),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user