From 0cd2a32b24de6bba228455a278e54e12d38830ad Mon Sep 17 00:00:00 2001 From: Aaron Greenspan Date: Wed, 6 Jan 2021 20:24:19 -0700 Subject: [PATCH] drives page --- ui/angular.json | 5 ++ ui/src/app/app-routing.module.ts | 5 ++ ui/src/app/app.component.ts | 5 ++ ui/src/app/models/server-model.ts | 2 +- .../external-drives/external-drives.module.ts | 30 +++++++++ .../external-drives/external-drives.page.html | 38 ++++++++++++ .../external-drives/external-drives.page.scss | 0 .../external-drives/external-drives.page.ts | 61 +++++++++++++++++++ .../server-routes/server-routing.module.ts | 2 +- ui/src/app/services/api/api.service.ts | 1 + ui/src/app/services/api/live-api.service.ts | 6 +- ui/src/app/services/api/mock-api.service.ts | 6 +- ui/src/assets/icon/eject-outline.svg | 4 ++ ui/src/assets/icon/eject.svg | 4 ++ ui/src/global.scss | 6 ++ ui/tsconfig.json | 3 +- 16 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 ui/src/app/pages/server-routes/external-drives/external-drives.module.ts create mode 100644 ui/src/app/pages/server-routes/external-drives/external-drives.page.html create mode 100644 ui/src/app/pages/server-routes/external-drives/external-drives.page.scss create mode 100644 ui/src/app/pages/server-routes/external-drives/external-drives.page.ts create mode 100644 ui/src/assets/icon/eject-outline.svg create mode 100644 ui/src/assets/icon/eject.svg diff --git a/ui/angular.json b/ui/angular.json index bf416b4bd..c4745d462 100644 --- a/ui/angular.json +++ b/ui/angular.json @@ -29,6 +29,11 @@ "glob": "**/*.svg", "input": "node_modules/ionicons/dist/ionicons/svg", "output": "./svg" + }, + { + "glob": "**/*.svg", + "input": "src/assets/icon", + "output": "./svg" } ], "styles": [ diff --git a/ui/src/app/app-routing.module.ts b/ui/src/app/app-routing.module.ts index 55cad94dd..6f2e8db05 100644 --- a/ui/src/app/app-routing.module.ts +++ b/ui/src/app/app-routing.module.ts @@ -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({ diff --git a/ui/src/app/app.component.ts b/ui/src/app/app.component.ts index 233c8374c..b6afe40cc 100644 --- a/ui/src/app/app.component.ts +++ b/ui/src/app/app.component.ts @@ -47,6 +47,11 @@ export class AppComponent { url: '/notifications', icon: 'notifications-outline', }, + { + title: 'External Drives', + url: '/drives', + icon: 'albums-outline', + }, ] constructor ( diff --git a/ui/src/app/models/server-model.ts b/ui/src/app/models/server-model.ts index 377389169..bcee0c2cf 100644 --- a/ui/src/app/models/server-model.ts +++ b/ui/src/app/models/server-model.ts @@ -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, } diff --git a/ui/src/app/pages/server-routes/external-drives/external-drives.module.ts b/ui/src/app/pages/server-routes/external-drives/external-drives.module.ts new file mode 100644 index 000000000..066b57a3a --- /dev/null +++ b/ui/src/app/pages/server-routes/external-drives/external-drives.module.ts @@ -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 { } diff --git a/ui/src/app/pages/server-routes/external-drives/external-drives.page.html b/ui/src/app/pages/server-routes/external-drives/external-drives.page.html new file mode 100644 index 000000000..332d8f531 --- /dev/null +++ b/ui/src/app/pages/server-routes/external-drives/external-drives.page.html @@ -0,0 +1,38 @@ + + + + + + External Drives + + + + + + + + + + + + + Storage + + + + + + + {{d.logicalname}} + + + + + + + + + + + + diff --git a/ui/src/app/pages/server-routes/external-drives/external-drives.page.scss b/ui/src/app/pages/server-routes/external-drives/external-drives.page.scss new file mode 100644 index 000000000..e69de29bb diff --git a/ui/src/app/pages/server-routes/external-drives/external-drives.page.ts b/ui/src/app/pages/server-routes/external-drives/external-drives.page.ts new file mode 100644 index 000000000..88890b1b0 --- /dev/null +++ b/ui/src/app/pages/server-routes/external-drives/external-drives.page.ts @@ -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 & { $ejecting$: BehaviorSubject } + +@Component({ + selector: 'external-drives', + templateUrl: './external-drives.page.html', + styleUrls: ['./external-drives.page.scss'], +}) +export class ExternalDrivesPage { + disks: Ejectable[] = [] + $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() + } +} + diff --git a/ui/src/app/pages/server-routes/server-routing.module.ts b/ui/src/app/pages/server-routes/server-routing.module.ts index c4cd29ec2..78f5516fc 100644 --- a/ui/src/app/pages/server-routes/server-routing.module.ts +++ b/ui/src/app/pages/server-routes/server-routing.module.ts @@ -37,7 +37,7 @@ const routes: Routes = [ path: 'developer', canActivate: [AuthGuard], loadChildren: () => import('./developer-routes/developer-routing.module').then( m => m.DeveloperRoutingModule), - }, + } ] @NgModule({ diff --git a/ui/src/app/services/api/api.service.ts b/ui/src/app/services/api/api.service.ts index 7678b3ba6..cc73fa08c 100644 --- a/ui/src/app/services/api/api.service.ts +++ b/ui/src/app/services/api/api.service.ts @@ -57,6 +57,7 @@ export abstract class ApiService { abstract deleteWifi (ssid: string): Promise abstract restartServer (): Promise abstract shutdownServer (): Promise + abstract ejectExternalDisk (logicalName: string): Promise } export module ReqRes { diff --git a/ui/src/app/services/api/live-api.service.ts b/ui/src/app/services/api/live-api.service.ts index 1e54ee770..31c6da4a6 100644 --- a/ui/src/app/services/api/live-api.service.ts +++ b/ui/src/app/services/api/live-api.service.ts @@ -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({ method: Method.GET, url: `/disks` }) } + async ejectExternalDisk (logicalName: string): Promise { + return this.authRequest({ method: Method.DELETE, url: `/disks/${logicalName}` }) + } + async updateAgent (version: string): Promise { const data: ReqRes.PostUpdateAgentReq = { version: `=${version}`, diff --git a/ui/src/app/services/api/mock-api.service.ts b/ui/src/app/services/api/mock-api.service.ts index ff109010c..ba5308a33 100644 --- a/ui/src/app/services/api/mock-api.service.ts +++ b/ui/src/app/services/api/mock-api.service.ts @@ -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 { return mockGetServer() } + async ejectExternalDisk (): Promise { + await pauseFor(2000) + return { } + } + async getCheckAuth (): Promise { return { } } diff --git a/ui/src/assets/icon/eject-outline.svg b/ui/src/assets/icon/eject-outline.svg new file mode 100644 index 000000000..6ecd2a10b --- /dev/null +++ b/ui/src/assets/icon/eject-outline.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/src/assets/icon/eject.svg b/ui/src/assets/icon/eject.svg new file mode 100644 index 000000000..3e2a87bc1 --- /dev/null +++ b/ui/src/assets/icon/eject.svg @@ -0,0 +1,4 @@ + + + + diff --git a/ui/src/global.scss b/ui/src/global.scss index 68f636155..1b9948100 100644 --- a/ui/src/global.scss +++ b/ui/src/global.scss @@ -194,6 +194,12 @@ ion-popover { } } +.alert-error-message { + .alert-message { + color: var(--ion-color-danger); + } +} + ion-slides { .slider-wrapper { height: 100%; diff --git a/ui/tsconfig.json b/ui/tsconfig.json index b55f72a21..af07fe92d 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -33,6 +33,7 @@ "src/polyfills.ts", ], "include": [ - "src/**/*.d.ts" + "src/**/*.d.ts", + "*.d.ts", ] }