mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-26 02:11:53 +00:00
Feature/server status restarting (#2503)
* extend `server-info` * add restarting, shutting down to FE status bar * fix build --------- Co-authored-by: Aiden McClelland <me@drbonez.dev>
This commit is contained in:
@@ -57,6 +57,8 @@ impl Database {
|
|||||||
backup_progress: None,
|
backup_progress: None,
|
||||||
updated: false,
|
updated: false,
|
||||||
update_progress: None,
|
update_progress: None,
|
||||||
|
shutting_down: false,
|
||||||
|
restarting: false,
|
||||||
},
|
},
|
||||||
wifi: WifiInfo {
|
wifi: WifiInfo {
|
||||||
ssids: Vec::new(),
|
ssids: Vec::new(),
|
||||||
@@ -166,6 +168,10 @@ pub struct ServerStatus {
|
|||||||
pub backup_progress: Option<BTreeMap<PackageId, BackupProgress>>,
|
pub backup_progress: Option<BTreeMap<PackageId, BackupProgress>>,
|
||||||
pub updated: bool,
|
pub updated: bool,
|
||||||
pub update_progress: Option<UpdateProgress>,
|
pub update_progress: Option<UpdateProgress>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub shutting_down: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub restarting: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
#[derive(Debug, Deserialize, Serialize, HasModel)]
|
||||||
|
|||||||
@@ -406,6 +406,8 @@ pub async fn init(cfg: &RpcContextConfig) -> Result<InitResult, Error> {
|
|||||||
updated: false,
|
updated: false,
|
||||||
update_progress: None,
|
update_progress: None,
|
||||||
backup_progress: None,
|
backup_progress: None,
|
||||||
|
shutting_down: false,
|
||||||
|
restarting: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
server_info.ntp_synced = if time_not_synced {
|
server_info.ntp_synced = if time_not_synced {
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ use rpc_toolkit::command;
|
|||||||
use crate::context::RpcContext;
|
use crate::context::RpcContext;
|
||||||
use crate::disk::main::export;
|
use crate::disk::main::export;
|
||||||
use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH};
|
use crate::init::{STANDBY_MODE_PATH, SYSTEM_REBUILD_PATH};
|
||||||
|
use crate::prelude::*;
|
||||||
use crate::sound::SHUTDOWN;
|
use crate::sound::SHUTDOWN;
|
||||||
use crate::util::docker::CONTAINER_TOOL;
|
use crate::util::docker::CONTAINER_TOOL;
|
||||||
use crate::util::{display_none, Invoke};
|
use crate::util::{display_none, Invoke};
|
||||||
use crate::{Error, PLATFORM};
|
use crate::PLATFORM;
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Shutdown {
|
pub struct Shutdown {
|
||||||
@@ -90,6 +91,14 @@ impl Shutdown {
|
|||||||
|
|
||||||
#[command(display(display_none))]
|
#[command(display(display_none))]
|
||||||
pub async fn shutdown(#[context] ctx: RpcContext) -> Result<(), Error> {
|
pub async fn shutdown(#[context] ctx: RpcContext) -> Result<(), Error> {
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
db.as_server_info_mut()
|
||||||
|
.as_status_info_mut()
|
||||||
|
.as_shutting_down_mut()
|
||||||
|
.ser(&true)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
ctx.shutdown
|
ctx.shutdown
|
||||||
.send(Some(Shutdown {
|
.send(Some(Shutdown {
|
||||||
export_args: Some((ctx.disk_guid.clone(), ctx.datadir.clone())),
|
export_args: Some((ctx.disk_guid.clone(), ctx.datadir.clone())),
|
||||||
@@ -102,6 +111,14 @@ pub async fn shutdown(#[context] ctx: RpcContext) -> Result<(), Error> {
|
|||||||
|
|
||||||
#[command(display(display_none))]
|
#[command(display(display_none))]
|
||||||
pub async fn restart(#[context] ctx: RpcContext) -> Result<(), Error> {
|
pub async fn restart(#[context] ctx: RpcContext) -> Result<(), Error> {
|
||||||
|
ctx.db
|
||||||
|
.mutate(|db| {
|
||||||
|
db.as_server_info_mut()
|
||||||
|
.as_status_info_mut()
|
||||||
|
.as_restarting_mut()
|
||||||
|
.ser(&true)
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
ctx.shutdown
|
ctx.shutdown
|
||||||
.send(Some(Shutdown {
|
.send(Some(Shutdown {
|
||||||
export_args: Some((ctx.disk_guid.clone(), ctx.datadir.clone())),
|
export_args: Some((ctx.disk_guid.clone(), ctx.datadir.clone())),
|
||||||
|
|||||||
@@ -26,10 +26,7 @@
|
|||||||
type="overlay"
|
type="overlay"
|
||||||
side="end"
|
side="end"
|
||||||
class="right-menu container"
|
class="right-menu container"
|
||||||
[class.container_offline]="
|
[class.container_offline]="offline$ | async"
|
||||||
(authService.isVerified$ | async) &&
|
|
||||||
!(connection.connected$ | async)
|
|
||||||
"
|
|
||||||
[class.right-menu_hidden]="!drawer.open"
|
[class.right-menu_hidden]="!drawer.open"
|
||||||
[style.--side-width.px]="drawer.width"
|
[style.--side-width.px]="drawer.width"
|
||||||
>
|
>
|
||||||
@@ -47,10 +44,7 @@
|
|||||||
[responsiveColViewport]="viewport"
|
[responsiveColViewport]="viewport"
|
||||||
id="main-content"
|
id="main-content"
|
||||||
class="container"
|
class="container"
|
||||||
[class.container_offline]="
|
[class.container_offline]="offline$ | async"
|
||||||
(authService.isVerified$ | async) &&
|
|
||||||
!(connection.connected$ | async)
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<ion-content
|
<ion-content
|
||||||
#viewport="viewport"
|
#viewport="viewport"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Component, inject, OnDestroy } from '@angular/core'
|
import { Component, inject, OnDestroy } from '@angular/core'
|
||||||
import { merge } from 'rxjs'
|
import { combineLatest, map, merge, startWith } from 'rxjs'
|
||||||
import { AuthService } from './services/auth.service'
|
import { AuthService } from './services/auth.service'
|
||||||
import { SplitPaneTracker } from './services/split-pane.service'
|
import { SplitPaneTracker } from './services/split-pane.service'
|
||||||
import { PatchDataService } from './services/patch-data.service'
|
import { PatchDataService } from './services/patch-data.service'
|
||||||
@@ -25,6 +25,19 @@ export class AppComponent implements OnDestroy {
|
|||||||
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
|
readonly sidebarOpen$ = this.splitPane.sidebarOpen$
|
||||||
readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$
|
readonly widgetDrawer$ = this.clientStorageService.widgetDrawer$
|
||||||
readonly theme$ = inject(THEME)
|
readonly theme$ = inject(THEME)
|
||||||
|
readonly offline$ = combineLatest([
|
||||||
|
this.authService.isVerified$,
|
||||||
|
this.connection.connected$,
|
||||||
|
this.patch
|
||||||
|
.watch$('server-info', 'status-info')
|
||||||
|
.pipe(startWith({ restarting: false, 'shutting-down': false })),
|
||||||
|
]).pipe(
|
||||||
|
map(
|
||||||
|
([verified, connected, status]) =>
|
||||||
|
verified &&
|
||||||
|
(!connected || status.restarting || status['shutting-down']),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private readonly titleService: Title,
|
private readonly titleService: Title,
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
import { ChangeDetectionStrategy, Component } from '@angular/core'
|
||||||
|
import { PatchDB } from 'patch-db-client'
|
||||||
import { combineLatest, map, Observable, startWith } from 'rxjs'
|
import { combineLatest, map, Observable, startWith } from 'rxjs'
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
|
import { DataModel } from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'connection-bar',
|
selector: 'connection-bar',
|
||||||
@@ -19,8 +21,11 @@ export class ConnectionBarComponent {
|
|||||||
}> = combineLatest([
|
}> = combineLatest([
|
||||||
this.connectionService.networkConnected$,
|
this.connectionService.networkConnected$,
|
||||||
this.websocket$.pipe(startWith(false)),
|
this.websocket$.pipe(startWith(false)),
|
||||||
|
this.patch
|
||||||
|
.watch$('server-info', 'status-info')
|
||||||
|
.pipe(startWith({ restarting: false, 'shutting-down': false })),
|
||||||
]).pipe(
|
]).pipe(
|
||||||
map(([network, websocket]) => {
|
map(([network, websocket, status]) => {
|
||||||
if (!network)
|
if (!network)
|
||||||
return {
|
return {
|
||||||
message: 'No Internet',
|
message: 'No Internet',
|
||||||
@@ -35,6 +40,20 @@ export class ConnectionBarComponent {
|
|||||||
icon: 'cloud-offline-outline',
|
icon: 'cloud-offline-outline',
|
||||||
dots: true,
|
dots: true,
|
||||||
}
|
}
|
||||||
|
if (status['shutting-down'])
|
||||||
|
return {
|
||||||
|
message: 'Shutting Down',
|
||||||
|
color: 'dark',
|
||||||
|
icon: 'power',
|
||||||
|
dots: true,
|
||||||
|
}
|
||||||
|
if (status.restarting)
|
||||||
|
return {
|
||||||
|
message: 'Restarting',
|
||||||
|
color: 'dark',
|
||||||
|
icon: 'power',
|
||||||
|
dots: true,
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
message: 'Connected',
|
message: 'Connected',
|
||||||
@@ -45,5 +64,8 @@ export class ConnectionBarComponent {
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
constructor(private readonly connectionService: ConnectionService) {}
|
constructor(
|
||||||
|
private readonly connectionService: ConnectionService,
|
||||||
|
private readonly patch: PatchDB<DataModel>,
|
||||||
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -352,7 +352,6 @@ export class ServerShowPage {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.embassyApi.restartServer({})
|
await this.embassyApi.restartServer({})
|
||||||
this.presentAlertInProgress(action, ` until ${action} completes.`)
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errToast.present(e)
|
this.errToast.present(e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -370,10 +369,6 @@ export class ServerShowPage {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.embassyApi.shutdownServer({})
|
await this.embassyApi.shutdownServer({})
|
||||||
this.presentAlertInProgress(
|
|
||||||
action,
|
|
||||||
'.<br /><br /><b>You will need to physically power cycle the device to regain connectivity.</b>',
|
|
||||||
)
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errToast.present(e)
|
this.errToast.present(e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -391,7 +386,6 @@ export class ServerShowPage {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.embassyApi.systemRebuild({})
|
await this.embassyApi.systemRebuild({})
|
||||||
this.presentAlertInProgress(action, ` until ${action} completes.`)
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
this.errToast.present(e)
|
this.errToast.present(e)
|
||||||
} finally {
|
} finally {
|
||||||
@@ -437,21 +431,6 @@ export class ServerShowPage {
|
|||||||
alert.present()
|
alert.present()
|
||||||
}
|
}
|
||||||
|
|
||||||
private async presentAlertInProgress(verb: string, message: string) {
|
|
||||||
const alert = await this.alertCtrl.create({
|
|
||||||
header: `${verb} In Progress...`,
|
|
||||||
message: `Stopping all services gracefully. This can take a while.<br /><br />If you have a speaker, your server will <b>♫ play a melody ♫</b> before shutting down. Your server will then become unreachable${message}`,
|
|
||||||
buttons: [
|
|
||||||
{
|
|
||||||
text: 'OK',
|
|
||||||
role: 'cancel',
|
|
||||||
cssClass: 'enter-click',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
alert.present()
|
|
||||||
}
|
|
||||||
|
|
||||||
settings: ServerSettings = {
|
settings: ServerSettings = {
|
||||||
Backups: [
|
Backups: [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ export module Mock {
|
|||||||
'backup-progress': null,
|
'backup-progress': null,
|
||||||
'update-progress': null,
|
'update-progress': null,
|
||||||
updated: true,
|
updated: true,
|
||||||
|
restarting: false,
|
||||||
|
'shutting-down': false,
|
||||||
}
|
}
|
||||||
export const MarketplaceEos: RR.GetMarketplaceEosRes = {
|
export const MarketplaceEos: RR.GetMarketplaceEosRes = {
|
||||||
version: '0.3.5',
|
version: '0.3.5',
|
||||||
|
|||||||
@@ -302,6 +302,27 @@ export class MockApiService extends ApiService {
|
|||||||
params: RR.RestartServerReq,
|
params: RR.RestartServerReq,
|
||||||
): Promise<RR.RestartServerRes> {
|
): Promise<RR.RestartServerRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
|
|
||||||
|
const patch = [
|
||||||
|
{
|
||||||
|
op: PatchOp.REPLACE,
|
||||||
|
path: '/server-info/status-info/restarting',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
this.mockRevision(patch)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const patch2 = [
|
||||||
|
{
|
||||||
|
op: PatchOp.REPLACE,
|
||||||
|
path: '/server-info/status-info/restarting',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
this.mockRevision(patch2)
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,14 +330,34 @@ export class MockApiService extends ApiService {
|
|||||||
params: RR.ShutdownServerReq,
|
params: RR.ShutdownServerReq,
|
||||||
): Promise<RR.ShutdownServerRes> {
|
): Promise<RR.ShutdownServerRes> {
|
||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
|
|
||||||
|
const patch = [
|
||||||
|
{
|
||||||
|
op: PatchOp.REPLACE,
|
||||||
|
path: '/server-info/status-info/shutting-down',
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
this.mockRevision(patch)
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
const patch2 = [
|
||||||
|
{
|
||||||
|
op: PatchOp.REPLACE,
|
||||||
|
path: '/server-info/status-info/shutting-down',
|
||||||
|
value: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
this.mockRevision(patch2)
|
||||||
|
}, 2000)
|
||||||
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
async systemRebuild(
|
async systemRebuild(
|
||||||
params: RR.RestartServerReq,
|
params: RR.SystemRebuildReq,
|
||||||
): Promise<RR.RestartServerRes> {
|
): Promise<RR.SystemRebuildRes> {
|
||||||
await pauseFor(2000)
|
return this.restartServer(params)
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async repairDisk(params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
|
async repairDisk(params: RR.RestartServerReq): Promise<RR.RestartServerRes> {
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ export const mockPatchData: DataModel = {
|
|||||||
'backup-progress': null,
|
'backup-progress': null,
|
||||||
updated: false,
|
updated: false,
|
||||||
'update-progress': null,
|
'update-progress': null,
|
||||||
|
restarting: false,
|
||||||
|
'shutting-down': false,
|
||||||
},
|
},
|
||||||
hostname: 'random-words',
|
hostname: 'random-words',
|
||||||
pubkey: 'npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m',
|
pubkey: 'npub1sg6plzptd64u62a878hep2kev88swjh3tw00gjsfl8f237lmu63q0uf63m',
|
||||||
|
|||||||
@@ -96,6 +96,8 @@ export interface ServerStatusInfo {
|
|||||||
}
|
}
|
||||||
updated: boolean
|
updated: boolean
|
||||||
'update-progress': { size: number | null; downloaded: number } | null
|
'update-progress': { size: number | null; downloaded: number } | null
|
||||||
|
restarting: boolean
|
||||||
|
'shutting-down': boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ServerStatus {
|
export enum ServerStatus {
|
||||||
|
|||||||
Reference in New Issue
Block a user