Compare commits

...

11 Commits

Author SHA1 Message Date
Mirko Rainer
5d3bc8cfa5 Update index.html (#1419)
* Update index.html

Increase proposed time to wait.

* Update index.html

Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>

Co-authored-by: Lucy C <12953208+elvece@users.noreply.github.com>
2022-05-05 15:41:28 -06:00
dependabot[bot]
b130608a78 Bump async from 2.6.3 to 2.6.4 in /frontend (#1426)
* Bump async from 2.6.3 to 2.6.4 in /frontend

Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix eos version

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Lucy Cifferello <12953208+elvece@users.noreply.github.com>
2022-05-04 16:51:25 -06:00
Chris Guida
6fa0a9762f Update server-show.page.ts
kernel logs are not just for device drivers
2022-05-04 18:38:05 -04:00
Aiden McClelland
17270e41fd version bump (#1423)
* version bump

* set `Current`

* update welcome message and adjust styling

Co-authored-by: Lucy Cifferello <12953208+elvece@users.noreply.github.com>
2022-05-04 16:36:23 -06:00
Aiden McClelland
4ac03293ed attempt to heal when health check passes 2022-05-02 15:36:26 -04:00
Aiden McClelland
7c17e26480 prevent the kernel from OOMKilling embassyd (#1402)
* prevent the kernel from OOMKilling embassyd

* privilege embassyd with respect to cpu usage

* add a docker slice
2022-04-20 11:26:41 -04:00
Mariusz Kogen
1ac711c864 smarter wget
If for some reason make will be interrupted, wget will try to download .zip file and cause the name is taken it will download it to .zip1 which wont be recognized. This fixes it.
2022-04-15 15:26:00 -04:00
kn0wmad
82c2adbc7b Update Makefile (#1400)
Remove FE config file during a `make clean`
2022-04-15 10:57:25 -06:00
Lucy Cifferello
1dcf390ee9 make montserrat important to override, mainly for menu 2022-04-14 12:44:08 -04:00
waterplea
a143e2581e chore(snek): remove keyboard activation 2022-04-14 12:44:08 -04:00
waterplea
d7bdc15e49 refactor: decompose app component 2022-04-14 12:44:08 -04:00
54 changed files with 715 additions and 559 deletions

View File

@@ -22,6 +22,7 @@ clean:
rm -f product_key.txt
rm -f system-images/**/*.tar
sudo rm -f $(EMBASSY_BINS)
rm -f frontend/config.json
rm -rf frontend/node_modules
rm -rf frontend/dist
rm -rf patch-db/client/node_modules
@@ -39,7 +40,7 @@ system-images/utils/utils.tar: $(UTILS_SRC)
cd system-images/utils && DOCKER_CLI_EXPERIMENTAL=enabled docker buildx build --tag start9/x_system/utils --platform=linux/arm64 -o type=docker,dest=utils.tar .
raspios.img:
wget https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2022-01-28/2022-01-28-raspios-bullseye-arm64-lite.zip
wget --continue https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2022-01-28/2022-01-28-raspios-bullseye-arm64-lite.zip
unzip 2022-01-28-raspios-bullseye-arm64-lite.zip
mv 2022-01-28-raspios-bullseye-arm64-lite.img raspios.img

2
backend/Cargo.lock generated
View File

@@ -825,7 +825,7 @@ dependencies = [
[[package]]
name = "embassy-os"
version = "0.3.0-rev.2"
version = "0.3.0-rev.3"
dependencies = [
"aes",
"async-trait",

View File

@@ -14,7 +14,7 @@ keywords = [
name = "embassy-os"
readme = "README.md"
repository = "https://github.com/Start9Labs/embassy-os"
version = "0.3.0-rev.2"
version = "0.3.0-rev.3"
[lib]
name = "embassy"

View File

@@ -9,6 +9,9 @@ Environment=RUST_LOG=embassyd=debug,embassy=debug
ExecStart=/usr/local/bin/embassyd
Restart=always
RestartSec=3
ManagedOOMPreference=avoid
CPUAccounting=true
CPUWeight=1000
[Install]
WantedBy=multi-user.target

View File

@@ -5,7 +5,7 @@ use patch_db::{DbHandle, LockType};
use tracing::instrument;
use crate::context::RpcContext;
use crate::dependencies::{break_transitive, DependencyError};
use crate::dependencies::{break_transitive, heal_transitive, DependencyError};
use crate::s9pk::manifest::PackageId;
use crate::status::health_check::{HealthCheckId, HealthCheckResult};
use crate::status::MainStatus;
@@ -115,6 +115,8 @@ pub async fn check<Db: DbHandle>(
&mut BTreeMap::new(),
)
.await?;
} else {
heal_transitive(ctx, &mut tx, &dependent, id).await?;
}
}

View File

@@ -11,8 +11,9 @@ use crate::{Error, ResultExt};
mod v0_3_0;
mod v0_3_0_1;
mod v0_3_0_2;
mod v0_3_0_3;
pub type Current = v0_3_0_2::Version;
pub type Current = v0_3_0_3::Version;
#[derive(serde::Serialize, serde::Deserialize)]
#[serde(untagged)]
@@ -20,6 +21,7 @@ enum Version {
V0_3_0(Wrapper<v0_3_0::Version>),
V0_3_0_1(Wrapper<v0_3_0_1::Version>),
V0_3_0_2(Wrapper<v0_3_0_2::Version>),
V0_3_0_3(Wrapper<v0_3_0_3::Version>),
Other(emver::Version),
}
@@ -122,6 +124,7 @@ pub async fn init<Db: DbHandle>(db: &mut Db) -> Result<(), Error> {
Version::V0_3_0(v) => v.0.migrate_to(&Current::new(), db).await?,
Version::V0_3_0_1(v) => v.0.migrate_to(&Current::new(), db).await?,
Version::V0_3_0_2(v) => v.0.migrate_to(&Current::new(), db).await?,
Version::V0_3_0_3(v) => v.0.migrate_to(&Current::new(), db).await?,
Version::Other(_) => {
return Err(Error::new(
eyre!("Cannot downgrade"),

View File

@@ -1,13 +1,6 @@
use std::path::Path;
use emver::VersionRange;
use tokio::process::Command;
use super::*;
use crate::disk::quirks::{fetch_quirks, save_quirks, update_quirks};
use crate::disk::BOOT_RW_PATH;
use crate::update::query_mounted_label;
use crate::util::Invoke;
const V0_3_0_2: emver::Version = emver::Version::new(0, 3, 0, 2);

View File

@@ -0,0 +1,26 @@
use emver::VersionRange;
use super::*;
const V0_3_0_3: emver::Version = emver::Version::new(0, 3, 0, 3);
pub struct Version;
#[async_trait]
impl VersionT for Version {
type Previous = v0_3_0_2::Version;
fn new() -> Self {
Version
}
fn semver(&self) -> emver::Version {
V0_3_0_3
}
fn compat(&self) -> &'static VersionRange {
&*v0_3_0::V0_3_0_COMPAT
}
async fn up<Db: DbHandle>(&self, _db: &mut Db) -> Result<(), Error> {
Ok(())
}
async fn down<Db: DbHandle>(&self, _db: &mut Db) -> Result<(), Error> {
Ok(())
}
}

View File

@@ -0,0 +1,8 @@
[Unit]
Description=Slice that limits docker resources
Before=slices.target
[Slice]
MemoryAccounting=true
MemoryHigh=80%
MemoryMax=85%

View File

@@ -51,11 +51,12 @@ apt-get autoremove -y
apt-get upgrade -y
sed -i 's/Restart=on-failure/Restart=always/g' /lib/systemd/system/tor@default.service
sed -i 's/ExecStart=\/usr\/bin\/dockerd/ExecStart=\/usr\/bin\/dockerd --exec-opt native.cgroupdriver=systemd/g' /lib/systemd/system/docker.service
sed -i '/}/i \ \ \ \ application\/wasm \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ wasm;' /etc/nginx/mime.types
sed -i 's/# server_names_hash_bucket_size 64;/server_names_hash_bucket_size 128;/g' /etc/nginx/nginx.conf
# sed -i 's/ExecStart=\/sbin\/wpa_supplicant -u -s -O \/run\/wpa_supplicant/ExecStart=\/sbin\/wpa_supplicant -u -s -O \/run\/wpa_supplicant -c \/etc\/wpa_supplicant.conf -i wlan0/g' /lib/systemd/system/wpa_supplicant.service
sed -i 's/#allow-interfaces=eth0/allow-interfaces=eth0,wlan0/g' /etc/avahi/avahi-daemon.conf
echo "#" > /etc/network/interfaces
echo '{ "cgroup-parent": "docker-engine.slice" }' > /etc/docker/daemon.json
mkdir -p /etc/nginx/ssl
# fix to suppress docker warning, fixed in 21.xx release of docker cli: https://github.com/docker/cli/pull/2934

View File

@@ -1,12 +1,12 @@
{
"name": "embassy-os",
"version": "0.3.0.1",
"version": "0.3.0.3",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "embassy-os",
"version": "0.3.0.1",
"version": "0.3.0.3",
"dependencies": {
"@angular/animations": "^13.3.0",
"@angular/common": "^13.3.0",
@@ -3931,9 +3931,9 @@
}
},
"node_modules/async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"dependencies": {
"lodash": "^4.17.14"
@@ -17012,9 +17012,9 @@
"dev": true
},
"async": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
"version": "2.6.4",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz",
"integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==",
"dev": true,
"requires": {
"lodash": "^4.17.14"

View File

@@ -1,6 +1,6 @@
{
"name": "embassy-os",
"version": "0.3.0.2",
"version": "0.3.0.3",
"author": "Start9 Labs, Inc",
"homepage": "https://start9.com/",
"scripts": {

View File

@@ -3,7 +3,9 @@
<img alt="" [src]="'data:image/png;base64,' + pkg.icon | trustUrl" />
</ion-thumbnail>
<ion-label>
<h2 class="pkg-title">{{ pkg.manifest.title }}</h2>
<h2 class="montserrat">
<strong>{{ pkg.manifest.title }}</strong>
</h2>
<h3>{{ pkg.manifest.description.short }}</h3>
<ng-content></ng-content>
</ion-label>

View File

@@ -1,4 +0,0 @@
.pkg-title {
font-family: 'Montserrat', sans-serif;
font-weight: bold;
}

View File

@@ -5,7 +5,6 @@ import { MarketplacePkg } from '../../../types/marketplace-pkg'
@Component({
selector: 'marketplace-item',
templateUrl: 'item.component.html',
styleUrls: ['item.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ItemComponent {

View File

@@ -1,7 +1,7 @@
<ion-grid>
<ion-row>
<ion-col sizeXs="12" sizeMd="9">
<div class="header">
<ion-col sizeXs="12" sizeSm="12" sizeMd="9" sizeLg="9" sizeXl="9">
<div class="header montserrat">
<img
class="logo"
alt=""

View File

@@ -1,5 +1,4 @@
.header {
font-family: 'Montserrat', sans-serif;
padding: 2%;
}

View File

@@ -83,3 +83,7 @@ ion-modal::part(content) {
animation-fill-mode: forwards;
width: 1em;
}
.montserrat {
font-family: 'Montserrat', sans-serif!important;
}

View File

@@ -1,300 +1,21 @@
<ion-app>
<ion-content>
<ion-split-pane
contentId="main-content"
[disabled]="!showMenu"
(ionSplitPaneVisible)="splitPaneVisible($event)"
contentId="main-content"
>
<ion-menu contentId="main-content" type="overlay">
<ion-content color="light" scrollY="false">
<div style="text-align: center" class="ion-padding">
<img
style="width: 45%; cursor: pointer"
src="assets/img/logo.png"
(click)="goToWebsite()"
/>
</div>
<div class="divider"></div>
<ion-item-group style="padding: 30px 0px">
<ion-menu-toggle
auto-hide="false"
*ngFor="let page of appPages; let i = index"
>
<ion-item
style="padding-left: 10px"
color="transparent"
button
(click)="selectedIndex = i"
routerDirection="root"
[routerLink]="[page.url]"
lines="none"
detail="false"
*ngIf="
page.url !== '/developer' ||
(localStorageService.showDevTools$ | async)
"
>
<ion-icon
slot="start"
[name]="page.icon"
[class]="selectedIndex === i ? 'bold' : 'dim'"
></ion-icon>
<ion-label
style="font-family: 'Montserrat'"
[class]="selectedIndex === i ? 'bold' : 'dim'"
>
{{ page.title }}
</ion-label>
<ng-container *ngIf="page.url === '/embassy'">
<ion-icon
*ngIf="eosService.updateAvailable$ | async"
color="success"
size="small"
name="rocket-outline"
></ion-icon>
</ng-container>
<ion-badge
*ngIf="page.url === '/notifications' && unreadCount"
color="danger"
style="margin-right: 3%"
>{{ unreadCount }}</ion-badge
>
</ion-item>
</ion-menu-toggle>
</ion-item-group>
<img
(click)="openSnek()"
style="
cursor: pointer;
position: absolute;
bottom: 90px;
left: 20px;
width: 20px;
"
src="assets/img/icons/snek.png"
/>
<div
style="
text-align: center;
height: 75px;
position: absolute;
bottom: 0;
right: 0;
width: 100%;
"
>
<div class="divider" style="margin-bottom: 10px"></div>
<ion-menu-toggle auto-hide="false">
<ion-item
button
lines="none"
style="
--background: transparent;
margin-bottom: 86px;
text-align: center;
"
fill="clear"
(click)="presentAlertLogout()"
>
<ion-label
><ion-text style="font-family: 'Montserrat'" color="dark">
Log Out
</ion-text></ion-label
>
</ion-item>
</ion-menu-toggle>
</div>
<app-menu></app-menu>
</ion-content>
</ion-menu>
<ion-router-outlet id="main-content"></ion-router-outlet>
</ion-split-pane>
<section id="preload" style="display: none">
<!-- 3rd party components -->
<qr-code value="hello"></qr-code>
<!-- Ionicons -->
<ion-icon name="add"></ion-icon>
<ion-icon name="alert-outline"></ion-icon>
<ion-icon name="alert-circle-outline"></ion-icon>
<ion-icon name="aperture-outline"></ion-icon>
<ion-icon name="arrow-back"></ion-icon>
<ion-icon name="arrow-forward"></ion-icon>
<ion-icon name="arrow-up"></ion-icon>
<ion-icon name="briefcase-outline"></ion-icon>
<ion-icon name="bookmark-outline"></ion-icon>
<ion-icon name="cellular-outline"></ion-icon>
<ion-icon name="chatbubbles-outline"></ion-icon>
<ion-icon name="checkmark"></ion-icon>
<ion-icon name="chevron-down"></ion-icon>
<ion-icon name="chevron-up"></ion-icon>
<!-- needed for detail="true" on ion-item button -->
<ion-icon name="chevron-forward"></ion-icon>
<ion-icon name="close"></ion-icon>
<ion-icon name="cloud-outline"></ion-icon>
<ion-icon name="cloud-done-outline"></ion-icon>
<ion-icon name="cloud-download-outline"></ion-icon>
<ion-icon name="cloud-offline-outline"></ion-icon>
<ion-icon name="cloud-upload-outline"></ion-icon>
<ion-icon name="code-outline"></ion-icon>
<ion-icon name="cog-outline"></ion-icon>
<ion-icon name="color-wand-outline"></ion-icon>
<ion-icon name="construct-outline"></ion-icon>
<ion-icon name="copy-outline"></ion-icon>
<ion-icon name="cube-outline"></ion-icon>
<ion-icon name="desktop-outline"></ion-icon>
<ion-icon name="download-outline"></ion-icon>
<ion-icon name="earth-outline"></ion-icon>
<ion-icon name="ellipsis-horizontal-outline"></ion-icon>
<ion-icon name="eye-off-outline"></ion-icon>
<ion-icon name="eye-outline"></ion-icon>
<ion-icon name="file-tray-stacked-outline"></ion-icon>
<ion-icon name="finger-print-outline"></ion-icon>
<ion-icon name="flash-outline"></ion-icon>
<ion-icon name="folder-open-outline"></ion-icon>
<ion-icon name="grid-outline"></ion-icon>
<ion-icon name="help-circle-outline"></ion-icon>
<ion-icon name="hammer-outline"></ion-icon>
<ion-icon name="home-outline"></ion-icon>
<ion-icon name="information-circle-outline"></ion-icon>
<ion-icon name="key-outline"></ion-icon>
<ion-icon name="list-outline"></ion-icon>
<ion-icon name="lock-closed-outline"></ion-icon>
<ion-icon name="logo-bitcoin"></ion-icon>
<ion-icon name="mail-outline"></ion-icon>
<ion-icon name="map-outline"></ion-icon>
<ion-icon name="medkit-outline"></ion-icon>
<ion-icon name="newspaper-outline"></ion-icon>
<ion-icon name="notifications-outline"></ion-icon>
<ion-icon name="open-outline"></ion-icon>
<ion-icon name="options-outline"></ion-icon>
<ion-icon name="pencil"></ion-icon>
<ion-icon name="phone-portrait-outline"></ion-icon>
<ion-icon name="play-circle-outline"></ion-icon>
<ion-icon name="power"></ion-icon>
<ion-icon name="pulse"></ion-icon>
<ion-icon name="qr-code-outline"></ion-icon>
<ion-icon name="receipt-outline"></ion-icon>
<ion-icon name="refresh"></ion-icon>
<ion-icon name="reload"></ion-icon>
<ion-icon name="remove"></ion-icon>
<ion-icon name="remove-circle-outline"></ion-icon>
<ion-icon name="remove-outline"></ion-icon>
<ion-icon name="reorder-three"></ion-icon>
<ion-icon name="rocket-outline"></ion-icon>
<ion-icon name="save-outline"></ion-icon>
<ion-icon name="shield-checkmark-outline"></ion-icon>
<ion-icon name="stop-outline"></ion-icon>
<ion-icon name="storefront-outline"></ion-icon>
<ion-icon name="swap-vertical"></ion-icon>
<ion-icon name="terminal-outline"></ion-icon>
<ion-icon name="trash"></ion-icon>
<ion-icon name="trash-outline"></ion-icon>
<ion-icon name="warning-outline"></ion-icon>
<ion-icon name="wifi"></ion-icon>
<!-- Ionic components -->
<ion-action-sheet></ion-action-sheet>
<ion-alert></ion-alert>
<ion-back-button></ion-back-button>
<ion-badge></ion-badge>
<ion-button></ion-button>
<ion-buttons></ion-buttons>
<ion-card></ion-card>
<ion-card-content></ion-card-content>
<ion-card-header></ion-card-header>
<ion-checkbox></ion-checkbox>
<ion-content></ion-content>
<ion-footer></ion-footer>
<ion-grid></ion-grid>
<ion-header></ion-header>
<ion-popover></ion-popover>
<ion-content>
<ion-refresher slot="fixed"></ion-refresher>
<ion-refresher-content pullingContent="lines"></ion-refresher-content>
<ion-infinite-scroll></ion-infinite-scroll>
<ion-infinite-scroll-content
loadingSpinner="lines"
></ion-infinite-scroll-content>
</ion-content>
<ion-input></ion-input>
<ion-item></ion-item>
<ion-item-divider></ion-item-divider>
<ion-item-group></ion-item-group>
<ion-label></ion-label>
<ion-list></ion-list>
<ion-loading></ion-loading>
<ion-modal></ion-modal>
<ion-note></ion-note>
<ion-radio></ion-radio>
<ion-reorder></ion-reorder>
<ion-row></ion-row>
<ion-searchbar></ion-searchbar>
<ion-segment></ion-segment>
<ion-segment-button></ion-segment-button>
<ion-select></ion-select>
<ion-select-option></ion-select-option>
<ion-slides></ion-slides>
<ion-spinner name="lines"></ion-spinner>
<ion-text></ion-text>
<ion-text style="font-weight: bold">load bold</ion-text>
<ion-title></ion-title>
<ion-toast></ion-toast>
<ion-toggle></ion-toggle>
<ion-toolbar></ion-toolbar>
<ion-menu-button></ion-menu-button>
<!-- fonts -->
<p style="font-family: Montserrat">a</p>
<p style="font-family: Montserrat; font-weight: bold">a</p>
<p style="font-family: Montserrat; font-weight: 100">a</p>
<p style="font-family: Open Sans">a</p>
<p style="font-family: Open Sans; font-weight: bold">a</p>
<p style="font-family: Open Sans; font-weight: 100">a</p>
<!-- images -->
<img src="assets/img/logo.png" />
<img src="assets/img/icons/snek.png" />
<img src="assets/img/icons/wifi-1.png" />
<img src="assets/img/icons/wifi-2.png" />
<img src="assets/img/icons/wifi-3.png" />
</section>
<section appPreloader></section>
</ion-content>
<ion-footer
[ngStyle]="{
'max-height': osUpdateProgress ? '100px' : '0px',
overflow: 'hidden',
'transition-property': 'max-height',
'transition-duration': '1s',
'transition-delay': '.05s'
}"
>
<ion-toolbar
style="border-top: 1px solid var(--ion-color-dark)"
color="light"
>
<ion-list>
<ion-list-header>
<ion-label
>Downloading EOS:
{{
(
(100 * (osUpdateProgress?.downloaded || 1)) /
(osUpdateProgress?.size || 1)
).toFixed(0)
}}%</ion-label
>
</ion-list-header>
<div style="padding: 0 16px 16px 16px">
<ion-progress-bar
color="secondary"
[value]="
osUpdateProgress &&
osUpdateProgress.downloaded / osUpdateProgress.size
"
></ion-progress-bar>
</div>
</ion-list>
</ion-toolbar>
<ion-footer>
<footer appFooter></footer>
</ion-footer>
</ion-app>

View File

@@ -1,11 +1,7 @@
.bold {
font-weight: bold;
}
.dim {
color: var(--ion-color-dark-shade);
:host {
display: block;
}
ion-split-pane {
--side-max-width: 280px;
}
}

View File

@@ -1,5 +1,5 @@
import { Component, HostListener, Inject, NgZone } from '@angular/core'
import { Router, RoutesRecognized } from '@angular/router'
import { Component, HostListener, NgZone } from '@angular/core'
import { Router } from '@angular/router'
import {
AlertController,
IonicSafeString,
@@ -31,11 +31,10 @@ import {
ConnectionService,
} from './services/connection.service'
import { ConfigService } from './services/config.service'
import { ServerStatus, UIData } from 'src/app/services/patch-db/data-model'
import { UIData } from 'src/app/services/patch-db/data-model'
import { LocalStorageService } from './services/local-storage.service'
import { EOSService } from './services/eos.service'
import { OSWelcomePage } from './modals/os-welcome/os-welcome.page'
import { SnakePage } from './modals/snake/snake.page'
@Component({
selector: 'app-root',
@@ -43,83 +42,11 @@ import { SnakePage } from './modals/snake/snake.page'
styleUrls: ['app.component.scss'],
})
export class AppComponent {
code = {
s: false,
n: false,
e: false,
k: false,
unlocked: false,
}
@HostListener('document:keydown.enter', ['$event'])
@debounce()
handleKeyboardEvent() {
const elems = document.getElementsByClassName('enter-click')
const elem = elems[elems.length - 1] as HTMLButtonElement
if (!elem || elem.classList.contains('no-click') || elem.disabled) return
if (elem) elem.click()
}
@HostListener('document:keypress', ['$event'])
async keyPress(e: KeyboardEvent) {
if (e.repeat || this.code.unlocked) return
if (this.code[e.key] === false) {
this.code[e.key] = true
}
if (
Object.entries(this.code)
.filter(([key, value]) => key.length === 1)
.map(([key, value]) => value)
.reduce((a, b) => a && b)
) {
await this.openSnek()
}
}
@HostListener('document:keyup', ['$event'])
keyUp(e: KeyboardEvent) {
if (this.code[e.key]) {
this.code[e.key] = false
}
}
ServerStatus = ServerStatus
showMenu = false
selectedIndex = 0
offlineToast: HTMLIonToastElement
updateToast: HTMLIonToastElement
notificationToast: HTMLIonToastElement
serverName: string
unreadCount: number
subscriptions: Subscription[] = []
osUpdateProgress: { size: number; downloaded: number }
appPages = [
{
title: 'Services',
url: '/services',
icon: 'grid-outline',
},
{
title: 'Embassy',
url: '/embassy',
icon: 'cube-outline',
},
{
title: 'Marketplace',
url: '/marketplace',
icon: 'storefront-outline',
},
{
title: 'Notifications',
url: '/notifications',
icon: 'notifications-outline',
},
{
title: 'Developer Tools',
url: '/developer',
icon: 'hammer-outline',
},
]
constructor(
private readonly storage: Storage,
@@ -135,14 +62,29 @@ export class AppComponent {
private readonly errToast: ErrorToastService,
private readonly config: ConfigService,
private readonly zone: NgZone,
public readonly splitPane: SplitPaneTracker,
public readonly patch: PatchDbService,
public readonly localStorageService: LocalStorageService,
public readonly eosService: EOSService,
private readonly splitPane: SplitPaneTracker,
private readonly patch: PatchDbService,
private readonly localStorageService: LocalStorageService,
private readonly eosService: EOSService,
) {
this.init()
}
@HostListener('document:keydown.enter', ['$event'])
@debounce()
handleKeyboardEvent() {
const elems = document.getElementsByClassName('enter-click')
const elem = elems[elems.length - 1] as HTMLButtonElement
if (elem && !elem.classList.contains('no-click') && !elem.disabled) {
elem.click()
}
}
splitPaneVisible({ detail }: any) {
this.splitPane.sidebarOpen$.next(detail.visible)
}
async init() {
await this.storage.create()
await this.authService.init()
@@ -167,8 +109,6 @@ export class AppComponent {
...this.connectionService.start(),
// watch connection to display connectivity issues
this.watchConnection(),
// watch router to highlight selected menu item
this.watchRouter(),
])
this.patch
@@ -186,8 +126,6 @@ export class AppComponent {
this.subscriptions = this.subscriptions.concat([
// watch status to present toast for updated state
this.watchStatus(),
// watch update-progress to present progress bar when server is updating
this.watchUpdateProgress(),
// watch version to refresh browser window
this.watchVersion(),
// watch unread notification count to display toast
@@ -212,40 +150,6 @@ export class AppComponent {
})
}
async goToWebsite(): Promise<void> {
let url: string
if (this.config.isTor()) {
url =
'http://privacy34kn4ez3y3nijweec6w4g54i3g54sdv7r5mr6soma3w4begyd.onion'
} else {
url = 'https://start9.com'
}
window.open(url, '_blank', 'noreferrer')
}
async presentAlertLogout() {
const alert = await this.alertCtrl.create({
header: 'Caution',
message:
'Do you know your password? If you log out and forget your password, you may permanently lose access to your Embassy.',
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Logout',
cssClass: 'enter-click',
handler: () => {
this.logout()
},
},
],
})
await alert.present()
}
private checkForEosUpdate(ui: UIData): void {
if (ui['auto-check-updates'] !== false) {
this.eosService.getEOS()
@@ -271,48 +175,6 @@ export class AppComponent {
}
}
async openSnek() {
this.code.unlocked = true
const modal = await this.modalCtrl.create({
component: SnakePage,
cssClass: 'snake-modal',
backdropDismiss: false,
})
modal.onDidDismiss().then(async ret => {
this.code.unlocked = false
if (
ret.data.highScore &&
(ret.data.highScore >
this.patch.getData().ui.gaming?.snake?.['high-score'] ||
!this.patch.getData().ui.gaming?.snake?.['high-score'])
) {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
cssClass: 'loader',
message: 'Saving High Score...',
})
await loader.present()
try {
await this.embassyApi.setDbValue({
pointer: '/gaming',
value: { snake: { 'high-score': ret.data.highScore } },
})
} catch (e) {
this.errToast.present(e)
} finally {
this.loadingCtrl.dismiss()
}
}
})
modal.present()
}
// should wipe cache independant of actual BE logout
private async logout() {
this.embassyApi.logout({})
this.authService.setUnverified()
}
private watchConnection(): Subscription {
return this.connectionService
.watchFailure$()
@@ -344,17 +206,6 @@ export class AppComponent {
})
}
private watchRouter(): Subscription {
return this.router.events
.pipe(filter((e: RoutesRecognized) => !!e.urlAfterRedirects))
.subscribe(e => {
const appPageIndex = this.appPages.findIndex(appPage =>
e.urlAfterRedirects.startsWith(appPage.url),
)
if (appPageIndex > -1) this.selectedIndex = appPageIndex
})
}
private watchStatus(): Subscription {
return this.patch
.watch$('server-info', 'status-info', 'updated')
@@ -364,15 +215,6 @@ export class AppComponent {
}
})
}
m
private watchUpdateProgress(): Subscription {
return this.patch
.watch$('server-info', 'status-info', 'update-progress')
.subscribe(progress => {
this.osUpdateProgress = progress
})
}
private watchVersion(): Subscription {
return this.patch.watch$('server-info', 'version').subscribe(version => {
@@ -387,7 +229,6 @@ export class AppComponent {
return this.patch
.watch$('server-info', 'unread-notification-count')
.subscribe(count => {
this.unreadCount = count
if (previous !== undefined && count > previous)
this.presentToastNotifications()
previous = count
@@ -530,8 +371,4 @@ export class AppComponent {
loader.dismiss()
}
}
splitPaneVisible(e: any) {
this.splitPane.sidebarOpen$.next(e.detail.visible)
}
}

View File

@@ -1,5 +1,5 @@
import { NgModule, CUSTOM_ELEMENTS_SCHEMA, ErrorHandler } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'
import { NgModule, ErrorHandler } from '@angular/core'
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'
import { RouteReuseStrategy } from '@angular/router'
import { IonicModule, IonicRouteStrategy, IonNav } from '@ionic/angular'
import { Drivers } from '@ionic/storage'
@@ -10,7 +10,6 @@ import { AppRoutingModule } from './app-routing.module'
import { ApiService } from './services/api/embassy-api.service'
import { PatchDbServiceFactory } from './services/patch-db/patch-db.factory'
import { ConfigService } from './services/config.service'
import { QrCodeModule } from 'ng-qrcode'
import { OSWelcomePageModule } from './modals/os-welcome/os-welcome.module'
import { PatchDbService } from './services/patch-db/patch-db.service'
import { LocalStorageBootstrap } from './services/patch-db/local-storage-bootstrap'
@@ -27,6 +26,9 @@ import {
WorkspaceConfig,
} from '@start9labs/shared'
import { MarketplaceModule } from './marketplace.module'
import { PreloaderModule } from './app/preloader/preloader.module'
import { FooterModule } from './app/footer/footer.module'
import { MenuModule } from './app/menu/menu.module'
const { useMocks } = require('../../../../config.json') as WorkspaceConfig
@@ -35,7 +37,7 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
entryComponents: [],
imports: [
HttpClientModule,
BrowserModule,
BrowserAnimationsModule,
IonicModule.forRoot({
mode: 'md',
}),
@@ -46,7 +48,9 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
name: '_embassystorage',
driverOrder: [Drivers.LocalStorage, Drivers.IndexedDB],
}),
QrCodeModule,
MenuModule,
PreloaderModule,
FooterModule,
OSWelcomePageModule,
MarkdownModule,
GenericInputComponentModule,
@@ -82,6 +86,5 @@ const { useMocks } = require('../../../../config.json') as WorkspaceConfig
},
],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}

View File

@@ -0,0 +1,16 @@
<ion-toolbar
*ngIf="progress$ | async as progress"
color="light"
[@heightCollapse]="animation"
>
<ion-list class="list">
<ion-list-header>
<ion-label>Downloading EOS: {{ getProgress(progress) }}%</ion-label>
</ion-list-header>
<ion-progress-bar
class="progress"
color="secondary"
[value]="getProgress(progress) / 100"
></ion-progress-bar>
</ion-list>
</ion-toolbar>

View File

@@ -0,0 +1,9 @@
.list {
box-shadow: inset 0 1px var(--ion-color-dark);
box-sizing: border-box;
}
.progress {
width: auto;
margin: 0 16px 16px 16px;
}

View File

@@ -0,0 +1,36 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
import { heightCollapse } from '../../util/animations'
import { PatchDbService } from '../../services/patch-db/patch-db.service'
import { map } from 'rxjs/operators'
import { ServerInfo } from '../../services/patch-db/data-model'
@Component({
selector: 'footer[appFooter]',
templateUrl: 'footer.component.html',
styleUrls: ['footer.component.scss'],
animations: [heightCollapse],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FooterComponent {
readonly progress$ = this.patch
.watch$('server-info', 'status-info', 'update-progress')
.pipe(map(a => a && { ...a }))
readonly animation = {
value: '',
params: {
duration: 1000,
delay: 50,
},
}
constructor(private readonly patch: PatchDbService) {}
getProgress({
downloaded,
size,
}: ServerInfo['status-info']['update-progress']): number {
return Math.round((100 * (downloaded || 1)) / (size || 1))
}
}

View File

@@ -0,0 +1,12 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { IonicModule } from '@ionic/angular'
import { FooterComponent } from './footer.component'
@NgModule({
imports: [CommonModule, IonicModule],
declarations: [FooterComponent],
exports: [FooterComponent],
})
export class FooterModule {}

View File

@@ -0,0 +1,60 @@
<a class="logo ion-padding" target="_blank" rel="noreferrer" [href]="href">
<img alt="Start9" src="assets/img/logo.png" />
</a>
<div class="divider"></div>
<ion-item-group class="menu">
<ion-menu-toggle *ngFor="let page of pages; let i = index" auto-hide="false">
<ion-item
*ngIf="
page.url !== '/developer' || (localStorageService.showDevTools$ | async)
"
button
class="link"
color="transparent"
routerDirection="root"
lines="none"
detail="false"
[routerLink]="page.url"
>
<ion-icon
slot="start"
class="icon label"
routerLinkActive="label_selected"
[name]="page.icon"
></ion-icon>
<ion-label class="label montserrat" routerLinkActive="label_selected">
{{ page.title }}
</ion-label>
<ion-icon
*ngIf="page.url === '/embassy' && eosService.updateAvailable$ | async"
color="success"
size="small"
name="rocket-outline"
></ion-icon>
<ion-badge
*ngIf="page.url === '/notifications' && notification$ | async as count"
color="danger"
class="badge"
>
{{ count }}
</ion-badge>
</ion-item>
</ion-menu-toggle>
</ion-item-group>
<img appSnek class="snek" alt="Play Snek" src="assets/img/icons/snek.png" />
<div class="bottom">
<div class="divider" style="margin-bottom: 10px"></div>
<ion-menu-toggle auto-hide="false">
<ion-item
button
lines="none"
style="--background: transparent; margin-bottom: 86px; text-align: center"
fill="clear"
(click)="presentAlertLogout()"
>
<ion-label>
<ion-text class="montserrat" color="dark"> Log Out </ion-text>
</ion-label>
</ion-item>
</ion-menu-toggle>
</div>

View File

@@ -0,0 +1,47 @@
:host {
display: block;
}
.logo {
display: block;
width: 50%;
margin: 0 auto;
}
.menu {
padding: 30px 0;
}
.icon {
margin-left: 10px;
}
.label {
color: var(--ion-color-dark-shade);
&_selected {
color: #fff;
font-weight: bold;
}
}
.badge {
margin-right: 3%;
}
.snek {
position: absolute;
bottom: 90px;
left: 20px;
width: 20px;
cursor: pointer;
}
.bottom {
position: absolute;
bottom: 0;
right: 0;
width: 100%;
height: 75px;
text-align: center;
}

View File

@@ -0,0 +1,93 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
import { AlertController } from '@ionic/angular'
import { ConfigService } from '../../services/config.service'
import { LocalStorageService } from '../../services/local-storage.service'
import { EOSService } from '../../services/eos.service'
import { ApiService } from '../../services/api/embassy-api.service'
import { AuthService } from '../../services/auth.service'
import { PatchDbService } from '../../services/patch-db/patch-db.service'
@Component({
selector: 'app-menu',
templateUrl: 'menu.component.html',
styleUrls: ['menu.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class MenuComponent {
readonly pages = [
{
title: 'Services',
url: '/services',
icon: 'grid-outline',
},
{
title: 'Embassy',
url: '/embassy',
icon: 'cube-outline',
},
{
title: 'Marketplace',
url: '/marketplace',
icon: 'storefront-outline',
},
{
title: 'Notifications',
url: '/notifications',
icon: 'notifications-outline',
},
{
title: 'Developer Tools',
url: '/developer',
icon: 'hammer-outline',
},
]
readonly notification$ = this.patch.watch$(
'server-info',
'unread-notification-count',
)
constructor(
private readonly config: ConfigService,
private readonly alertCtrl: AlertController,
private readonly embassyApi: ApiService,
private readonly authService: AuthService,
private readonly patch: PatchDbService,
public readonly localStorageService: LocalStorageService,
public readonly eosService: EOSService,
) {}
get href(): string {
return this.config.isTor()
? 'http://privacy34kn4ez3y3nijweec6w4g54i3g54sdv7r5mr6soma3w4begyd.onion'
: 'https://start9.com'
}
async presentAlertLogout() {
const alert = await this.alertCtrl.create({
header: 'Caution',
message:
'Do you know your password? If you log out and forget your password, you may permanently lose access to your Embassy.',
buttons: [
{
text: 'Cancel',
role: 'cancel',
},
{
text: 'Logout',
cssClass: 'enter-click',
handler: () => this.logout(),
},
],
})
await alert.present()
}
// should wipe cache independent of actual BE logout
private logout() {
this.embassyApi.logout({})
this.authService.setUnverified()
}
}

View File

@@ -0,0 +1,14 @@
import { CommonModule } from '@angular/common'
import { NgModule } from '@angular/core'
import { RouterModule } from '@angular/router'
import { IonicModule } from '@ionic/angular'
import { MenuComponent } from './menu.component'
import { SnekModule } from '../snek/snek.module'
@NgModule({
imports: [CommonModule, IonicModule, RouterModule, SnekModule],
declarations: [MenuComponent],
exports: [MenuComponent],
})
export class MenuModule {}

View File

@@ -0,0 +1,71 @@
<!-- Ionicons -->
<ion-icon *ngFor="let icon of icons" [name]="icon"></ion-icon>
<!-- 3rd party components -->
<qr-code value="hello"></qr-code>
<!-- Ionic components -->
<ion-action-sheet></ion-action-sheet>
<ion-alert></ion-alert>
<ion-back-button></ion-back-button>
<ion-badge></ion-badge>
<ion-button></ion-button>
<ion-buttons></ion-buttons>
<ion-card></ion-card>
<ion-card-content></ion-card-content>
<ion-card-header></ion-card-header>
<ion-checkbox></ion-checkbox>
<ion-content></ion-content>
<ion-footer></ion-footer>
<ion-grid></ion-grid>
<ion-header></ion-header>
<ion-popover></ion-popover>
<ion-content>
<ion-refresher slot="fixed"></ion-refresher>
<ion-refresher-content pullingContent="lines"></ion-refresher-content>
<ion-infinite-scroll></ion-infinite-scroll>
<ion-infinite-scroll-content
loadingSpinner="lines"
></ion-infinite-scroll-content>
</ion-content>
<ion-input></ion-input>
<ion-item></ion-item>
<ion-item-divider></ion-item-divider>
<ion-item-group></ion-item-group>
<ion-label></ion-label>
<ion-list></ion-list>
<ion-loading></ion-loading>
<ion-modal></ion-modal>
<ion-note></ion-note>
<ion-radio></ion-radio>
<ion-reorder></ion-reorder>
<ion-row></ion-row>
<ion-searchbar></ion-searchbar>
<ion-segment></ion-segment>
<ion-segment-button></ion-segment-button>
<ion-select></ion-select>
<ion-select-option></ion-select-option>
<ion-slides></ion-slides>
<ion-spinner name="lines"></ion-spinner>
<ion-text></ion-text>
<ion-text><strong>load bold font</strong></ion-text>
<ion-title></ion-title>
<ion-toast></ion-toast>
<ion-toggle></ion-toggle>
<ion-toolbar></ion-toolbar>
<ion-menu-button></ion-menu-button>
<!-- fonts -->
<p style="font-family: Montserrat">a</p>
<p style="font-family: Montserrat; font-weight: bold">a</p>
<p style="font-family: Montserrat; font-weight: 100">a</p>
<p style="font-family: Open Sans">a</p>
<p style="font-family: Open Sans; font-weight: bold">a</p>
<p style="font-family: Open Sans; font-weight: 100">a</p>
<!-- images -->
<img src="assets/img/logo.png" />
<img src="assets/img/icons/snek.png" />
<img src="assets/img/icons/wifi-1.png" />
<img src="assets/img/icons/wifi-2.png" />
<img src="assets/img/icons/wifi-3.png" />

View File

@@ -0,0 +1,91 @@
import { ChangeDetectionStrategy, Component } from '@angular/core'
// TODO: Turn into DI token if this is needed someplace else too
const ICONS = [
'add',
'alert-outline',
'alert-circle-outline',
'aperture-outline',
'arrow-back',
'arrow-up',
'briefcase-outline',
'bookmark-outline',
'cellular-outline',
'chatbubbles-outline',
'checkmark',
'chevron-down',
'chevron-up',
'chevron-forward',
'close',
'cloud-outline',
'cloud-done-outline',
'cloud-download-outline',
'cloud-offline-outline',
'cloud-upload-outline',
'code-outline',
'cog-outline',
'color-wand-outline',
'construct-outline',
'copy-outline',
'cube-outline',
'desktop-outline',
'download-outline',
'earth-outline',
'ellipsis-horizontal-outline',
'eye-off-outline',
'eye-outline',
'file-tray-stacked-outline',
'finger-print-outline',
'flash-outline',
'folder-open-outline',
'grid-outline',
'help-circle-outline',
'hammer-outline',
'home-outline',
'information-circle-outline',
'key-outline',
'list-outline',
'lock-closed-outline',
'logo-bitcoin',
'mail-outline',
'map-outline',
'medkit-outline',
'newspaper-outline',
'notifications-outline',
'open-outline',
'options-outline',
'pencil',
'phone-portrait-outline',
'play-circle-outline',
'power',
'pulse',
'qr-code-outline',
'receipt-outline',
'refresh',
'reload',
'remove',
'remove-circle-outline',
'remove-outline',
'reorder-three',
'rocket-outline',
'save-outline',
'shield-checkmark-outline',
'stop-outline',
'storefront-outline',
'swap-vertical',
'terminal-outline',
'trash',
'trash-outline',
'warning-outline',
'wifi',
]
@Component({
selector: 'section[appPreloader]',
templateUrl: 'preloader.component.html',
styles: [':host { display: none }'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class PreloaderComponent {
readonly icons = ICONS
}

View File

@@ -0,0 +1,14 @@
import { CommonModule } from '@angular/common'
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core'
import { IonicModule } from '@ionic/angular'
import { QrCodeModule } from 'ng-qrcode'
import { PreloaderComponent } from './preloader.component'
@NgModule({
imports: [CommonModule, IonicModule, QrCodeModule],
declarations: [PreloaderComponent],
exports: [PreloaderComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class PreloaderModule {}

View File

@@ -0,0 +1,57 @@
import { Directive, HostListener } from '@angular/core'
import { LoadingController, ModalController } from '@ionic/angular'
import { ErrorToastService } from '@start9labs/shared'
import { SnakePage } from '../../modals/snake/snake.page'
import { PatchDbService } from '../../services/patch-db/patch-db.service'
import { ApiService } from '../../services/api/embassy-api.service'
@Directive({
selector: 'img[appSnek]',
})
export class SnekDirective {
constructor(
private readonly modalCtrl: ModalController,
private readonly loadingCtrl: LoadingController,
private readonly errToast: ErrorToastService,
private readonly embassyApi: ApiService,
private readonly patch: PatchDbService,
) {}
@HostListener('click')
async onClick() {
const modal = await this.modalCtrl.create({
component: SnakePage,
cssClass: 'snake-modal',
backdropDismiss: false,
})
modal.onDidDismiss().then(async ({ data }) => {
const highScore =
this.patch.getData().ui.gaming?.snake?.['high-score'] || 0
if (data?.highScore > highScore) {
const loader = await this.loadingCtrl.create({
spinner: 'lines',
cssClass: 'loader',
message: 'Saving High Score...',
})
await loader.present()
try {
await this.embassyApi.setDbValue({
pointer: '/gaming',
value: { snake: { 'high-score': data.highScore } },
})
} catch (e) {
this.errToast.present(e)
} finally {
this.loadingCtrl.dismiss()
}
}
})
modal.present()
}
}

View File

@@ -0,0 +1,11 @@
import { NgModule } from '@angular/core'
import { SnakePageModule } from '../../modals/snake/snake.module'
import { SnekDirective } from './snek.directive'
@NgModule({
imports: [SnakePageModule],
declarations: [SnekDirective],
exports: [SnekDirective],
})
export class SnekModule {}

View File

@@ -1,6 +1,6 @@
<ion-header>
<ion-toolbar>
<ion-title>{{ version }} Release Notes</ion-title>
<ion-title>Release Notes</ion-title>
<ion-buttons slot="end">
<ion-button (click)="dismiss()">
<ion-icon slot="icon-only" name="close"></ion-icon>
@@ -10,13 +10,14 @@
</ion-header>
<ion-content class="ion-padding">
<p class="note-padding">Minor compatibility fix.</p>
<h5>Previous releases:</h5>
<h6>0.3.0.1 Release Notes</h6>
<p>Minor bugfixes and performance improvements.</p>
<p></p>
<h6>0.3.0 Release Notes</h6>
<h4>This release:</h4>
<p class="section-subheader">{{ version }}</p>
<p class="note-padding">Bugfixes and minor performance updates.</p>
<h4>Previous releases:</h4>
<p class="section-subheader">0.3.0.2</p>
<p class="note-padding">Minor bugfixes and performance improvements.</p>
<p></p>
<p class="section-subheader">0.3.0</p>
<p>
Check out the full
<a

View File

@@ -18,4 +18,12 @@
.note-padding {
padding-bottom: 12px;
}
.section-subheader {
font-weight: bold;
}
h4 {
font-style: italic;
}

View File

@@ -1,11 +1,12 @@
import { NgModule } from '@angular/core'
import { CommonModule } from '@angular/common'
import { IonicModule } from '@ionic/angular'
import { SnakePage } from './snake.page'
@NgModule({
declarations: [SnakePage],
imports: [CommonModule, IonicModule],
declarations: [SnakePage],
exports: [SnakePage],
})
export class SnakePageModule {}

View File

@@ -5,7 +5,7 @@
<img [src]="dep.icon" alt="" />
</ion-thumbnail>
<ion-label>
<h2 class="inline">
<h2 class="montserrat">
<ion-icon
*ngIf="!!dep.errorText"
class="icon"

View File

@@ -1,7 +1,3 @@
.inline {
font-family: 'Montserrat', sans-serif;
}
.icon {
padding-right: 4px;
}

View File

@@ -8,7 +8,10 @@
<img [src]="pkg['static-files'].icon" alt="" />
</ion-avatar>
<ion-label>
<h1 class="name" [class.less-large]="pkg.manifest.title.length > 20">
<h1
class="montserrat"
[class.less-large]="pkg.manifest.title.length > 20"
>
{{ pkg.manifest.title }}
</h1>
<h2>{{ pkg.manifest.version | displayEmver }}</h2>

View File

@@ -1,7 +1,3 @@
.name {
font-family: 'Montserrat', sans-serif;
}
.less-large {
font-size: 18px !important;
}

View File

@@ -1,4 +1,4 @@
<h1 class="heading ion-text-center">{{ name }}</h1>
<h1 class="heading montserrat ion-text-center">{{ name }}</h1>
<marketplace-search [(query)]="query"></marketplace-search>

View File

@@ -1,5 +1,4 @@
.heading {
font-family: 'Montserrat', sans-serif;
font-size: 42px;
margin: 32px 0;
}

View File

@@ -1,5 +1,5 @@
import { Component } from '@angular/core'
import { defer, Observable } from 'rxjs'
import { Observable } from 'rxjs'
import { filter, first, map, startWith, switchMapTo, tap } from 'rxjs/operators'
import { exists, isEmptyObject } from '@start9labs/shared'
import {
@@ -16,14 +16,13 @@ import { spreadProgress } from '../utils/spread-progress'
templateUrl: './marketplace-list.page.html',
})
export class MarketplaceListPage {
readonly localPkgs$: Observable<Record<string, PackageDataEntry>> = defer(
() => this.patch.watch$('package-data'),
).pipe(
filter(data => exists(data) && !isEmptyObject(data)),
tap(pkgs => Object.values(pkgs).forEach(spreadProgress)),
map(pkgs => ({ ...pkgs })),
startWith({}),
)
readonly localPkgs$: Observable<Record<string, PackageDataEntry>> = this.patch
.watch$('package-data')
.pipe(
filter(data => exists(data) && !isEmptyObject(data)),
tap(pkgs => Object.values(pkgs).forEach(spreadProgress)),
startWith({}),
)
readonly categories$ = this.marketplaceService
.getCategories()
@@ -31,13 +30,13 @@ export class MarketplaceListPage {
map(categories => new Set(['featured', 'updates', ...categories, 'all'])),
)
readonly pkgs$: Observable<MarketplacePkg[]> = defer(() =>
this.patch.watch$('server-info'),
).pipe(
filter(data => exists(data) && !isEmptyObject(data)),
first(),
switchMapTo(this.marketplaceService.getPackages()),
)
readonly pkgs$: Observable<MarketplacePkg[]> = this.patch
.watch$('server-info')
.pipe(
filter(data => exists(data) && !isEmptyObject(data)),
first(),
switchMapTo(this.marketplaceService.getPackages()),
)
readonly name$: Observable<string> = this.marketplaceService
.getMarketplace()

View File

@@ -2,7 +2,7 @@
<ion-item *ngIf="dependentInfo" lines="none" class="rec-item">
<ion-label>
<h2 class="heading">
<ion-text class="title">
<ion-text class="montserrat title">
{{ title }}
</ion-text>
</h2>

View File

@@ -5,7 +5,6 @@
.title {
margin: 5px;
font-family: 'Montserrat', sans-serif;
font-size: 18px;
}

View File

@@ -6,7 +6,6 @@ import { ServerShowPage } from './server-show.page'
import { FormsModule } from '@angular/forms'
import { TextSpinnerComponentModule } from '@start9labs/shared'
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
import { SnakePageModule } from 'src/app/modals/snake/snake.module'
const routes: Routes = [
{
@@ -23,7 +22,6 @@ const routes: Routes = [
RouterModule.forChild(routes),
TextSpinnerComponentModule,
BadgeMenuComponentModule,
SnakePageModule,
],
declarations: [ServerShowPage],
})

View File

@@ -387,7 +387,7 @@ export class ServerShowPage {
},
{
title: 'Kernel Logs',
description: 'Diagnostic log stream for device drivers',
description: 'Diagnostic log stream for device drivers and other kernel processes',
icon: 'receipt-outline',
action: () =>
this.navCtrl.navigateForward(['kernel-logs'], {

View File

@@ -20,7 +20,7 @@ export const mockPatchData: DataModel = {
},
'server-info': {
id: 'embassy-abcdefgh',
version: '0.3.0.1',
version: '0.3.0.3',
'last-backup': null,
'lan-address': 'https://embassy-abcdefgh.local',
'tor-address': 'http://myveryownspecialtoraddress.onion',

View File

@@ -7,6 +7,9 @@ import {
debounceTime,
finalize,
mergeMap,
skip,
switchMap,
take,
tap,
withLatestFrom,
} from 'rxjs/operators'
@@ -14,6 +17,7 @@ import { isEmptyObject, pauseFor } from '@start9labs/shared'
import { DataModel } from './data-model'
import { ApiService } from '../api/embassy-api.service'
import { AuthService } from '../auth.service'
import { patch } from '@start9labs/emver'
export const PATCH_HTTP = new InjectionToken<Source<DataModel>>('')
export const PATCH_SOURCE = new InjectionToken<Source<DataModel>>('')
@@ -176,9 +180,19 @@ export class PatchDbService {
// prettier-ignore
watch$: Store<DataModel>['watch$'] = (...args: (string | number)[]): Observable<DataModel> => {
// TODO: refactor with a better solution to race condition
const argsString = '/' + args.join('/')
const source$ =
this.patchDb?.store.watch$(...(args as [])) ||
this.patchConnection$.pipe(
skip(1),
take(1),
switchMap(() => this.patchDb.store.watch$(...(args as []))),
)
console.log('patchDB: WATCHING ', argsString)
return this.patchDb.store.watch$(...(args as [])).pipe(
return source$.pipe(
tap(data => console.log('patchDB: NEW VALUE', argsString, data)),
catchError(e => {
console.error('patchDB: WATCH ERROR', e)

View File

@@ -0,0 +1,17 @@
import { animate, style, transition, trigger } from '@angular/animations'
const TRANSITION = '{{duration}}ms {{delay}}ms ease-in-out'
const DURATION = { params: { duration: 300, delay: 0 } }
export const heightCollapse = trigger('heightCollapse', [
transition(
':enter',
[style({ height: 0 }), animate(TRANSITION, style({ height: '*' }))],
DURATION,
),
transition(
':leave',
[style({ height: '*' }), animate(TRANSITION, style({ height: 0 }))],
DURATION,
),
])

View File

@@ -10,6 +10,6 @@
</head>
<body>
<h2 style="color: #e0e0e0; text-align: center; padding-top: 20px;">EmbassyOS is initializing...</h2>
<p style="color: #e0e0e0; text-align: center;">Please wait a few seconds and refresh the page.</p>
<p style="color: #e0e0e0; text-align: center;">This process can take up to several minutes to complete. Please wait to refresh the page.</p>
</body>
</html>
</html>

View File

@@ -865,7 +865,7 @@ dependencies = [
[[package]]
name = "embassy-os"
version = "0.3.0"
version = "0.3.0-rev.3"
dependencies = [
"aes",
"async-trait",