mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 20:14:49 +00:00
feat: unified restart notification with reason-specific messaging
Replace statusInfo.updated (bool) with serverInfo.restart (nullable enum) to unify all restart-needed scenarios under a single PatchDB field. Backend sets the restart reason in RPC handlers for hostname change (mdns), language change, kiosk toggle, and OS update download. Init clears it on boot. The update flow checks this field to prevent updates when a restart is already pending. Frontend shows a persistent action bar with reason-specific i18n messages instead of per-feature restart dialogs. For .local hostname changes, the existing "open new address" dialog is preserved — the restart toast appears after the user logs in on the new address. Also includes migration in v0_4_0_alpha_23 to remove statusInfo.updated and initialize serverInfo.restart. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -125,7 +125,6 @@ impl Public {
|
||||
},
|
||||
status_info: ServerStatus {
|
||||
backup_progress: None,
|
||||
updated: false,
|
||||
update_progress: None,
|
||||
shutting_down: false,
|
||||
restarting: false,
|
||||
@@ -152,6 +151,7 @@ impl Public {
|
||||
kiosk: Some(kiosk).filter(|_| &*PLATFORM != "raspberrypi"),
|
||||
language,
|
||||
keyboard,
|
||||
restart: None,
|
||||
},
|
||||
package_data: AllPackageData::default(),
|
||||
ui: serde_json::from_str(*DB_UI_SEED_CELL.get().unwrap_or(&"null"))
|
||||
@@ -218,6 +218,18 @@ pub struct ServerInfo {
|
||||
pub kiosk: Option<bool>,
|
||||
pub language: Option<InternedString>,
|
||||
pub keyboard: Option<KeyboardOptions>,
|
||||
#[serde(default)]
|
||||
pub restart: Option<RestartReason>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[ts(export)]
|
||||
pub enum RestartReason {
|
||||
Mdns,
|
||||
Language,
|
||||
Kiosk,
|
||||
Update,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Deserialize, Serialize, HasModel, TS)]
|
||||
@@ -364,7 +376,6 @@ pub struct BackupProgress {
|
||||
#[ts(export)]
|
||||
pub struct ServerStatus {
|
||||
pub backup_progress: Option<BTreeMap<PackageId, BackupProgress>>,
|
||||
pub updated: bool,
|
||||
pub update_progress: Option<FullProgress>,
|
||||
#[serde(default)]
|
||||
pub shutting_down: bool,
|
||||
|
||||
@@ -7,7 +7,7 @@ use tracing::instrument;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::context::RpcContext;
|
||||
use crate::db::model::public::ServerInfo;
|
||||
use crate::db::model::public::{RestartReason, ServerInfo};
|
||||
use crate::prelude::*;
|
||||
use crate::util::Invoke;
|
||||
|
||||
@@ -272,6 +272,7 @@ pub async fn set_hostname_rpc(
|
||||
}
|
||||
if let Some(hostname) = &hostname {
|
||||
hostname.save(server_info)?;
|
||||
server_info.as_restart_mut().ser(&Some(RestartReason::Mdns))?;
|
||||
}
|
||||
ServerHostnameInfo::load(server_info)
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@ use crate::account::AccountInfo;
|
||||
use crate::context::config::ServerConfig;
|
||||
use crate::context::{CliContext, InitContext, RpcContext};
|
||||
use crate::db::model::Database;
|
||||
use crate::db::model::public::ServerStatus;
|
||||
use crate::db::model::public::{RestartReason, ServerStatus};
|
||||
use crate::developer::OS_DEVELOPER_KEY_PATH;
|
||||
use crate::hostname::ServerHostname;
|
||||
use crate::middleware::auth::local::LocalAuthContext;
|
||||
@@ -371,7 +371,6 @@ pub async fn init(
|
||||
let ram = get_mem_info().await?.total.0 as u64 * 1024 * 1024;
|
||||
let devices = lshw().await?;
|
||||
let status_info = ServerStatus {
|
||||
updated: false,
|
||||
update_progress: None,
|
||||
backup_progress: None,
|
||||
shutting_down: false,
|
||||
@@ -379,6 +378,7 @@ pub async fn init(
|
||||
};
|
||||
db.mutate(|v| {
|
||||
let server_info = v.as_public_mut().as_server_info_mut();
|
||||
server_info.as_restart_mut().ser(&None::<RestartReason>)?;
|
||||
server_info.as_ntp_synced_mut().ser(&ntp_synced)?;
|
||||
server_info.as_ram_mut().ser(&ram)?;
|
||||
server_info.as_devices_mut().ser(&devices)?;
|
||||
|
||||
@@ -16,6 +16,7 @@ use ts_rs::TS;
|
||||
|
||||
use crate::bins::set_locale;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::public::RestartReason;
|
||||
use crate::disk::util::{get_available, get_used};
|
||||
use crate::logs::{LogSource, LogsParams, SYSTEM_UNIT};
|
||||
use crate::prelude::*;
|
||||
@@ -351,10 +352,9 @@ pub fn kiosk<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(|ctx: RpcContext| async move {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_kiosk_mut()
|
||||
.ser(&Some(true))
|
||||
let server_info = db.as_public_mut().as_server_info_mut();
|
||||
server_info.as_kiosk_mut().ser(&Some(true))?;
|
||||
server_info.as_restart_mut().ser(&Some(RestartReason::Kiosk))
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
@@ -369,10 +369,9 @@ pub fn kiosk<C: Context>() -> ParentHandler<C> {
|
||||
from_fn_async(|ctx: RpcContext| async move {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_kiosk_mut()
|
||||
.ser(&Some(false))
|
||||
let server_info = db.as_public_mut().as_server_info_mut();
|
||||
server_info.as_kiosk_mut().ser(&Some(false))?;
|
||||
server_info.as_restart_mut().ser(&Some(RestartReason::Kiosk))
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
@@ -1367,10 +1366,11 @@ pub async fn set_language(
|
||||
save_language(&*language).await?;
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
let server_info = db.as_public_mut().as_server_info_mut();
|
||||
server_info
|
||||
.as_language_mut()
|
||||
.ser(&Some(language.clone()))
|
||||
.ser(&Some(language.clone()))?;
|
||||
server_info.as_restart_mut().ser(&Some(RestartReason::Language))
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
|
||||
@@ -19,6 +19,7 @@ use ts_rs::TS;
|
||||
|
||||
use crate::PLATFORM;
|
||||
use crate::context::{CliContext, RpcContext};
|
||||
use crate::db::model::public::RestartReason;
|
||||
use crate::notifications::{NotificationLevel, notify};
|
||||
use crate::prelude::*;
|
||||
use crate::progress::{
|
||||
@@ -80,9 +81,9 @@ pub async fn update_system(
|
||||
.await
|
||||
.into_public()
|
||||
.into_server_info()
|
||||
.into_status_info()
|
||||
.into_updated()
|
||||
.into_restart()
|
||||
.de()?
|
||||
.is_some()
|
||||
{
|
||||
return Err(Error::new(
|
||||
eyre!("{}", t!("update.already-updated-restart-required")),
|
||||
@@ -281,10 +282,18 @@ async fn maybe_do_update(
|
||||
|
||||
let start_progress = progress.snapshot();
|
||||
|
||||
let status = ctx
|
||||
.db
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
let mut status = peeked.as_public().as_server_info().as_status_info().de()?;
|
||||
let server_info = db.as_public_mut().as_server_info_mut();
|
||||
|
||||
if server_info.as_restart().de()?.is_some() {
|
||||
return Err(Error::new(
|
||||
eyre!("{}", t!("update.already-updated-restart-required")),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
|
||||
let mut status = server_info.as_status_info().de()?;
|
||||
if status.update_progress.is_some() {
|
||||
return Err(Error::new(
|
||||
eyre!("{}", t!("update.already-updating")),
|
||||
@@ -293,22 +302,12 @@ async fn maybe_do_update(
|
||||
}
|
||||
|
||||
status.update_progress = Some(start_progress);
|
||||
db.as_public_mut()
|
||||
.as_server_info_mut()
|
||||
.as_status_info_mut()
|
||||
.ser(&status)?;
|
||||
Ok(status)
|
||||
server_info.as_status_info_mut().ser(&status)?;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
|
||||
if status.updated {
|
||||
return Err(Error::new(
|
||||
eyre!("{}", t!("update.already-updated-restart-required")),
|
||||
crate::ErrorKind::InvalidRequest,
|
||||
));
|
||||
}
|
||||
|
||||
let progress_task = NonDetachingJoinHandle::from(tokio::spawn(progress.clone().sync_to_db(
|
||||
ctx.db.clone(),
|
||||
|db| {
|
||||
@@ -338,10 +337,12 @@ async fn maybe_do_update(
|
||||
Ok(()) => {
|
||||
ctx.db
|
||||
.mutate(|db| {
|
||||
let status_info =
|
||||
db.as_public_mut().as_server_info_mut().as_status_info_mut();
|
||||
status_info.as_update_progress_mut().ser(&None)?;
|
||||
status_info.as_updated_mut().ser(&true)
|
||||
let server_info = db.as_public_mut().as_server_info_mut();
|
||||
server_info
|
||||
.as_status_info_mut()
|
||||
.as_update_progress_mut()
|
||||
.ser(&None)?;
|
||||
server_info.as_restart_mut().ser(&Some(RestartReason::Update))
|
||||
})
|
||||
.await
|
||||
.result?;
|
||||
|
||||
@@ -28,7 +28,13 @@ impl VersionT for Version {
|
||||
&V0_3_0_COMPAT
|
||||
}
|
||||
#[instrument(skip_all)]
|
||||
fn up(self, _db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||
fn up(self, db: &mut Value, _: Self::PreUpRes) -> Result<Value, Error> {
|
||||
db["public"]["serverInfo"]["statusInfo"]
|
||||
.as_object_mut()
|
||||
.map(|m| m.remove("updated"));
|
||||
|
||||
db["public"]["serverInfo"]["restart"] = Value::Null;
|
||||
|
||||
Ok(Value::Null)
|
||||
}
|
||||
fn down(self, _db: &mut Value) -> Result<(), Error> {
|
||||
|
||||
Reference in New Issue
Block a user