mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
drives page
This commit is contained in:
committed by
Aiden McClelland
parent
31318687bf
commit
0cd2a32b24
@@ -29,6 +29,11 @@
|
||||
"glob": "**/*.svg",
|
||||
"input": "node_modules/ionicons/dist/ionicons/svg",
|
||||
"output": "./svg"
|
||||
},
|
||||
{
|
||||
"glob": "**/*.svg",
|
||||
"input": "src/assets/icon",
|
||||
"output": "./svg"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
|
||||
@@ -33,6 +33,11 @@ const routes: Routes = [
|
||||
canActivateChild: [AuthGuard],
|
||||
loadChildren: () => import('./pages/apps-routes/apps-routing.module').then(m => m.AppsRoutingModule),
|
||||
},
|
||||
{
|
||||
path: 'drives',
|
||||
canActivate: [AuthGuard],
|
||||
loadChildren: () => import('./pages/server-routes/external-drives/external-drives.module').then( m => m.ExternalDrivesPageModule),
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -47,6 +47,11 @@ export class AppComponent {
|
||||
url: '/notifications',
|
||||
icon: 'notifications-outline',
|
||||
},
|
||||
{
|
||||
title: 'External Drives',
|
||||
url: '/drives',
|
||||
icon: 'albums-outline',
|
||||
},
|
||||
]
|
||||
|
||||
constructor (
|
||||
|
||||
@@ -161,7 +161,7 @@ export interface DiskInfo {
|
||||
|
||||
export interface DiskPartition {
|
||||
logicalname: string,
|
||||
isMounted: boolean, // Do not let them back up to this if true
|
||||
isMounted: boolean, // We do not allow backups to mounted partitions
|
||||
size: string | null,
|
||||
label: string | null,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { NgModule } from '@angular/core'
|
||||
import { CommonModule } from '@angular/common'
|
||||
import { IonicModule } from '@ionic/angular'
|
||||
import { ExternalDrivesPage } from './external-drives.page'
|
||||
import { Routes, RouterModule } from '@angular/router'
|
||||
import { SharingModule } from 'src/app/modules/sharing.module'
|
||||
import { PwaBackComponentModule } from 'src/app/components/pwa-back-button/pwa-back.component.module'
|
||||
import { BadgeMenuComponentModule } from 'src/app/components/badge-menu-button/badge-menu.component.module'
|
||||
import { ObjectConfigComponentModule } from 'src/app/components/object-config/object-config.component.module'
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: ExternalDrivesPage,
|
||||
},
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
SharingModule,
|
||||
ObjectConfigComponentModule,
|
||||
RouterModule.forChild(routes),
|
||||
PwaBackComponentModule,
|
||||
BadgeMenuComponentModule,
|
||||
],
|
||||
declarations: [ExternalDrivesPage],
|
||||
})
|
||||
export class ExternalDrivesPageModule { }
|
||||
@@ -0,0 +1,38 @@
|
||||
<ion-header>
|
||||
<ion-toolbar>
|
||||
<ion-buttons slot="start">
|
||||
<pwa-back-button></pwa-back-button>
|
||||
</ion-buttons>
|
||||
<ion-title>External Drives</ion-title>
|
||||
<ion-buttons slot="end">
|
||||
<badge-menu-button></badge-menu-button>
|
||||
</ion-buttons>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content *ngIf="!($loading$ | async)" class="ion-padding-top">
|
||||
<ion-refresher slot="fixed" (ionRefresh)="doRefresh($event)">
|
||||
<ion-refresher-content pullingIcon="lines" refreshingSpinner="lines"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
|
||||
<ion-item-divider>
|
||||
Storage
|
||||
</ion-item-divider>
|
||||
|
||||
<ion-item-group>
|
||||
<!-- <ion-list> -->
|
||||
<ion-item *ngFor="let d of disks; let i = index">
|
||||
<ion-icon slot="start" name="save-outline"></ion-icon>
|
||||
<ion-label>{{d.logicalname}}</ion-label>
|
||||
<ion-button *ngIf="!(d.$ejecting$ | async)" slot="end" fill="clear" color="medium" (click)="ejectDisk(i)">
|
||||
<ion-icon color="primary" class="icon" src="/assets/icon/eject.svg"></ion-icon>
|
||||
</ion-button>
|
||||
<ion-spinner *ngIf="d.$ejecting$ | async" name="lines" color="medium"></ion-spinner>
|
||||
</ion-item>
|
||||
<!-- </ion-list> -->
|
||||
</ion-item-group>
|
||||
</ion-content>
|
||||
|
||||
<ion-content *ngIf="$loading$ | async" class="ion-padding-top">
|
||||
<ion-spinner class="center" name="lines" color="warning"></ion-spinner>
|
||||
</ion-content>
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Component } from '@angular/core'
|
||||
import { pauseFor } from 'src/app/util/misc.util'
|
||||
import { ApiService } from 'src/app/services/api/api.service'
|
||||
import { DiskInfo } from 'src/app/models/server-model'
|
||||
import { markAsLoadingDuring$, markAsLoadingDuringAsync, markAsLoadingDuringP } from 'src/app/services/loader.service'
|
||||
import { BehaviorSubject } from 'rxjs'
|
||||
import { AlertController } from '@ionic/angular'
|
||||
|
||||
type Ejectable<T> = T & { $ejecting$: BehaviorSubject<boolean> }
|
||||
|
||||
@Component({
|
||||
selector: 'external-drives',
|
||||
templateUrl: './external-drives.page.html',
|
||||
styleUrls: ['./external-drives.page.scss'],
|
||||
})
|
||||
export class ExternalDrivesPage {
|
||||
disks: Ejectable<DiskInfo>[] = []
|
||||
$loading$ = new BehaviorSubject(false)
|
||||
|
||||
constructor (
|
||||
private readonly apiService: ApiService,
|
||||
private readonly alertCtrl: AlertController,
|
||||
) { }
|
||||
|
||||
ngOnInit () {
|
||||
markAsLoadingDuringP(this.$loading$, this.fetchDisks())
|
||||
}
|
||||
|
||||
async doRefresh (event: any) {
|
||||
await Promise.all([
|
||||
this.fetchDisks(),
|
||||
pauseFor(600),
|
||||
])
|
||||
event.target.complete()
|
||||
}
|
||||
|
||||
async fetchDisks () {
|
||||
return this.apiService.getExternalDisks().then(ds => {
|
||||
this.disks = ds.map(d => ({ ...d, $ejecting$: new BehaviorSubject(false)})).sort( (a, b) => a.logicalname < b.logicalname ? -1 : 1 )
|
||||
})
|
||||
}
|
||||
|
||||
async ejectDisk (diskIndex: number) {
|
||||
const d = this.disks[diskIndex]
|
||||
markAsLoadingDuringP(d.$ejecting$, this.apiService.ejectExternalDisk(d.logicalname))
|
||||
.then(() => this.disks.splice(diskIndex, 1))
|
||||
.catch((e: Error) => {
|
||||
this.alertError(`Could not eject ${d.logicalname}: ${e.message}`)
|
||||
})
|
||||
}
|
||||
|
||||
async alertError (desc: string) {
|
||||
const alert = await this.alertCtrl.create({
|
||||
backdropDismiss: true,
|
||||
message: desc,
|
||||
cssClass: 'alert-error-message',
|
||||
})
|
||||
await alert.present()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ const routes: Routes = [
|
||||
path: 'developer',
|
||||
canActivate: [AuthGuard],
|
||||
loadChildren: () => import('./developer-routes/developer-routing.module').then( m => m.DeveloperRoutingModule),
|
||||
},
|
||||
}
|
||||
]
|
||||
|
||||
@NgModule({
|
||||
|
||||
@@ -57,6 +57,7 @@ export abstract class ApiService {
|
||||
abstract deleteWifi (ssid: string): Promise<Unit>
|
||||
abstract restartServer (): Promise<Unit>
|
||||
abstract shutdownServer (): Promise<Unit>
|
||||
abstract ejectExternalDisk (logicalName: string): Promise<Unit>
|
||||
}
|
||||
|
||||
export module ReqRes {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { HttpErrorResponse } from '@angular/common/http'
|
||||
import { isUnauthorized } from 'src/app/util/web.util'
|
||||
import { Replace } from 'src/app/util/types.util'
|
||||
import { AppMetrics, parseMetricsPermissive } from 'src/app/util/metrics.util'
|
||||
import { modulateTime } from 'src/app/util/misc.util'
|
||||
// import { modulateTime } from 'src/app/util/misc.util'
|
||||
|
||||
@Injectable()
|
||||
export class LiveApiService extends ApiService {
|
||||
@@ -61,6 +61,10 @@ export class LiveApiService extends ApiService {
|
||||
return this.authRequest<ReqRes.GetExternalDisksRes>({ method: Method.GET, url: `/disks` })
|
||||
}
|
||||
|
||||
async ejectExternalDisk (logicalName: string): Promise<Unit> {
|
||||
return this.authRequest({ method: Method.DELETE, url: `/disks/${logicalName}` })
|
||||
}
|
||||
|
||||
async updateAgent (version: string): Promise<Unit> {
|
||||
const data: ReqRes.PostUpdateAgentReq = {
|
||||
version: `=${version}`,
|
||||
|
||||
@@ -29,13 +29,17 @@ export class MockApiService extends ApiService {
|
||||
async postConfigureDependency (dependencyId: string, dependentId: string, dryRun?: boolean): Promise<{ config: object, breakages: DependentBreakage[] }> {
|
||||
await pauseFor(2000)
|
||||
throw new Error ('some misc backend error ohh we forgot to make this endpoint or something')
|
||||
// return { config: mockCupsDependentConfig, breakages: [ ] }
|
||||
}
|
||||
|
||||
async getServer (): Promise<ApiServer> {
|
||||
return mockGetServer()
|
||||
}
|
||||
|
||||
async ejectExternalDisk (): Promise<Unit> {
|
||||
await pauseFor(2000)
|
||||
return { }
|
||||
}
|
||||
|
||||
async getCheckAuth (): Promise<ReqRes.GetCheckAuthRes> {
|
||||
return { }
|
||||
}
|
||||
|
||||
4
ui/src/assets/icon/eject-outline.svg
Normal file
4
ui/src/assets/icon/eject-outline.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M413.822 269.498L274.042 106.378C271.789 103.751 268.994 101.641 265.849 100.195C262.704 98.7488 259.284 98 255.822 98C252.36 98 248.94 98.7488 245.795 100.195C242.65 101.641 239.855 103.751 237.602 106.378L97.822 269.498C84.482 285.068 95.542 309.118 116.042 309.118H395.642C416.142 309.118 427.202 285.068 413.822 269.498V269.498Z" fill="black"/>
|
||||
<line x1="116" y1="356" x2="396" y2="356" stroke="black" stroke-width="48" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 589 B |
4
ui/src/assets/icon/eject.svg
Normal file
4
ui/src/assets/icon/eject.svg
Normal file
@@ -0,0 +1,4 @@
|
||||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M455.761 282.295L278.654 75.6159C275.799 72.2862 272.258 69.6135 268.273 67.7811C264.289 65.9487 259.955 65 255.569 65C251.183 65 246.849 65.9487 242.864 67.7811C238.879 69.6135 235.338 72.2862 232.483 75.6159L55.3767 282.295C38.4744 302.023 52.4879 332.495 78.4621 332.495H432.726C458.7 332.495 472.714 302.023 455.761 282.295V282.295Z" fill="#989898"/>
|
||||
<line x1="76" y1="415.305" x2="435.589" y2="415.305" stroke="#989898" stroke-width="56" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 608 B |
@@ -194,6 +194,12 @@ ion-popover {
|
||||
}
|
||||
}
|
||||
|
||||
.alert-error-message {
|
||||
.alert-message {
|
||||
color: var(--ion-color-danger);
|
||||
}
|
||||
}
|
||||
|
||||
ion-slides {
|
||||
.slider-wrapper {
|
||||
height: 100%;
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
"src/polyfills.ts",
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
"src/**/*.d.ts",
|
||||
"*.d.ts",
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user