mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
Update/misc UI fixes (#1961)
* fix login error message spacing and ensure not longer than 64 chars * spinner color to tertiary * totally responsive homepage cards * copy changes, back button for marketplace, minor styling * center setup wizard tiles; adjust external link style * remove cert note from setup success * convert launch card to go to login button * change system settings to system; update icons * refactor card widget input as full card details; more card resizing for home page * cleanup * clean up widget params * delete contructor Co-authored-by: Matt Hill <matthewonthemoon@gmail.com>
This commit is contained in:
@@ -49,7 +49,6 @@
|
||||
[ngModelOptions]="{'standalone': true}"
|
||||
[type]="!unmasked2 ? 'password' : 'text'"
|
||||
(ionChange)="checkVer()"
|
||||
maxlength="64"
|
||||
placeholder="Retype Password"
|
||||
></ion-input>
|
||||
<ion-button
|
||||
|
||||
@@ -63,6 +63,8 @@ export class PasswordPage {
|
||||
|
||||
if (this.password.length < 12) {
|
||||
this.pwError = 'Must be 12 characters or greater'
|
||||
} else if (this.password.length > 64) {
|
||||
this.pwError = 'Must be less than 65 characters'
|
||||
} else {
|
||||
this.pwError = ''
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ion-content>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col>
|
||||
<ion-card color="dark">
|
||||
<ion-card-header class="ion-text-center">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ion-content>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col class="ion-text-center">
|
||||
<ion-card color="dark">
|
||||
<ion-card-header
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ion-content>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col class="ion-text-center">
|
||||
<div style="padding-bottom: 32px">
|
||||
<img
|
||||
@@ -9,7 +9,6 @@
|
||||
style="max-width: 220px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ion-card *ngIf="!loading" color="dark">
|
||||
<ion-card-header>
|
||||
<ion-button
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ion-content>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col class="ion-text-center">
|
||||
<ion-card color="dark">
|
||||
<ion-card-header>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ion-content>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col>
|
||||
<ion-card color="dark">
|
||||
<ion-card-header class="ion-text-center">
|
||||
|
||||
@@ -1,32 +1,13 @@
|
||||
<canvas #canvas> Your browser does not support the canvas element. </canvas>
|
||||
<canvas #canvas>Your browser does not support the canvas element.</canvas>
|
||||
<ion-content>
|
||||
<div class="grid-center-wrapper">
|
||||
<ion-grid [fixed]="true">
|
||||
<!-- kiosk mode -->
|
||||
<ng-container *ngIf="isKiosk; else notKiosk">
|
||||
<ion-card>
|
||||
<ion-row>
|
||||
<ion-col size-xs="12" class="ion-text-center">
|
||||
<div class="inline">
|
||||
<ion-icon
|
||||
name="checkmark-circle-outline"
|
||||
color="success"
|
||||
></ion-icon>
|
||||
<h1>Setup Complete!</h1>
|
||||
</div>
|
||||
<h3 class="ion-padding-top">
|
||||
You will be redirected momentarily.
|
||||
</h3>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-card>
|
||||
</ng-container>
|
||||
<!-- not kiosk -->
|
||||
<ng-template #notKiosk>
|
||||
<ion-card>
|
||||
<ion-row>
|
||||
<ion-col size-xs="12" class="ion-text-center">
|
||||
<div style="margin-bottom: 4rem">
|
||||
<ion-grid>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col>
|
||||
<!-- kiosk mode -->
|
||||
<ng-container *ngIf="isKiosk; else notKiosk">
|
||||
<ion-card>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col size-xs="12" class="ion-text-center">
|
||||
<div class="inline">
|
||||
<ion-icon
|
||||
name="checkmark-circle-outline"
|
||||
@@ -34,199 +15,215 @@
|
||||
></ion-icon>
|
||||
<h1>Setup Complete!</h1>
|
||||
</div>
|
||||
<h3 *ngIf="recoverySource && recoverySource.type === 'backup'">
|
||||
You can now safely unplug your backup drive
|
||||
<h3 class="ion-padding-top">
|
||||
You will be redirected momentarily.
|
||||
</h3>
|
||||
<h3 *ngIf="recoverySource && recoverySource.type === 'migrate'">
|
||||
You can now safely unplug your old drive
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-container">
|
||||
<ion-card id="information" (click)="download()">
|
||||
<ion-card-content>
|
||||
<ion-card-title
|
||||
>Download your Embassy's address information for permanent
|
||||
access</ion-card-title
|
||||
>
|
||||
<p>
|
||||
<code>embassy.local</code> was for setup only. It will no
|
||||
longer work.
|
||||
</p>
|
||||
</ion-card-content>
|
||||
<ion-footer>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-card>
|
||||
</ng-container>
|
||||
<!-- not kiosk -->
|
||||
<ng-template #notKiosk>
|
||||
<ion-card>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col size-xs="12" class="ion-text-center">
|
||||
<div style="margin-bottom: 4rem">
|
||||
<div class="inline">
|
||||
<ion-icon
|
||||
name="checkmark-circle-outline"
|
||||
color="success"
|
||||
></ion-icon>
|
||||
<h1>Setup Complete!</h1>
|
||||
</div>
|
||||
<h3
|
||||
*ngIf="recoverySource && recoverySource.type === 'backup'"
|
||||
>
|
||||
You can now safely unplug your backup drive
|
||||
</h3>
|
||||
<h3
|
||||
*ngIf="recoverySource && recoverySource.type === 'migrate'"
|
||||
>
|
||||
You can now safely unplug your old drive
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-container">
|
||||
<ion-card id="information" (click)="download()">
|
||||
<ion-card-content>
|
||||
<ion-card-title
|
||||
>Download permanent address info</ion-card-title
|
||||
>
|
||||
<p>
|
||||
embassy.local was for setup purposes only. It will no
|
||||
longer work.
|
||||
</p>
|
||||
</ion-card-content>
|
||||
<ion-footer>
|
||||
<div class="container">
|
||||
<div class="inline">
|
||||
<p>Download</p>
|
||||
<ion-icon name="download-outline"></ion-icon>
|
||||
</div>
|
||||
</div>
|
||||
</ion-footer>
|
||||
</ion-card>
|
||||
<ion-card
|
||||
[disabled]="disableLogin"
|
||||
id="launch"
|
||||
href="{{ lanAddress }}"
|
||||
target="_blank"
|
||||
>
|
||||
<div class="container">
|
||||
<div class="inline">
|
||||
<p>Download</p>
|
||||
<ion-icon name="download-outline"></ion-icon>
|
||||
<p>Go to Embassy login</p>
|
||||
<ion-icon name="open-outline"></ion-icon>
|
||||
</div>
|
||||
</div>
|
||||
</ion-footer>
|
||||
</ion-card>
|
||||
<ion-card id="launch" href="{{ lanAddress }}" target="_blank">
|
||||
<ion-card-content>
|
||||
<ion-card-title
|
||||
>Launch to login to your Embassy</ion-card-title
|
||||
>
|
||||
<p class="ion-padding-bottom">
|
||||
<span class="emphasis-warn">Important!</span><br />
|
||||
Your browser will warn you that the page is untrusted. You
|
||||
can safely bypass this warning. It will go away after you
|
||||
</ion-card>
|
||||
</div>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<!-- cert elem -->
|
||||
<a hidden id="install-cert" download="embassy.crt"></a>
|
||||
<!-- download elem -->
|
||||
<div hidden id="downloadable">
|
||||
<div
|
||||
style="
|
||||
font-family: Montserrat;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: auto;
|
||||
width: clamp(900px, 35vw, 600px);
|
||||
"
|
||||
>
|
||||
<h1
|
||||
style="
|
||||
font-variant-caps: all-small-caps;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
"
|
||||
>
|
||||
Embassy Address Info
|
||||
</h1>
|
||||
|
||||
<section
|
||||
style="
|
||||
padding: 1rem 3rem 2rem 3rem;
|
||||
border: solid #c4c4c5 3px;
|
||||
margin-bottom: 24px;
|
||||
"
|
||||
>
|
||||
<h2 style="font-variant-caps: all-small-caps">
|
||||
Access from home (LAN)
|
||||
</h2>
|
||||
<p>
|
||||
Visit the address below when you are connected to the same
|
||||
WiFi or Local Area Network (LAN) as your Embassy:
|
||||
</p>
|
||||
<p
|
||||
style="
|
||||
padding: 16px;
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
overflow: auto;
|
||||
"
|
||||
>
|
||||
<code id="lan-addr"></code>
|
||||
</p>
|
||||
<div>
|
||||
<h3 style="color: #f8546a; font-weight: bold">
|
||||
Important!
|
||||
</h3>
|
||||
<p>
|
||||
Be sure to
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-lan"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style="
|
||||
color: #6866cc;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
"
|
||||
>
|
||||
setup a secure connection </a
|
||||
>.
|
||||
follow the instructions
|
||||
</a>
|
||||
to establish a secure connection by installing your
|
||||
Embassy's certificate.
|
||||
</p>
|
||||
</ion-card-content>
|
||||
<ion-footer>
|
||||
<div class="container">
|
||||
<div class="inline">
|
||||
<p>Launch</p>
|
||||
<ion-icon name="rocket-outline"></ion-icon>
|
||||
</div>
|
||||
</div>
|
||||
</ion-footer>
|
||||
</ion-card>
|
||||
</div>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
<!-- cert elem -->
|
||||
<a hidden id="install-cert" download="embassy.crt"></a>
|
||||
<!-- download elem -->
|
||||
<div hidden id="downloadable">
|
||||
<div
|
||||
style="
|
||||
font-family: Montserrat;
|
||||
color: #333333;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: auto;
|
||||
width: clamp(900px, 35vw, 600px);
|
||||
"
|
||||
>
|
||||
<h1
|
||||
style="
|
||||
font-variant-caps: all-small-caps;
|
||||
text-align: center;
|
||||
padding: 1rem;
|
||||
"
|
||||
>
|
||||
Embassy Address Info
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<section
|
||||
style="
|
||||
padding: 1rem 3rem 2rem 3rem;
|
||||
border: solid #c4c4c5 3px;
|
||||
margin-bottom: 24px;
|
||||
"
|
||||
>
|
||||
<h2 style="font-variant-caps: all-small-caps">
|
||||
Access from home (LAN)
|
||||
</h2>
|
||||
<p>
|
||||
Visit the address below when you are connected to the same
|
||||
WiFi or Local Area Network (LAN) as your Embassy:
|
||||
</p>
|
||||
<p
|
||||
style="
|
||||
padding: 16px;
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
overflow: auto;
|
||||
"
|
||||
>
|
||||
<code id="lan-addr"></code>
|
||||
</p>
|
||||
<div>
|
||||
<h3 style="color: #f8546a; font-weight: bold">Important!</h3>
|
||||
<p>
|
||||
Your browser will warn you that the page is untrusted. You
|
||||
can safely bypass this warning. It will go away after you
|
||||
<div style="padding: 2rem; text-align: center">
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-lan"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
id="cert"
|
||||
download="embassy.crt"
|
||||
style="
|
||||
color: #6866cc;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
padding: 1em 1.2em;
|
||||
box-sizing: border-box;
|
||||
font-size: 1rem;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
border-radius: clamp(2rem, 3rem, 4rem);
|
||||
cursor: pointer;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px -1px,
|
||||
rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
|
||||
background: #6866cc;
|
||||
color: #f4f4f5;
|
||||
"
|
||||
>
|
||||
follow the instructions
|
||||
Download certificate
|
||||
</a>
|
||||
to download and trust your Embassy's Root Certificate
|
||||
Authority.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<div style="padding: 2rem; text-align: center">
|
||||
<a
|
||||
id="cert"
|
||||
download="embassy.crt"
|
||||
<section
|
||||
style="
|
||||
padding: 1rem 3rem 2rem 3rem;
|
||||
border: solid #c4c4c5 3px;
|
||||
"
|
||||
>
|
||||
<h2 style="font-variant-caps: all-small-caps">
|
||||
Access on the go (Tor)
|
||||
</h2>
|
||||
<p>Visit the address below when you are away from home:</p>
|
||||
<p
|
||||
style="
|
||||
display: inline-block;
|
||||
padding: 1em 1.2em;
|
||||
box-sizing: border-box;
|
||||
font-size: 1rem;
|
||||
text-decoration: none;
|
||||
text-align: center;
|
||||
border-radius: clamp(2rem, 3rem, 4rem);
|
||||
cursor: pointer;
|
||||
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px -1px,
|
||||
rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
|
||||
background: #6866cc;
|
||||
color: #f4f4f5;
|
||||
padding: 16px;
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
overflow: auto;
|
||||
"
|
||||
>
|
||||
Download Root CA
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section
|
||||
style="padding: 1rem 3rem 2rem 3rem; border: solid #c4c4c5 3px"
|
||||
>
|
||||
<h2 style="font-variant-caps: all-small-caps">
|
||||
Access on the go (Tor)
|
||||
</h2>
|
||||
<p>Visit the address below when you are away from home:</p>
|
||||
<p
|
||||
style="
|
||||
padding: 16px;
|
||||
font-weight: bold;
|
||||
font-size: 1.1rem;
|
||||
overflow: auto;
|
||||
"
|
||||
>
|
||||
<code id="tor-addr"></code>
|
||||
</p>
|
||||
<div>
|
||||
<h3 style="color: #f8546a; font-weight: bold">Important!</h3>
|
||||
<p>
|
||||
This address will only work from a Tor-enabled browser.
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-tor"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style="
|
||||
color: #6866cc;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
"
|
||||
>
|
||||
Follow the instructions
|
||||
</a>
|
||||
to get setup.
|
||||
<code id="tor-addr"></code>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<div>
|
||||
<h3 style="color: #f8546a; font-weight: bold">
|
||||
Important!
|
||||
</h3>
|
||||
<p>
|
||||
This address will only work from a Tor-enabled browser.
|
||||
<a
|
||||
href="https://docs.start9.com/latest/user-manual/connecting/connecting-tor"
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
style="
|
||||
color: #6866cc;
|
||||
font-weight: bold;
|
||||
text-decoration: none;
|
||||
"
|
||||
>
|
||||
Follow the instructions
|
||||
</a>
|
||||
to get setup.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</ion-card>
|
||||
</ng-template>
|
||||
</ion-grid>
|
||||
</div>
|
||||
</ion-card>
|
||||
</ng-template>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</ion-content>
|
||||
|
||||
@@ -17,9 +17,8 @@ ion-content {
|
||||
}
|
||||
|
||||
ion-grid {
|
||||
padding-top: 2rem;
|
||||
max-width: 760px;
|
||||
margin: auto;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.grid-center-wrapper {
|
||||
@@ -47,6 +46,7 @@ ion-card {
|
||||
|
||||
ion-card {
|
||||
max-width: 91%;
|
||||
min-width: 91%;
|
||||
background: #615F5F;
|
||||
color: var(--ion-text-color);
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
@@ -78,14 +78,6 @@ ion-card {
|
||||
}
|
||||
}
|
||||
|
||||
p {
|
||||
a {
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
color: var(--ion-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
ion-footer {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
@@ -101,12 +93,6 @@ ion-card {
|
||||
ion-icon {
|
||||
font-size: 1.6rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-md::before {
|
||||
@@ -114,21 +100,31 @@ ion-card {
|
||||
}
|
||||
}
|
||||
|
||||
#launch:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 80%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#launch {
|
||||
background: var(--alt-blue);
|
||||
height: 100%;
|
||||
|
||||
.container p {
|
||||
font-size: 1.4rem !important;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
ion-icon {
|
||||
font-size: 1.7rem;
|
||||
}
|
||||
}
|
||||
|
||||
#information:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 77%;
|
||||
top: 80%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--color-accent);
|
||||
|
||||
@@ -42,6 +42,7 @@ export class SuccessPage {
|
||||
fadeFactor = 0.07
|
||||
columns: any[] = []
|
||||
maxStackHeight: any
|
||||
disableLogin = true
|
||||
|
||||
constructor(
|
||||
@Inject(DOCUMENT) private readonly document: Document,
|
||||
@@ -119,7 +120,9 @@ export class SuccessPage {
|
||||
encodeURIComponent(this.cert),
|
||||
)
|
||||
let html = this.document.getElementById('downloadable')?.innerHTML || ''
|
||||
this.downloadHtml.download('embassy-info.html', html)
|
||||
this.downloadHtml.download('embassy-info.html', html).then(_ => {
|
||||
this.disableLogin = false
|
||||
})
|
||||
}
|
||||
|
||||
checkBottom() {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ion-content>
|
||||
<ion-grid>
|
||||
<ion-row>
|
||||
<ion-row class="ion-align-items-center">
|
||||
<ion-col>
|
||||
<ion-card color="dark">
|
||||
<ion-card-header class="ion-text-center">
|
||||
|
||||
@@ -116,12 +116,12 @@
|
||||
--color-accent: #6866cc;
|
||||
--color-dark-black: #121212;
|
||||
|
||||
--alt-red: #FF4961;
|
||||
--alt-orange: #F89248;
|
||||
--alt-yellow: #E5D53E;
|
||||
--alt-green: #3DCF6F;
|
||||
--alt-blue: #00A8A8;
|
||||
--alt-purple: #9747FF;
|
||||
--alt-red: #FF4961;
|
||||
--alt-orange: #F89248;
|
||||
--alt-yellow: #E5D53E;
|
||||
--alt-green: #3DCF6F;
|
||||
--alt-blue: #00A8A8;
|
||||
--alt-purple: #9747FF;
|
||||
}
|
||||
|
||||
h1,
|
||||
@@ -177,7 +177,7 @@ ion-grid {
|
||||
}
|
||||
|
||||
ion-row {
|
||||
height: 100%;
|
||||
height: 90%;
|
||||
}
|
||||
|
||||
ion-card {
|
||||
@@ -334,4 +334,11 @@ ion-footer {
|
||||
ion-item {
|
||||
margin: 0 0.5rem 2rem 0.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
p a {
|
||||
color: var(--ion-text-color);
|
||||
// text-decoration: none;
|
||||
font-weight: 600;
|
||||
text-underline-offset: 0.4rem;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<ion-grid class="full-height">
|
||||
<ion-row class="ion-align-items-center ion-text-center full-height">
|
||||
<ion-col>
|
||||
<ion-spinner color="warning"></ion-spinner>
|
||||
<ion-spinner color="tertiary"></ion-spinner>
|
||||
<p>{{ text }}</p>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
|
||||
@@ -22,7 +22,7 @@ const routes: Routes = [
|
||||
import('./pages/home/home.module').then(m => m.HomePageModule),
|
||||
},
|
||||
{
|
||||
path: 'settings',
|
||||
path: 'system',
|
||||
canActivate: [AuthGuard],
|
||||
canActivateChild: [AuthGuard],
|
||||
loadChildren: () =>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
{{ page.title }}
|
||||
</ion-label>
|
||||
<ion-icon
|
||||
*ngIf="page.url === '/settings' && (showEOSUpdate$ | async)"
|
||||
*ngIf="page.url === '/system' && (showEOSUpdate$ | async)"
|
||||
color="success"
|
||||
size="small"
|
||||
name="rocket-outline"
|
||||
|
||||
@@ -38,9 +38,9 @@ export class MenuComponent {
|
||||
icon: 'notifications-outline',
|
||||
},
|
||||
{
|
||||
title: 'System Settings',
|
||||
url: '/settings',
|
||||
icon: 'settings-outline',
|
||||
title: 'System',
|
||||
url: '/system',
|
||||
icon: 'construct-outline',
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@ const ICONS = [
|
||||
'newspaper-outline',
|
||||
'notifications-outline',
|
||||
'open-outline',
|
||||
'options-outline',
|
||||
'pencil',
|
||||
'phone-portrait-outline',
|
||||
'play-circle-outline',
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
<ng-container *ngTemplateOutlet="content"></ng-container>
|
||||
</a>
|
||||
<ng-template #internal>
|
||||
<a [routerLink]="link">
|
||||
<a [routerLink]="link" [queryParams]="qp">
|
||||
<ng-container *ngTemplateOutlet="content"></ng-container>
|
||||
</a>
|
||||
</ng-template>
|
||||
|
||||
@@ -13,7 +13,8 @@ import {
|
||||
})
|
||||
export class AnyLinkComponent implements OnInit {
|
||||
@Input() link!: string
|
||||
externalLink: boolean = false
|
||||
@Input() qp?: Record<string, string>
|
||||
externalLink = false
|
||||
|
||||
ngOnInit() {
|
||||
try {
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
<ion-card>
|
||||
<any-link link="{{ link }}">
|
||||
<div class="p1">
|
||||
<ion-card-header>
|
||||
<ion-card-title>{{ title }}</ion-card-title>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-icon name="{{ icon }}" style="color: {{ color }}"></ion-icon>
|
||||
</ion-card-content>
|
||||
<ion-footer>
|
||||
{{ description }}
|
||||
</ion-footer>
|
||||
</div>
|
||||
</any-link>
|
||||
</ion-card>
|
||||
<div
|
||||
class="outer-wrapper"
|
||||
#outerWrapper
|
||||
[ngStyle]="{ height: outerHeight, width: outerWidth }"
|
||||
>
|
||||
<div
|
||||
class="inner-wrapper"
|
||||
#innerWrapper
|
||||
[ngStyle]="{ transform: innerTransform }"
|
||||
>
|
||||
<ion-card>
|
||||
<any-link [link]="cardDetails.link" [qp]="cardDetails.qp">
|
||||
<ion-card-header>
|
||||
<ion-card-title>{{ cardDetails.title }}</ion-card-title>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
<ion-icon
|
||||
[name]="cardDetails.icon"
|
||||
[style.color]="cardDetails.color"
|
||||
></ion-icon>
|
||||
</ion-card-content>
|
||||
<ion-footer>
|
||||
<p>{{ cardDetails.description }}</p>
|
||||
</ion-footer>
|
||||
</any-link>
|
||||
</ion-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,8 @@ ion-card {
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 44px;
|
||||
margin: auto;
|
||||
max-width: 22rem;
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
text-align: center;
|
||||
transition: all 350ms ease;
|
||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||
@@ -18,7 +19,8 @@ ion-card {
|
||||
font-family: 'Open Sans';
|
||||
padding: 0.6rem;
|
||||
font-weight: 600;
|
||||
font-size: 1.3rem;
|
||||
font-size: calc(12px + 0.5vw);
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
ion-card-content {
|
||||
@@ -29,7 +31,7 @@ ion-card {
|
||||
align-items: center;
|
||||
|
||||
ion-icon {
|
||||
font-size: 8rem;
|
||||
font-size: calc(90px + 0.5vw);
|
||||
--ionicon-stroke-width: 1rem;
|
||||
}
|
||||
}
|
||||
@@ -37,7 +39,13 @@ ion-card {
|
||||
ion-footer {
|
||||
padding: 1rem;
|
||||
font-family: 'Open Sans';
|
||||
font-size: 1.2rem;
|
||||
font-size: clamp(1rem, calc(12px + 0.5vw), 1.3rem);
|
||||
height: 9rem;
|
||||
width: clamp(13rem, 80%, 18rem);
|
||||
margin: 0 auto;
|
||||
* {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.footer-md::before {
|
||||
@@ -45,6 +53,17 @@ ion-card {
|
||||
}
|
||||
}
|
||||
|
||||
.p1 {
|
||||
padding: 1.2rem;
|
||||
@media (max-width: 900px) {
|
||||
ion-card-title, ion-footer {
|
||||
height: auto !important;
|
||||
}
|
||||
ion-footer {
|
||||
width: 10rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
ion-footer {
|
||||
width: 14rem;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,11 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
HostListener,
|
||||
Input,
|
||||
ViewChild,
|
||||
} from '@angular/core'
|
||||
|
||||
@Component({
|
||||
selector: 'widget-card',
|
||||
@@ -7,11 +14,53 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class WidgetCardComponent {
|
||||
@Input() title: string = ''
|
||||
@Input() icon: string = ''
|
||||
@Input() color: string = ''
|
||||
@Input() description: string = ''
|
||||
@Input() link: string = ''
|
||||
@Input() cardDetails!: Card
|
||||
@Input() containerDimensions!: Dimension
|
||||
@ViewChild('outerWrapper') outerWrapper: ElementRef<HTMLElement> =
|
||||
{} as ElementRef<HTMLElement>
|
||||
@ViewChild('innerWrapper') innerWrapper: ElementRef<HTMLElement> =
|
||||
{} as ElementRef<HTMLElement>
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize() {
|
||||
this.resize()
|
||||
}
|
||||
maxHeight = 0
|
||||
maxWidth = 0
|
||||
innerTransform = ''
|
||||
outerWidth: any
|
||||
outerHeight: any
|
||||
|
||||
constructor() {}
|
||||
ngAfterViewInit() {
|
||||
this.maxHeight = (<HTMLElement> (
|
||||
this.innerWrapper.nativeElement
|
||||
)).getBoundingClientRect().height
|
||||
this.maxWidth = (<HTMLElement> (
|
||||
this.innerWrapper.nativeElement
|
||||
)).getBoundingClientRect().width
|
||||
this.resize()
|
||||
}
|
||||
|
||||
resize() {
|
||||
const height = this.containerDimensions.height
|
||||
const width = this.containerDimensions.width
|
||||
const isMax = width >= this.maxWidth && height >= this.maxHeight
|
||||
const scale = Math.min(width / this.maxWidth, height / this.maxHeight)
|
||||
this.innerTransform = isMax ? '' : 'scale(' + scale + ')'
|
||||
this.outerWidth = isMax ? '' : this.maxWidth * scale
|
||||
this.outerHeight = isMax ? '' : this.maxHeight * scale
|
||||
}
|
||||
}
|
||||
|
||||
export interface Dimension {
|
||||
height: number
|
||||
width: number
|
||||
}
|
||||
|
||||
export interface Card {
|
||||
title: string
|
||||
icon: string
|
||||
color: string
|
||||
description: string
|
||||
link: string
|
||||
qp?: Record<string, string>
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
<ion-grid>
|
||||
<ion-row class="ion-justify-content-center ion-align-items-center">
|
||||
<ion-col
|
||||
*ngFor="let card of cards"
|
||||
size="auto"
|
||||
class="ion-align-self-center"
|
||||
>
|
||||
<widget-card
|
||||
title="{{ card.title }}"
|
||||
icon="{{ card.icon }}"
|
||||
color="{{ card.color }}"
|
||||
description="{{ card.description }}"
|
||||
link="{{ card.link }}"
|
||||
></widget-card>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
<div #gridContent>
|
||||
<ion-grid>
|
||||
<ion-row class="ion-justify-content-center ion-align-items-center">
|
||||
<ion-col
|
||||
*ngFor="let card of cards"
|
||||
sizeLg="4"
|
||||
sizeSm="6"
|
||||
sizeXs="12"
|
||||
class="ion-align-self-center"
|
||||
>
|
||||
<widget-card
|
||||
[cardDetails]="card"
|
||||
[containerDimensions]="containerDimensions"
|
||||
></widget-card>
|
||||
</ion-col>
|
||||
</ion-row>
|
||||
</ion-grid>
|
||||
</div>
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
ion-row {
|
||||
grid-row-gap: 1rem;
|
||||
ion-col {
|
||||
max-width: 22rem !important;
|
||||
--ion-grid-column-padding: 1rem;
|
||||
}
|
||||
|
||||
ion-col {
|
||||
padding: 0 0.5rem 0.5rem 1rem;
|
||||
@media (min-width: 1800px) {
|
||||
div {
|
||||
padding: 0 20%;
|
||||
}
|
||||
ion-col {
|
||||
max-width: 24rem !important;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,11 @@
|
||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
import {
|
||||
ChangeDetectionStrategy,
|
||||
Component,
|
||||
ElementRef,
|
||||
HostListener,
|
||||
ViewChild,
|
||||
} from '@angular/core'
|
||||
import { Card, Dimension } from '../widget-card/widget-card.component'
|
||||
|
||||
@Component({
|
||||
selector: 'widget-list',
|
||||
@@ -7,7 +14,27 @@ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class WidgetListComponent {
|
||||
constructor() {}
|
||||
@ViewChild('gridContent') gridContent: ElementRef<HTMLElement> =
|
||||
{} as ElementRef<HTMLElement>
|
||||
@HostListener('window:resize', ['$event'])
|
||||
onResize() {
|
||||
this.setContainerDimensions()
|
||||
}
|
||||
|
||||
containerDimensions: Dimension = {} as Dimension
|
||||
|
||||
ngAfterViewInit() {
|
||||
this.setContainerDimensions()
|
||||
}
|
||||
|
||||
setContainerDimensions() {
|
||||
this.containerDimensions.height = (<HTMLElement> (
|
||||
this.gridContent.nativeElement
|
||||
)).getBoundingClientRect().height
|
||||
this.containerDimensions.width = (<HTMLElement> (
|
||||
this.gridContent.nativeElement
|
||||
)).getBoundingClientRect().width
|
||||
}
|
||||
|
||||
cards: Card[] = [
|
||||
{
|
||||
@@ -15,7 +42,8 @@ export class WidgetListComponent {
|
||||
icon: 'storefront-outline',
|
||||
color: 'var(--alt-blue)',
|
||||
description: 'Shop for your favorite open source services',
|
||||
link: '/marketplace/browse',
|
||||
link: '/marketplace',
|
||||
qp: { back: 'true' },
|
||||
},
|
||||
{
|
||||
title: 'LAN Setup',
|
||||
@@ -23,21 +51,21 @@ export class WidgetListComponent {
|
||||
color: 'var(--alt-orange)',
|
||||
description:
|
||||
'Install your Embassy certificate for a secure local connection',
|
||||
link: '/settings/lan',
|
||||
link: '/system/lan',
|
||||
},
|
||||
{
|
||||
title: 'Create Backup',
|
||||
icon: 'duplicate-outline',
|
||||
color: 'var(--alt-purple)',
|
||||
description: 'Back up your Embassy and service data',
|
||||
link: '/settings/backup',
|
||||
link: '/system/backup',
|
||||
},
|
||||
{
|
||||
title: 'Embassy Info',
|
||||
icon: 'information-circle-outline',
|
||||
color: 'var(--alt-green)',
|
||||
description: 'View basic information about your Embassy',
|
||||
link: '/settings/specs',
|
||||
link: '/system/specs',
|
||||
},
|
||||
{
|
||||
title: 'User Manual',
|
||||
@@ -55,11 +83,3 @@ export class WidgetListComponent {
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
interface Card {
|
||||
title: string
|
||||
icon: string
|
||||
color: string
|
||||
description: string
|
||||
link: string
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ export class ToButtonsPipe implements PipeTransform {
|
||||
this.modalService.presentModalConfig({ pkgId: pkg.manifest.id }),
|
||||
title: 'Config',
|
||||
description: `Customize ${pkgTitle}`,
|
||||
icon: 'construct-outline',
|
||||
icon: 'options-outline',
|
||||
},
|
||||
// properties
|
||||
{
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<div style="padding: 36px 0">
|
||||
<div class="padding-top">
|
||||
<widget-list></widget-list>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
.padding-top {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
@media (min-width: 2000px) {
|
||||
.padding-top {
|
||||
padding-top: 10rem;
|
||||
}
|
||||
}
|
||||
@@ -39,9 +39,6 @@
|
||||
></ion-icon>
|
||||
</ion-button>
|
||||
</ion-item>
|
||||
<p style="text-align: left; padding-top: 4px">
|
||||
<ion-text color="danger">{{ error }}</ion-text>
|
||||
</p>
|
||||
</ion-item-group>
|
||||
<ion-button
|
||||
class="login-button"
|
||||
@@ -51,6 +48,9 @@
|
||||
>
|
||||
<span style="font-size: larger; font-weight: bold">Login</span>
|
||||
</ion-button>
|
||||
<p style="text-align: left; padding-top: 4px">
|
||||
<ion-text color="danger">{{ error }}</ion-text>
|
||||
</p>
|
||||
</form>
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
|
||||
@@ -28,6 +28,7 @@ ion-card {
|
||||
background: var(--ion-color-step-200);
|
||||
box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25);
|
||||
border-radius: 44px;
|
||||
min-height: 16rem;
|
||||
}
|
||||
|
||||
.input-label {
|
||||
|
||||
@@ -40,6 +40,10 @@ export class LoginPage {
|
||||
|
||||
try {
|
||||
document.cookie = ''
|
||||
if (this.password.length > 64) {
|
||||
this.error = 'Password must be less than 65 characters'
|
||||
return
|
||||
}
|
||||
await this.api.login({
|
||||
password: this.password,
|
||||
metadata: { platforms: getPlatforms() },
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start" *ngIf="back">
|
||||
<ion-back-button></ion-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>Marketplace</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<badge-menu-button></badge-menu-button>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'
|
||||
import { ActivatedRoute } from '@angular/router'
|
||||
import { ModalController } from '@ionic/angular'
|
||||
import { AbstractMarketplaceService } from '@start9labs/marketplace'
|
||||
import { PatchDB } from 'patch-db-client'
|
||||
@@ -15,6 +16,8 @@ import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class MarketplaceListPage {
|
||||
readonly back = !!this.route.snapshot.queryParamMap.get('back')
|
||||
|
||||
readonly store$ = this.marketplaceService.getSelectedStore$().pipe(
|
||||
filter(Boolean),
|
||||
map(({ info, packages }) => {
|
||||
@@ -73,6 +76,7 @@ export class MarketplaceListPage {
|
||||
private readonly marketplaceService: MarketplaceService,
|
||||
private readonly modalCtrl: ModalController,
|
||||
private readonly config: ConfigService,
|
||||
private readonly route: ActivatedRoute,
|
||||
) {}
|
||||
|
||||
category = 'featured'
|
||||
|
||||
@@ -4,11 +4,7 @@ import { Routes, RouterModule } from '@angular/router'
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'browse',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'browse',
|
||||
loadChildren: () =>
|
||||
import('./marketplace-list/marketplace-list.module').then(
|
||||
m => m.MarketplaceListPageModule,
|
||||
|
||||
@@ -26,7 +26,7 @@ export class NotificationsPage {
|
||||
notifications: ServerNotifications = []
|
||||
beforeCursor?: number
|
||||
needInfinite = false
|
||||
fromToast = false
|
||||
fromToast = !!this.route.snapshot.queryParamMap.get('toast')
|
||||
readonly perPage = 40
|
||||
readonly packageData$ = this.patch.watch$('package-data')
|
||||
|
||||
@@ -41,7 +41,6 @@ export class NotificationsPage {
|
||||
) {}
|
||||
|
||||
async ngOnInit() {
|
||||
this.fromToast = !!this.route.snapshot.queryParamMap.get('toast')
|
||||
this.notifications = await this.getNotifications()
|
||||
this.loading = false
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-title> System Settings </ion-title>
|
||||
<ion-title> System </ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<badge-menu-button></badge-menu-button>
|
||||
</ion-buttons>
|
||||
|
||||
Reference in New Issue
Block a user