mirror of
https://github.com/Start9Labs/start-os.git
synced 2026-03-30 12:11:56 +00:00
v0.3.4
remove health checks from manifest remove "restarting" bool on "starting" status remove restarting attr
This commit is contained in:
@@ -308,14 +308,12 @@ impl RpcContext {
|
|||||||
let main = match status.main {
|
let main = match status.main {
|
||||||
MainStatus::BackingUp { started, .. } => {
|
MainStatus::BackingUp { started, .. } => {
|
||||||
if let Some(_) = started {
|
if let Some(_) = started {
|
||||||
MainStatus::Starting { restarting: false }
|
MainStatus::Starting
|
||||||
} else {
|
} else {
|
||||||
MainStatus::Stopped
|
MainStatus::Stopped
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MainStatus::Running { .. } => {
|
MainStatus::Running { .. } => MainStatus::Starting,
|
||||||
MainStatus::Starting { restarting: false }
|
|
||||||
}
|
|
||||||
a => a.clone(),
|
a => a.clone(),
|
||||||
};
|
};
|
||||||
let new_package = PackageDataEntry::Installed {
|
let new_package = PackageDataEntry::Installed {
|
||||||
|
|||||||
@@ -67,10 +67,7 @@ pub async fn start(#[context] ctx: RpcContext, #[arg] id: PackageId) -> Result<(
|
|||||||
let mut tx = db.begin().await?;
|
let mut tx = db.begin().await?;
|
||||||
let receipts = StartReceipts::new(&mut tx, &id).await?;
|
let receipts = StartReceipts::new(&mut tx, &id).await?;
|
||||||
let version = receipts.version.get(&mut tx).await?;
|
let version = receipts.version.get(&mut tx).await?;
|
||||||
receipts
|
receipts.status.set(&mut tx, MainStatus::Starting).await?;
|
||||||
.status
|
|
||||||
.set(&mut tx, MainStatus::Starting { restarting: false })
|
|
||||||
.await?;
|
|
||||||
heal_all_dependents_transitive(&ctx, &mut tx, &id, &receipts.dependency_receipt).await?;
|
heal_all_dependents_transitive(&ctx, &mut tx, &id, &receipts.dependency_receipt).await?;
|
||||||
|
|
||||||
tx.commit().await?;
|
tx.commit().await?;
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ impl Database {
|
|||||||
server_info: ServerInfo {
|
server_info: ServerInfo {
|
||||||
id: account.server_id.clone(),
|
id: account.server_id.clone(),
|
||||||
version: Current::new().semver().into(),
|
version: Current::new().semver().into(),
|
||||||
hostname: Some(account.hostname.no_dot_host_name()),
|
hostname: account.hostname.no_dot_host_name(),
|
||||||
last_backup: None,
|
last_backup: None,
|
||||||
last_wifi_region: None,
|
last_wifi_region: None,
|
||||||
eos_version_compat: Current::new().compat().clone(),
|
eos_version_compat: Current::new().compat().clone(),
|
||||||
@@ -98,7 +98,7 @@ impl DatabaseModel {
|
|||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct ServerInfo {
|
pub struct ServerInfo {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub hostname: Option<String>,
|
pub hostname: String,
|
||||||
pub version: Version,
|
pub version: Version,
|
||||||
pub last_backup: Option<DateTime<Utc>>,
|
pub last_backup: Option<DateTime<Utc>>,
|
||||||
/// Used in the wifi to determine the region to set the system to
|
/// Used in the wifi to determine the region to set the system to
|
||||||
|
|||||||
@@ -161,12 +161,7 @@ async fn save_state(
|
|||||||
set_status(&mut db, &seed.manifest, &MainStatus::Stopping).await
|
set_status(&mut db, &seed.manifest, &MainStatus::Stopping).await
|
||||||
}
|
}
|
||||||
(None, StartStop::Stop, StartStop::Start) => {
|
(None, StartStop::Stop, StartStop::Start) => {
|
||||||
set_status(
|
set_status(&mut db, &seed.manifest, &MainStatus::Starting).await
|
||||||
&mut db,
|
|
||||||
&seed.manifest,
|
|
||||||
&MainStatus::Starting { restarting: false },
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
}
|
}
|
||||||
(None, StartStop::Stop, StartStop::Stop) => {
|
(None, StartStop::Stop, StartStop::Stop) => {
|
||||||
set_status(&mut db, &seed.manifest, &MainStatus::Stopped).await
|
set_status(&mut db, &seed.manifest, &MainStatus::Stopped).await
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
use std::collections::BTreeMap;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use chrono::Utc;
|
|
||||||
|
|
||||||
use super::{pause, resume, start, stop, ManagerSharedState, Status};
|
|
||||||
use crate::status::MainStatus;
|
|
||||||
use crate::Error;
|
|
||||||
|
|
||||||
/// Allocates a db handle. DO NOT CALL with a db handle already in scope
|
|
||||||
async fn synchronize_once(shared: &ManagerSharedState) -> Result<Status, Error> {
|
|
||||||
let mut db = shared.seed.ctx.db.handle();
|
|
||||||
let mut status = crate::db::DatabaseModel::new()
|
|
||||||
.package_data()
|
|
||||||
.idx_model(&shared.seed.manifest.id)
|
|
||||||
.expect(&mut db)
|
|
||||||
.await?
|
|
||||||
.installed()
|
|
||||||
.expect(&mut db)
|
|
||||||
.await?
|
|
||||||
.status()
|
|
||||||
.main()
|
|
||||||
.get_mut(&mut db)
|
|
||||||
.await?;
|
|
||||||
let manager_status = *shared.status.1.borrow();
|
|
||||||
match manager_status {
|
|
||||||
Status::Stopped => match &mut *status {
|
|
||||||
MainStatus::Stopped => (),
|
|
||||||
MainStatus::Stopping => {
|
|
||||||
*status = MainStatus::Stopped;
|
|
||||||
}
|
|
||||||
MainStatus::Restarting => {
|
|
||||||
*status = MainStatus::Starting { restarting: true };
|
|
||||||
}
|
|
||||||
MainStatus::Starting { .. } => {
|
|
||||||
start(shared).await?;
|
|
||||||
}
|
|
||||||
MainStatus::Running { started, .. } => {
|
|
||||||
*started = Utc::now();
|
|
||||||
start(shared).await?;
|
|
||||||
}
|
|
||||||
MainStatus::BackingUp { .. } => (),
|
|
||||||
},
|
|
||||||
Status::Starting => match *status {
|
|
||||||
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
|
||||||
stop(shared).await?;
|
|
||||||
}
|
|
||||||
MainStatus::Starting { .. } | MainStatus::Running { .. } => (),
|
|
||||||
MainStatus::BackingUp { .. } => {
|
|
||||||
pause(shared).await?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Status::Running => match *status {
|
|
||||||
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
|
||||||
stop(shared).await?;
|
|
||||||
}
|
|
||||||
MainStatus::Starting { .. } => {
|
|
||||||
*status = MainStatus::Running {
|
|
||||||
started: Utc::now(),
|
|
||||||
health: BTreeMap::new(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
MainStatus::Running { .. } => (),
|
|
||||||
MainStatus::BackingUp { .. } => {
|
|
||||||
pause(shared).await?;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Status::Paused => match *status {
|
|
||||||
MainStatus::Stopped | MainStatus::Stopping | MainStatus::Restarting => {
|
|
||||||
stop(shared).await?;
|
|
||||||
}
|
|
||||||
MainStatus::Starting { .. } | MainStatus::Running { .. } => {
|
|
||||||
resume(shared).await?;
|
|
||||||
}
|
|
||||||
MainStatus::BackingUp { .. } => (),
|
|
||||||
},
|
|
||||||
Status::Shutdown => (),
|
|
||||||
}
|
|
||||||
status.save(&mut db).await?;
|
|
||||||
Ok(manager_status)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn synchronizer(shared: &ManagerSharedState) {
|
|
||||||
let mut status_recv = shared.status.0.subscribe();
|
|
||||||
loop {
|
|
||||||
tokio::select! {
|
|
||||||
_ = tokio::time::sleep(Duration::from_secs(5)) => (),
|
|
||||||
_ = shared.synchronize_now.notified() => (),
|
|
||||||
_ = status_recv.changed() => (),
|
|
||||||
}
|
|
||||||
let status = match synchronize_once(shared).await {
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!(
|
|
||||||
"Synchronizer for {}@{} failed: {}",
|
|
||||||
shared.seed.manifest.id,
|
|
||||||
shared.seed.manifest.version,
|
|
||||||
e
|
|
||||||
);
|
|
||||||
tracing::debug!("{:?}", e);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Ok(status) => status,
|
|
||||||
};
|
|
||||||
tracing::trace!("{} status synchronized", shared.seed.manifest.id);
|
|
||||||
shared.synchronized.notify_waiters();
|
|
||||||
match status {
|
|
||||||
Status::Shutdown => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -554,181 +554,182 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let package_id = "test-package".parse().unwrap();
|
let package_id = "test-package".parse().unwrap();
|
||||||
let package_version: Version = "0.3.0.3".parse().unwrap();
|
let package_version: Version = "0.3.0.3".parse().unwrap();
|
||||||
let name = ProcedureName::Action("test-deep-dir".parse().unwrap());
|
let name = ProcedureName::Action("test-deep-dir".parse().unwrap());
|
||||||
let volumes: Volumes = serde_json::from_value(serde_json::json!({
|
let volumes: Volumes = serde_json::from_value(serde_json::json!({
|
||||||
"main": {
|
"main": {
|
||||||
"type": "data"
|
"type": "data"
|
||||||
},
|
},
|
||||||
"compat": {
|
"compat": {
|
||||||
"type": "assets"
|
"type": "assets"
|
||||||
},
|
},
|
||||||
"filebrowser" :{
|
"filebrowser" :{
|
||||||
"package-id": "filebrowser",
|
"package-id": "filebrowser",
|
||||||
"path": "data",
|
"path": "data",
|
||||||
"readonly": true,
|
"readonly": true,
|
||||||
"type": "pointer",
|
"type": "pointer",
|
||||||
"volume-id": "main",
|
"volume-id": "main",
|
||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
.unwrap();
|
|
||||||
let input: Option<serde_json::Value> = None;
|
|
||||||
let timeout = Some(Duration::from_secs(10));
|
|
||||||
js_action
|
|
||||||
.execute::<serde_json::Value, serde_json::Value>(
|
|
||||||
&path,
|
|
||||||
&package_id,
|
|
||||||
&package_version,
|
|
||||||
name,
|
|
||||||
&volumes,
|
|
||||||
input,
|
|
||||||
timeout,
|
|
||||||
ProcessGroupId(0),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
let input: Option<serde_json::Value> = None;
|
||||||
#[tokio::test]
|
let timeout = Some(Duration::from_secs(10));
|
||||||
async fn js_action_test_deep_dir_escape() {
|
js_action
|
||||||
let js_action = JsProcedure { args: vec![] };
|
.execute::<serde_json::Value, serde_json::Value>(
|
||||||
let path: PathBuf = "test/js_action_execute/"
|
&path,
|
||||||
.parse::<PathBuf>()
|
&package_id,
|
||||||
.unwrap()
|
&package_version,
|
||||||
.canonicalize()
|
name,
|
||||||
|
&volumes,
|
||||||
|
input,
|
||||||
|
timeout,
|
||||||
|
ProcessGroupId(0),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
#[tokio::test]
|
||||||
|
async fn js_action_test_deep_dir_escape() {
|
||||||
|
let js_action = JsProcedure { args: vec![] };
|
||||||
|
let path: PathBuf = "test/js_action_execute/"
|
||||||
|
.parse::<PathBuf>()
|
||||||
|
.unwrap()
|
||||||
|
.canonicalize()
|
||||||
|
.unwrap();
|
||||||
|
let package_id = "test-package".parse().unwrap();
|
||||||
|
let package_version: Version = "0.3.0.3".parse().unwrap();
|
||||||
|
let name = ProcedureName::Action("test-deep-dir-escape".parse().unwrap());
|
||||||
|
let volumes: Volumes = serde_json::from_value(serde_json::json!({
|
||||||
|
"main": {
|
||||||
|
"type": "data"
|
||||||
|
},
|
||||||
|
"compat": {
|
||||||
|
"type": "assets"
|
||||||
|
},
|
||||||
|
"filebrowser" :{
|
||||||
|
"package-id": "filebrowser",
|
||||||
|
"path": "data",
|
||||||
|
"readonly": true,
|
||||||
|
"type": "pointer",
|
||||||
|
"volume-id": "main",
|
||||||
|
}
|
||||||
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let package_id = "test-package".parse().unwrap();
|
let input: Option<serde_json::Value> = None;
|
||||||
let package_version: Version = "0.3.0.3".parse().unwrap();
|
let timeout = Some(Duration::from_secs(10));
|
||||||
let name = ProcedureName::Action("test-deep-dir-escape".parse().unwrap());
|
js_action
|
||||||
let volumes: Volumes = serde_json::from_value(serde_json::json!({
|
.execute::<serde_json::Value, serde_json::Value>(
|
||||||
"main": {
|
&path,
|
||||||
"type": "data"
|
&package_id,
|
||||||
},
|
&package_version,
|
||||||
"compat": {
|
name,
|
||||||
"type": "assets"
|
&volumes,
|
||||||
},
|
input,
|
||||||
"filebrowser" :{
|
timeout,
|
||||||
"package-id": "filebrowser",
|
ProcessGroupId(0),
|
||||||
"path": "data",
|
None,
|
||||||
"readonly": true,
|
None,
|
||||||
"type": "pointer",
|
)
|
||||||
"volume-id": "main",
|
.await
|
||||||
}
|
.unwrap()
|
||||||
}))
|
.unwrap();
|
||||||
.unwrap();
|
}
|
||||||
let input: Option<serde_json::Value> = None;
|
#[tokio::test]
|
||||||
let timeout = Some(Duration::from_secs(10));
|
async fn js_action_test_zero_dir() {
|
||||||
js_action
|
let js_action = JsProcedure { args: vec![] };
|
||||||
.execute::<serde_json::Value, serde_json::Value>(
|
let path: PathBuf = "test/js_action_execute/"
|
||||||
&path,
|
.parse::<PathBuf>()
|
||||||
&package_id,
|
.unwrap()
|
||||||
&package_version,
|
.canonicalize()
|
||||||
name,
|
.unwrap();
|
||||||
&volumes,
|
let package_id = "test-package".parse().unwrap();
|
||||||
input,
|
let package_version: Version = "0.3.0.3".parse().unwrap();
|
||||||
timeout,
|
let name = ProcedureName::Action("test-zero-dir".parse().unwrap());
|
||||||
ProcessGroupId(0),
|
let volumes: Volumes = serde_json::from_value(serde_json::json!({
|
||||||
None,
|
"main": {
|
||||||
None,
|
"type": "data"
|
||||||
)
|
},
|
||||||
.await
|
"compat": {
|
||||||
.unwrap()
|
"type": "assets"
|
||||||
|
},
|
||||||
|
"filebrowser" :{
|
||||||
|
"package-id": "filebrowser",
|
||||||
|
"path": "data",
|
||||||
|
"readonly": true,
|
||||||
|
"type": "pointer",
|
||||||
|
"volume-id": "main",
|
||||||
|
}
|
||||||
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
let input: Option<serde_json::Value> = None;
|
||||||
#[tokio::test]
|
let timeout = Some(Duration::from_secs(10));
|
||||||
async fn js_action_test_zero_dir() {
|
js_action
|
||||||
let js_action = JsProcedure { args: vec![] };
|
.execute::<serde_json::Value, serde_json::Value>(
|
||||||
let path: PathBuf = "test/js_action_execute/"
|
&path,
|
||||||
.parse::<PathBuf>()
|
&package_id,
|
||||||
.unwrap()
|
&package_version,
|
||||||
.canonicalize()
|
name,
|
||||||
|
&volumes,
|
||||||
|
input,
|
||||||
|
timeout,
|
||||||
|
ProcessGroupId(0),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
#[tokio::test]
|
||||||
|
async fn js_action_test_read_dir() {
|
||||||
|
let js_action = JsProcedure { args: vec![] };
|
||||||
|
let path: PathBuf = "test/js_action_execute/"
|
||||||
|
.parse::<PathBuf>()
|
||||||
|
.unwrap()
|
||||||
|
.canonicalize()
|
||||||
|
.unwrap();
|
||||||
|
let package_id = "test-package".parse().unwrap();
|
||||||
|
let package_version: Version = "0.3.0.3".parse().unwrap();
|
||||||
|
let name = ProcedureName::Action("test-read-dir".parse().unwrap());
|
||||||
|
let volumes: Volumes = serde_json::from_value(serde_json::json!({
|
||||||
|
"main": {
|
||||||
|
"type": "data"
|
||||||
|
},
|
||||||
|
"compat": {
|
||||||
|
"type": "assets"
|
||||||
|
},
|
||||||
|
"filebrowser" :{
|
||||||
|
"package-id": "filebrowser",
|
||||||
|
"path": "data",
|
||||||
|
"readonly": true,
|
||||||
|
"type": "pointer",
|
||||||
|
"volume-id": "main",
|
||||||
|
}
|
||||||
|
}))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let package_id = "test-package".parse().unwrap();
|
let input: Option<serde_json::Value> = None;
|
||||||
let package_version: Version = "0.3.0.3".parse().unwrap();
|
let timeout = Some(Duration::from_secs(10));
|
||||||
let name = ProcedureName::Action("test-zero-dir".parse().unwrap());
|
js_action
|
||||||
let volumes: Volumes = serde_json::from_value(serde_json::json!({
|
.execute::<serde_json::Value, serde_json::Value>(
|
||||||
"main": {
|
&path,
|
||||||
"type": "data"
|
&package_id,
|
||||||
},
|
&package_version,
|
||||||
"compat": {
|
name,
|
||||||
"type": "assets"
|
&volumes,
|
||||||
},
|
input,
|
||||||
"filebrowser" :{
|
timeout,
|
||||||
"package-id": "filebrowser",
|
ProcessGroupId(0),
|
||||||
"path": "data",
|
None,
|
||||||
"readonly": true,
|
None,
|
||||||
"type": "pointer",
|
)
|
||||||
"volume-id": "main",
|
.await
|
||||||
}
|
.unwrap()
|
||||||
}))
|
.unwrap();
|
||||||
.unwrap();
|
}
|
||||||
let input: Option<serde_json::Value> = None;
|
|
||||||
let timeout = Some(Duration::from_secs(10));
|
|
||||||
js_action
|
|
||||||
.execute::<serde_json::Value, serde_json::Value>(
|
|
||||||
&path,
|
|
||||||
&package_id,
|
|
||||||
&package_version,
|
|
||||||
name,
|
|
||||||
&volumes,
|
|
||||||
input,
|
|
||||||
timeout,
|
|
||||||
ProcessGroupId(0),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
#[tokio::test]
|
|
||||||
async fn js_action_test_read_dir() {
|
|
||||||
let js_action = JsProcedure { args: vec![] };
|
|
||||||
let path: PathBuf = "test/js_action_execute/"
|
|
||||||
.parse::<PathBuf>()
|
|
||||||
.unwrap()
|
|
||||||
.canonicalize()
|
|
||||||
.unwrap();
|
|
||||||
let package_id = "test-package".parse().unwrap();
|
|
||||||
let package_version: Version = "0.3.0.3".parse().unwrap();
|
|
||||||
let name = ProcedureName::Action("test-read-dir".parse().unwrap());
|
|
||||||
let volumes: Volumes = serde_json::from_value(serde_json::json!({
|
|
||||||
"main": {
|
|
||||||
"type": "data"
|
|
||||||
},
|
|
||||||
"compat": {
|
|
||||||
"type": "assets"
|
|
||||||
},
|
|
||||||
"filebrowser" :{
|
|
||||||
"package-id": "filebrowser",
|
|
||||||
"path": "data",
|
|
||||||
"readonly": true,
|
|
||||||
"type": "pointer",
|
|
||||||
"volume-id": "main",
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.unwrap();
|
|
||||||
let input: Option<serde_json::Value> = None;
|
|
||||||
let timeout = Some(Duration::from_secs(10));
|
|
||||||
js_action
|
|
||||||
.execute::<serde_json::Value, serde_json::Value>(
|
|
||||||
&path,
|
|
||||||
&package_id,
|
|
||||||
&package_version,
|
|
||||||
name,
|
|
||||||
&volumes,
|
|
||||||
input,
|
|
||||||
timeout,
|
|
||||||
ProcessGroupId(0),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn js_action_test_deep_dir() {
|
async fn js_action_test_deep_dir() {
|
||||||
|
|||||||
@@ -26,9 +26,7 @@ pub enum MainStatus {
|
|||||||
Stopped,
|
Stopped,
|
||||||
Restarting,
|
Restarting,
|
||||||
Stopping,
|
Stopping,
|
||||||
Starting {
|
Starting,
|
||||||
restarting: bool,
|
|
||||||
},
|
|
||||||
Running {
|
Running {
|
||||||
started: DateTime<Utc>,
|
started: DateTime<Utc>,
|
||||||
health: BTreeMap<HealthCheckId, HealthCheckResult>,
|
health: BTreeMap<HealthCheckId, HealthCheckResult>,
|
||||||
|
|||||||
@@ -9,40 +9,17 @@ use sqlx::PgPool;
|
|||||||
use crate::init::InitReceipts;
|
use crate::init::InitReceipts;
|
||||||
use crate::Error;
|
use crate::Error;
|
||||||
|
|
||||||
mod v0_3_0;
|
|
||||||
mod v0_3_0_1;
|
|
||||||
mod v0_3_0_2;
|
|
||||||
mod v0_3_0_3;
|
|
||||||
mod v0_3_1;
|
|
||||||
mod v0_3_1_1;
|
|
||||||
mod v0_3_1_2;
|
|
||||||
mod v0_3_2;
|
|
||||||
mod v0_3_2_1;
|
|
||||||
mod v0_3_3;
|
|
||||||
mod v0_3_4;
|
|
||||||
mod v0_3_4_1;
|
|
||||||
mod v0_3_4_2;
|
|
||||||
mod v0_3_4_3;
|
mod v0_3_4_3;
|
||||||
|
mod v0_4_0;
|
||||||
|
|
||||||
pub type Current = v0_3_4_3::Version;
|
pub type Current = v0_4_0::Version;
|
||||||
|
|
||||||
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)]
|
||||||
#[serde(untagged)]
|
#[serde(untagged)]
|
||||||
enum Version {
|
enum Version {
|
||||||
V0_3_0(Wrapper<v0_3_0::Version>),
|
LT0_3_4_3(LTWrapper<v0_3_4_3::Version>),
|
||||||
V0_3_0_1(Wrapper<v0_3_0_1::Version>),
|
|
||||||
V0_3_0_2(Wrapper<v0_3_0_2::Version>),
|
|
||||||
V0_3_0_3(Wrapper<v0_3_0_3::Version>),
|
|
||||||
V0_3_1(Wrapper<v0_3_1::Version>),
|
|
||||||
V0_3_1_1(Wrapper<v0_3_1_1::Version>),
|
|
||||||
V0_3_1_2(Wrapper<v0_3_1_2::Version>),
|
|
||||||
V0_3_2(Wrapper<v0_3_2::Version>),
|
|
||||||
V0_3_2_1(Wrapper<v0_3_2_1::Version>),
|
|
||||||
V0_3_3(Wrapper<v0_3_3::Version>),
|
|
||||||
V0_3_4(Wrapper<v0_3_4::Version>),
|
|
||||||
V0_3_4_1(Wrapper<v0_3_4_1::Version>),
|
|
||||||
V0_3_4_2(Wrapper<v0_3_4_2::Version>),
|
|
||||||
V0_3_4_3(Wrapper<v0_3_4_3::Version>),
|
V0_3_4_3(Wrapper<v0_3_4_3::Version>),
|
||||||
|
V0_4_0(Wrapper<v0_4_0::Version>),
|
||||||
Other(emver::Version),
|
Other(emver::Version),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,20 +35,9 @@ impl Version {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn as_sem_ver(&self) -> emver::Version {
|
fn as_sem_ver(&self) -> emver::Version {
|
||||||
match self {
|
match self {
|
||||||
Version::V0_3_0(Wrapper(x)) => x.semver(),
|
Version::LT0_3_4_3(LTWrapper(x)) => x.semver(),
|
||||||
Version::V0_3_0_1(Wrapper(x)) => x.semver(),
|
|
||||||
Version::V0_3_0_2(Wrapper(x)) => x.semver(),
|
|
||||||
Version::V0_3_0_3(Wrapper(x)) => x.semver(),
|
|
||||||
Version::V0_3_1(Wrapper(x)) => x.semver(),
|
|
||||||
Version::V0_3_1_1(Wrapper(x)) => x.semver(),
|
|
||||||
Version::V0_3_1_2(Wrapper(x)) => x.semver(),
|
|
||||||
Version::V0_3_2(Wrapper(x)) => x.semver(),
|
|
||||||
Version::V0_3_2_1(Wrapper(x)) => x.semver(),
|
|
||||||
Version::V0_3_3(Wrapper(x)) => x.semver(),
|
|
||||||
Version::V0_3_4(Wrapper(x)) => x.semver(),
|
|
||||||
Version::V0_3_4_1(Wrapper(x)) => x.semver(),
|
|
||||||
Version::V0_3_4_2(Wrapper(x)) => x.semver(),
|
|
||||||
Version::V0_3_4_3(Wrapper(x)) => x.semver(),
|
Version::V0_3_4_3(Wrapper(x)) => x.semver(),
|
||||||
|
Version::V0_4_0(Wrapper(x)) => x.semver(),
|
||||||
Version::Other(x) => x.clone(),
|
Version::Other(x) => x.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -177,6 +143,32 @@ where
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct LTWrapper<T>(T, emver::Version);
|
||||||
|
impl<T> serde::Serialize for LTWrapper<T>
|
||||||
|
where
|
||||||
|
T: VersionT,
|
||||||
|
{
|
||||||
|
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
|
||||||
|
self.0.semver().serialize(serializer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<'de, T> serde::Deserialize<'de> for LTWrapper<T>
|
||||||
|
where
|
||||||
|
T: VersionT,
|
||||||
|
{
|
||||||
|
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
|
||||||
|
let v = crate::util::Version::deserialize(deserializer)?;
|
||||||
|
let version = T::new();
|
||||||
|
if *v < version.semver() {
|
||||||
|
Ok(Self(version, v.into_version()))
|
||||||
|
} else {
|
||||||
|
Err(serde::de::Error::custom("Mismatched Version"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
struct Wrapper<T>(T);
|
struct Wrapper<T>(T);
|
||||||
impl<T> serde::Serialize for Wrapper<T>
|
impl<T> serde::Serialize for Wrapper<T>
|
||||||
@@ -209,62 +201,20 @@ pub async fn init<Db: DbHandle>(
|
|||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let version = Version::from_util_version(receipts.server_version.get(db).await?);
|
let version = Version::from_util_version(receipts.server_version.get(db).await?);
|
||||||
match version {
|
match version {
|
||||||
Version::V0_3_0(v) => {
|
Version::LT0_3_4_3(_) => {
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
return Err(Error::new(
|
||||||
.await?
|
eyre!("Cannot migrate from pre-0.3.4. Please update to v0.3.4 first."),
|
||||||
}
|
crate::ErrorKind::MigrationFailed,
|
||||||
Version::V0_3_0_1(v) => {
|
));
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
Version::V0_3_0_2(v) => {
|
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
Version::V0_3_0_3(v) => {
|
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
Version::V0_3_1(v) => {
|
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
Version::V0_3_1_1(v) => {
|
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
Version::V0_3_1_2(v) => {
|
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
Version::V0_3_2(v) => {
|
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
Version::V0_3_2_1(v) => {
|
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
Version::V0_3_3(v) => {
|
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
Version::V0_3_4(v) => {
|
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
Version::V0_3_4_1(v) => {
|
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
|
||||||
.await?
|
|
||||||
}
|
|
||||||
Version::V0_3_4_2(v) => {
|
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
|
||||||
.await?
|
|
||||||
}
|
}
|
||||||
Version::V0_3_4_3(v) => {
|
Version::V0_3_4_3(v) => {
|
||||||
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||||
.await?
|
.await?
|
||||||
}
|
}
|
||||||
|
Version::V0_4_0(v) => {
|
||||||
|
v.0.migrate_to(&Current::new(), db, secrets, receipts)
|
||||||
|
.await?
|
||||||
|
}
|
||||||
Version::Other(_) => {
|
Version::Other(_) => {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
eyre!("Cannot downgrade"),
|
eyre!("Cannot downgrade"),
|
||||||
@@ -297,19 +247,6 @@ mod tests {
|
|||||||
|
|
||||||
fn versions() -> impl Strategy<Value = Version> {
|
fn versions() -> impl Strategy<Value = Version> {
|
||||||
prop_oneof![
|
prop_oneof![
|
||||||
Just(Version::V0_3_0(Wrapper(v0_3_0::Version::new()))),
|
|
||||||
Just(Version::V0_3_0_1(Wrapper(v0_3_0_1::Version::new()))),
|
|
||||||
Just(Version::V0_3_0_2(Wrapper(v0_3_0_2::Version::new()))),
|
|
||||||
Just(Version::V0_3_0_3(Wrapper(v0_3_0_3::Version::new()))),
|
|
||||||
Just(Version::V0_3_1(Wrapper(v0_3_1::Version::new()))),
|
|
||||||
Just(Version::V0_3_1_1(Wrapper(v0_3_1_1::Version::new()))),
|
|
||||||
Just(Version::V0_3_1_2(Wrapper(v0_3_1_2::Version::new()))),
|
|
||||||
Just(Version::V0_3_2(Wrapper(v0_3_2::Version::new()))),
|
|
||||||
Just(Version::V0_3_2_1(Wrapper(v0_3_2_1::Version::new()))),
|
|
||||||
Just(Version::V0_3_3(Wrapper(v0_3_3::Version::new()))),
|
|
||||||
Just(Version::V0_3_4(Wrapper(v0_3_4::Version::new()))),
|
|
||||||
Just(Version::V0_3_4_1(Wrapper(v0_3_4_1::Version::new()))),
|
|
||||||
Just(Version::V0_3_4_2(Wrapper(v0_3_4_2::Version::new()))),
|
|
||||||
Just(Version::V0_3_4_3(Wrapper(v0_3_4_3::Version::new()))),
|
Just(Version::V0_3_4_3(Wrapper(v0_3_4_3::Version::new()))),
|
||||||
em_version().prop_map(Version::Other),
|
em_version().prop_map(Version::Other),
|
||||||
]
|
]
|
||||||
|
|||||||
36
backend/src/version/v0_4_0.rs
Normal file
36
backend/src/version/v0_4_0.rs
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
use async_trait::async_trait;
|
||||||
|
use emver::VersionRange;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const V0_4_0: emver::Version = emver::Version::new(0, 4, 0, 0);
|
||||||
|
lazy_static! {
|
||||||
|
pub static ref V0_4_0_COMPAT: VersionRange = VersionRange::Conj(
|
||||||
|
Box::new(VersionRange::Anchor(emver::GTE, V0_4_0)),
|
||||||
|
Box::new(VersionRange::Anchor(emver::LTE, Current::new().semver())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Version;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl VersionT for Version {
|
||||||
|
type Previous = v0_3_4::Version;
|
||||||
|
fn new() -> Self {
|
||||||
|
Version
|
||||||
|
}
|
||||||
|
fn semver(&self) -> emver::Version {
|
||||||
|
V0_4_0
|
||||||
|
}
|
||||||
|
fn compat(&self) -> &'static VersionRange {
|
||||||
|
&*V0_4_0_COMPAT
|
||||||
|
}
|
||||||
|
async fn up<Db: DbHandle>(&self, db: &mut Db, secrets: &PgPool) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
async fn down<Db: DbHandle>(&self, db: &mut Db, secrets: &PgPool) -> Result<(), Error> {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": null,
|
"name": null,
|
||||||
"ack-welcome": "0.3.4.3",
|
"ack-welcome": "0.4.0",
|
||||||
"marketplace": {
|
"marketplace": {
|
||||||
"selected-url": "https://registry.start9.com/",
|
"selected-url": "https://registry.start9.com/",
|
||||||
"known-hosts": {
|
"known-hosts": {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { CommonModule } from '@angular/common'
|
|||||||
import { Routes, RouterModule } from '@angular/router'
|
import { Routes, RouterModule } from '@angular/router'
|
||||||
import { IonicModule } from '@ionic/angular'
|
import { IonicModule } from '@ionic/angular'
|
||||||
import { AppShowPage } from './app-show.page'
|
import { AppShowPage } from './app-show.page'
|
||||||
import { EmverPipesModule, ResponsiveColModule } from '@start9labs/shared'
|
import { EmverPipesModule, ResponsiveColModule, SharedPipesModule } from '@start9labs/shared'
|
||||||
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
|
import { StatusComponentModule } from 'src/app/components/status/status.component.module'
|
||||||
import { AppConfigPageModule } from 'src/app/modals/app-config/app-config.module'
|
import { AppConfigPageModule } from 'src/app/modals/app-config/app-config.module'
|
||||||
import { LaunchablePipeModule } from 'src/app/pipes/launchable/launchable.module'
|
import { LaunchablePipeModule } from 'src/app/pipes/launchable/launchable.module'
|
||||||
@@ -16,7 +16,6 @@ import { AppShowMenuComponent } from './components/app-show-menu/app-show-menu.c
|
|||||||
import { AppShowHealthChecksComponent } from './components/app-show-health-checks/app-show-health-checks.component'
|
import { AppShowHealthChecksComponent } from './components/app-show-health-checks/app-show-health-checks.component'
|
||||||
import { AppShowAdditionalComponent } from './components/app-show-additional/app-show-additional.component'
|
import { AppShowAdditionalComponent } from './components/app-show-additional/app-show-additional.component'
|
||||||
import { HealthColorPipe } from './pipes/health-color.pipe'
|
import { HealthColorPipe } from './pipes/health-color.pipe'
|
||||||
import { ToHealthChecksPipe } from './pipes/to-health-checks.pipe'
|
|
||||||
import { ToButtonsPipe } from './pipes/to-buttons.pipe'
|
import { ToButtonsPipe } from './pipes/to-buttons.pipe'
|
||||||
import { ToDependenciesPipe } from './pipes/to-dependencies.pipe'
|
import { ToDependenciesPipe } from './pipes/to-dependencies.pipe'
|
||||||
import { ToStatusPipe } from './pipes/to-status.pipe'
|
import { ToStatusPipe } from './pipes/to-status.pipe'
|
||||||
@@ -34,7 +33,6 @@ const routes: Routes = [
|
|||||||
AppShowPage,
|
AppShowPage,
|
||||||
HealthColorPipe,
|
HealthColorPipe,
|
||||||
ProgressDataPipe,
|
ProgressDataPipe,
|
||||||
ToHealthChecksPipe,
|
|
||||||
ToButtonsPipe,
|
ToButtonsPipe,
|
||||||
ToDependenciesPipe,
|
ToDependenciesPipe,
|
||||||
ToStatusPipe,
|
ToStatusPipe,
|
||||||
@@ -56,6 +54,7 @@ const routes: Routes = [
|
|||||||
LaunchablePipeModule,
|
LaunchablePipeModule,
|
||||||
UiPipeModule,
|
UiPipeModule,
|
||||||
ResponsiveColModule,
|
ResponsiveColModule,
|
||||||
|
SharedPipesModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AppShowPageModule {}
|
export class AppShowPageModule {}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
<!-- ** health checks ** -->
|
<!-- ** health checks ** -->
|
||||||
<app-show-health-checks
|
<app-show-health-checks
|
||||||
*ngIf="isRunning(status)"
|
*ngIf="isRunning(status)"
|
||||||
[pkg]="pkg"
|
[pkgId]="pkgId"
|
||||||
></app-show-health-checks>
|
></app-show-health-checks>
|
||||||
<!-- ** dependencies ** -->
|
<!-- ** dependencies ** -->
|
||||||
<app-show-dependencies
|
<app-show-dependencies
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ const STATES = [
|
|||||||
export class AppShowPage {
|
export class AppShowPage {
|
||||||
readonly secure = this.config.isSecure()
|
readonly secure = this.config.isSecure()
|
||||||
|
|
||||||
private readonly pkgId = getPkgId(this.route)
|
readonly pkgId = getPkgId(this.route)
|
||||||
|
|
||||||
readonly pkg$ = this.patch.watch$('package-data', this.pkgId).pipe(
|
readonly pkg$ = this.patch.watch$('package-data', this.pkgId).pipe(
|
||||||
tap(pkg => {
|
tap(pkg => {
|
||||||
|
|||||||
@@ -1,92 +1,82 @@
|
|||||||
<ng-container
|
<ng-container *ngIf="healthChecks$ | async as checks">
|
||||||
*ngIf="pkg | toHealthChecks | async | keyvalue: asIsOrder as checks"
|
<ion-item-divider>Health Checks</ion-item-divider>
|
||||||
>
|
<!-- connected -->
|
||||||
<ng-container *ngIf="checks.length">
|
<ng-container *ngIf="connected$ | async; else disconnected">
|
||||||
<ion-item-divider>Health Checks</ion-item-divider>
|
<ion-item *ngFor="let check of checks">
|
||||||
<!-- connected -->
|
<!-- result -->
|
||||||
<ng-container *ngIf="connected$ | async; else disconnected">
|
<ng-container *ngIf="check.result as result; else noResult">
|
||||||
<ion-item *ngFor="let health of checks">
|
<ion-spinner
|
||||||
<!-- result -->
|
*ngIf="isLoading(result)"
|
||||||
<ng-container *ngIf="health.value?.result as result; else noResult">
|
class="icon-spinner"
|
||||||
<ion-spinner
|
color="primary"
|
||||||
*ngIf="isLoading(result)"
|
slot="start"
|
||||||
class="icon-spinner"
|
></ion-spinner>
|
||||||
color="primary"
|
<ion-icon
|
||||||
slot="start"
|
*ngIf="result === 'success'"
|
||||||
></ion-spinner>
|
slot="start"
|
||||||
<ion-icon
|
name="checkmark"
|
||||||
*ngIf="result === HealthResult.Success"
|
color="success"
|
||||||
slot="start"
|
></ion-icon>
|
||||||
name="checkmark"
|
<ion-icon
|
||||||
color="success"
|
*ngIf="result === 'failure'"
|
||||||
></ion-icon>
|
slot="start"
|
||||||
<ion-icon
|
name="warning-outline"
|
||||||
*ngIf="result === HealthResult.Failure"
|
color="warning"
|
||||||
slot="start"
|
></ion-icon>
|
||||||
name="warning-outline"
|
<ion-icon
|
||||||
color="warning"
|
*ngIf="result === 'disabled'"
|
||||||
></ion-icon>
|
slot="start"
|
||||||
<ion-icon
|
name="remove"
|
||||||
*ngIf="result === HealthResult.Disabled"
|
color="dark"
|
||||||
slot="start"
|
></ion-icon>
|
||||||
name="remove"
|
|
||||||
color="dark"
|
|
||||||
></ion-icon>
|
|
||||||
<ion-label>
|
|
||||||
<h2 class="bold">
|
|
||||||
{{ pkg.manifest['health-checks'][health.key].name }}
|
|
||||||
</h2>
|
|
||||||
<ion-text [color]="result | healthColor">
|
|
||||||
<p>
|
|
||||||
<span *ngIf="isReady(result)">{{ result | titlecase }}</span>
|
|
||||||
<span *ngIf="result === HealthResult.Starting">...</span>
|
|
||||||
<span *ngIf="result === HealthResult.Failure">
|
|
||||||
{{ $any(health.value).error }}
|
|
||||||
</span>
|
|
||||||
<span *ngIf="result === HealthResult.Loading">
|
|
||||||
{{ $any(health.value).message }}
|
|
||||||
</span>
|
|
||||||
<span
|
|
||||||
*ngIf="
|
|
||||||
result === HealthResult.Success &&
|
|
||||||
pkg.manifest['health-checks'][health.key]['success-message']
|
|
||||||
"
|
|
||||||
>:
|
|
||||||
{{
|
|
||||||
pkg.manifest['health-checks'][health.key]['success-message']
|
|
||||||
}}
|
|
||||||
</span>
|
|
||||||
</p>
|
|
||||||
</ion-text>
|
|
||||||
</ion-label>
|
|
||||||
</ng-container>
|
|
||||||
<!-- no result -->
|
|
||||||
<ng-template #noResult>
|
|
||||||
<ion-spinner
|
|
||||||
class="icon-spinner"
|
|
||||||
color="dark"
|
|
||||||
slot="start"
|
|
||||||
></ion-spinner>
|
|
||||||
<ion-label>
|
|
||||||
<h2 class="bold">
|
|
||||||
{{ pkg.manifest['health-checks'][health.key].name }}
|
|
||||||
</h2>
|
|
||||||
<p class="primary">Awaiting result...</p>
|
|
||||||
</ion-label>
|
|
||||||
</ng-template>
|
|
||||||
</ion-item>
|
|
||||||
</ng-container>
|
|
||||||
<!-- disconnected -->
|
|
||||||
<ng-template #disconnected>
|
|
||||||
<ion-item *ngFor="let health of checks">
|
|
||||||
<ion-avatar slot="start">
|
|
||||||
<ion-skeleton-text class="avatar"></ion-skeleton-text>
|
|
||||||
</ion-avatar>
|
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<ion-skeleton-text class="label"></ion-skeleton-text>
|
<h2 class="bold">
|
||||||
<ion-skeleton-text class="description"></ion-skeleton-text>
|
{{ check.name }}
|
||||||
|
</h2>
|
||||||
|
<ion-text [color]="result | healthColor">
|
||||||
|
<p>
|
||||||
|
<span *ngIf="isReady(result)">{{ result | titlecase }}</span>
|
||||||
|
<span *ngIf="result === 'starting'">...</span>
|
||||||
|
<span *ngIf="result === 'failure'">
|
||||||
|
{{ $any(check).error }}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="result === 'loading'">
|
||||||
|
{{ $any(check).message }}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="result === 'success'"
|
||||||
|
>:
|
||||||
|
{{ $any(check).message }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
</ion-text>
|
||||||
</ion-label>
|
</ion-label>
|
||||||
</ion-item>
|
</ng-container>
|
||||||
</ng-template>
|
<!-- no result -->
|
||||||
|
<ng-template #noResult>
|
||||||
|
<ion-spinner
|
||||||
|
class="icon-spinner"
|
||||||
|
color="dark"
|
||||||
|
slot="start"
|
||||||
|
></ion-spinner>
|
||||||
|
<ion-label>
|
||||||
|
<h2 class="bold">
|
||||||
|
{{ check.name }}
|
||||||
|
</h2>
|
||||||
|
<p class="primary">Awaiting result...</p>
|
||||||
|
</ion-label>
|
||||||
|
</ng-template>
|
||||||
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
<!-- disconnected -->
|
||||||
|
<ng-template #disconnected>
|
||||||
|
<ion-item *ngFor="let check of checks">
|
||||||
|
<ion-avatar slot="start">
|
||||||
|
<ion-skeleton-text class="avatar"></ion-skeleton-text>
|
||||||
|
</ion-avatar>
|
||||||
|
<ion-label>
|
||||||
|
<ion-skeleton-text class="label"></ion-skeleton-text>
|
||||||
|
<ion-skeleton-text class="description"></ion-skeleton-text>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ng-template>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
|
||||||
|
import { PatchDB } from 'patch-db-client'
|
||||||
|
import { map } from 'rxjs'
|
||||||
import { ConnectionService } from 'src/app/services/connection.service'
|
import { ConnectionService } from 'src/app/services/connection.service'
|
||||||
import {
|
import { DataModel, HealthResult } from 'src/app/services/patch-db/data-model'
|
||||||
HealthResult,
|
import { isEmptyObject } from '@start9labs/shared'
|
||||||
PackageDataEntry,
|
|
||||||
} from 'src/app/services/patch-db/data-model'
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-show-health-checks',
|
selector: 'app-show-health-checks',
|
||||||
@@ -12,14 +12,26 @@ import {
|
|||||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
})
|
})
|
||||||
export class AppShowHealthChecksComponent {
|
export class AppShowHealthChecksComponent {
|
||||||
@Input()
|
@Input() pkgId!: string
|
||||||
pkg!: PackageDataEntry
|
|
||||||
|
|
||||||
HealthResult = HealthResult
|
|
||||||
|
|
||||||
readonly connected$ = this.connectionService.connected$
|
readonly connected$ = this.connectionService.connected$
|
||||||
|
|
||||||
constructor(private readonly connectionService: ConnectionService) {}
|
get healthChecks$() {
|
||||||
|
return this.patch
|
||||||
|
.watch$('package-data', this.pkgId, 'installed', 'status', 'main')
|
||||||
|
.pipe(
|
||||||
|
map(main => {
|
||||||
|
if (main.status !== 'running' || isEmptyObject(main.health))
|
||||||
|
return null
|
||||||
|
return Object.values(main.health)
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private readonly connectionService: ConnectionService,
|
||||||
|
private readonly patch: PatchDB<DataModel>,
|
||||||
|
) {}
|
||||||
|
|
||||||
isLoading(result: HealthResult): boolean {
|
isLoading(result: HealthResult): boolean {
|
||||||
return result === HealthResult.Starting || result === HealthResult.Loading
|
return result === HealthResult.Starting || result === HealthResult.Loading
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
import { Pipe, PipeTransform } from '@angular/core'
|
|
||||||
import {
|
|
||||||
DataModel,
|
|
||||||
HealthCheckResult,
|
|
||||||
PackageDataEntry,
|
|
||||||
PackageMainStatus,
|
|
||||||
} from 'src/app/services/patch-db/data-model'
|
|
||||||
import { isEmptyObject } from '@start9labs/shared'
|
|
||||||
import { map, startWith } from 'rxjs/operators'
|
|
||||||
import { PatchDB } from 'patch-db-client'
|
|
||||||
import { Observable } from 'rxjs'
|
|
||||||
|
|
||||||
@Pipe({
|
|
||||||
name: 'toHealthChecks',
|
|
||||||
})
|
|
||||||
export class ToHealthChecksPipe implements PipeTransform {
|
|
||||||
constructor(private readonly patch: PatchDB<DataModel>) {}
|
|
||||||
|
|
||||||
transform(
|
|
||||||
pkg: PackageDataEntry,
|
|
||||||
): Observable<Record<string, HealthCheckResult | null>> | null {
|
|
||||||
const healthChecks = Object.keys(pkg.manifest['health-checks']).reduce(
|
|
||||||
(obj, key) => ({ ...obj, [key]: null }),
|
|
||||||
{},
|
|
||||||
)
|
|
||||||
|
|
||||||
const healthChecks$ = this.patch
|
|
||||||
.watch$('package-data', pkg.manifest.id, 'installed', 'status', 'main')
|
|
||||||
.pipe(
|
|
||||||
map(main => {
|
|
||||||
// Question: is this ok or do we have to use Object.keys
|
|
||||||
// to maintain order and the keys initially present in pkg?
|
|
||||||
return main.status === PackageMainStatus.Running &&
|
|
||||||
!isEmptyObject(main.health)
|
|
||||||
? main.health
|
|
||||||
: healthChecks
|
|
||||||
}),
|
|
||||||
startWith(healthChecks),
|
|
||||||
)
|
|
||||||
|
|
||||||
return isEmptyObject(healthChecks) ? null : healthChecks$
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -87,7 +87,6 @@ export module Mock {
|
|||||||
'shm-size': '',
|
'shm-size': '',
|
||||||
'sigterm-timeout': '1ms',
|
'sigterm-timeout': '1ms',
|
||||||
},
|
},
|
||||||
'health-checks': {},
|
|
||||||
config: {
|
config: {
|
||||||
get: null,
|
get: null,
|
||||||
set: null,
|
set: null,
|
||||||
@@ -382,7 +381,6 @@ export module Mock {
|
|||||||
'shm-size': '',
|
'shm-size': '',
|
||||||
'sigterm-timeout': '10000µs',
|
'sigterm-timeout': '10000µs',
|
||||||
},
|
},
|
||||||
'health-checks': {},
|
|
||||||
config: {
|
config: {
|
||||||
get: null,
|
get: null,
|
||||||
set: null,
|
set: null,
|
||||||
@@ -535,7 +533,6 @@ export module Mock {
|
|||||||
'shm-size': '',
|
'shm-size': '',
|
||||||
'sigterm-timeout': '1m',
|
'sigterm-timeout': '1m',
|
||||||
},
|
},
|
||||||
'health-checks': {},
|
|
||||||
config: { get: {} as any, set: {} as any },
|
config: { get: {} as any, set: {} as any },
|
||||||
volumes: {},
|
volumes: {},
|
||||||
'min-os-version': '0.2.12',
|
'min-os-version': '0.2.12',
|
||||||
|
|||||||
@@ -727,7 +727,18 @@ export class MockApiService extends ApiService {
|
|||||||
await pauseFor(2000)
|
await pauseFor(2000)
|
||||||
|
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const patch2 = [
|
if (params.id !== 'bitcoind') {
|
||||||
|
const patch2 = [
|
||||||
|
{
|
||||||
|
op: PatchOp.REPLACE,
|
||||||
|
path: path + '/health',
|
||||||
|
value: {},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
this.mockRevision(patch2)
|
||||||
|
}
|
||||||
|
|
||||||
|
const patch3 = [
|
||||||
{
|
{
|
||||||
op: PatchOp.REPLACE,
|
op: PatchOp.REPLACE,
|
||||||
path: path + '/status',
|
path: path + '/status',
|
||||||
@@ -739,52 +750,7 @@ export class MockApiService extends ApiService {
|
|||||||
value: new Date().toISOString(),
|
value: new Date().toISOString(),
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
this.mockRevision(patch2)
|
|
||||||
|
|
||||||
const patch3 = [
|
|
||||||
{
|
|
||||||
op: PatchOp.REPLACE,
|
|
||||||
path: path + '/health',
|
|
||||||
value: {
|
|
||||||
'ephemeral-health-check': {
|
|
||||||
result: 'starting',
|
|
||||||
},
|
|
||||||
'unnecessary-health-check': {
|
|
||||||
result: 'disabled',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
this.mockRevision(patch3)
|
this.mockRevision(patch3)
|
||||||
|
|
||||||
await pauseFor(2000)
|
|
||||||
|
|
||||||
const patch4 = [
|
|
||||||
{
|
|
||||||
op: PatchOp.REPLACE,
|
|
||||||
path: path + '/health',
|
|
||||||
value: {
|
|
||||||
'ephemeral-health-check': {
|
|
||||||
result: 'starting',
|
|
||||||
},
|
|
||||||
'unnecessary-health-check': {
|
|
||||||
result: 'disabled',
|
|
||||||
},
|
|
||||||
'chain-state': {
|
|
||||||
result: 'loading',
|
|
||||||
message: 'Bitcoin is syncing from genesis',
|
|
||||||
},
|
|
||||||
'p2p-interface': {
|
|
||||||
result: 'success',
|
|
||||||
},
|
|
||||||
'rpc-interface': {
|
|
||||||
result: 'failure',
|
|
||||||
error: 'RPC interface unreachable.',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
this.mockRevision(patch4)
|
|
||||||
}, 2000)
|
}, 2000)
|
||||||
|
|
||||||
const originalPatch = [
|
const originalPatch = [
|
||||||
@@ -896,11 +862,6 @@ export class MockApiService extends ApiService {
|
|||||||
path: path + '/status',
|
path: path + '/status',
|
||||||
value: PackageMainStatus.Stopping,
|
value: PackageMainStatus.Stopping,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
op: PatchOp.REPLACE,
|
|
||||||
path: path + '/health',
|
|
||||||
value: {},
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
return this.withRevision(patch)
|
return this.withRevision(patch)
|
||||||
|
|||||||
@@ -127,24 +127,6 @@ export const mockPatchData: DataModel = {
|
|||||||
'shm-size': '',
|
'shm-size': '',
|
||||||
'sigterm-timeout': '.49m',
|
'sigterm-timeout': '.49m',
|
||||||
},
|
},
|
||||||
'health-checks': {
|
|
||||||
'chain-state': {
|
|
||||||
name: 'Chain State',
|
|
||||||
},
|
|
||||||
'ephemeral-health-check': {
|
|
||||||
name: 'Ephemeral Health Check',
|
|
||||||
},
|
|
||||||
'p2p-interface': {
|
|
||||||
name: 'P2P Interface',
|
|
||||||
'success-message': 'the health check ran succesfully',
|
|
||||||
},
|
|
||||||
'rpc-interface': {
|
|
||||||
name: 'RPC Interface',
|
|
||||||
},
|
|
||||||
'unnecessary-health-check': {
|
|
||||||
name: 'Unneccessary Health Check',
|
|
||||||
},
|
|
||||||
} as any,
|
|
||||||
config: {
|
config: {
|
||||||
get: {},
|
get: {},
|
||||||
set: {},
|
set: {},
|
||||||
@@ -243,9 +225,12 @@ export const mockPatchData: DataModel = {
|
|||||||
'Your reason for re-syncing. Why are you doing this?',
|
'Your reason for re-syncing. Why are you doing this?',
|
||||||
nullable: false,
|
nullable: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: false,
|
|
||||||
pattern: '^[a-zA-Z]+$',
|
pattern: '^[a-zA-Z]+$',
|
||||||
'pattern-description': 'Must contain only letters.',
|
'pattern-description': 'Must contain only letters.',
|
||||||
|
placeholder: null,
|
||||||
|
textarea: false,
|
||||||
|
warning: null,
|
||||||
|
default: null,
|
||||||
},
|
},
|
||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
@@ -253,14 +238,19 @@ export const mockPatchData: DataModel = {
|
|||||||
description: 'Tell the class your name.',
|
description: 'Tell the class your name.',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: false,
|
|
||||||
warning: 'You may loose all your money by providing your name.',
|
warning: 'You may loose all your money by providing your name.',
|
||||||
|
placeholder: null,
|
||||||
|
pattern: null,
|
||||||
|
'pattern-description': null,
|
||||||
|
textarea: false,
|
||||||
|
default: null,
|
||||||
},
|
},
|
||||||
notifications: {
|
notifications: {
|
||||||
name: 'Notification Preferences',
|
name: 'Notification Preferences',
|
||||||
type: 'list',
|
type: 'list',
|
||||||
subtype: 'enum',
|
subtype: 'enum',
|
||||||
description: 'how you want to be notified',
|
description: 'how you want to be notified',
|
||||||
|
warning: null,
|
||||||
range: '[1,3]',
|
range: '[1,3]',
|
||||||
default: ['email'],
|
default: ['email'],
|
||||||
spec: {
|
spec: {
|
||||||
@@ -282,6 +272,9 @@ export const mockPatchData: DataModel = {
|
|||||||
default: 100,
|
default: 100,
|
||||||
range: '[0, 9999]',
|
range: '[0, 9999]',
|
||||||
integral: true,
|
integral: true,
|
||||||
|
units: null,
|
||||||
|
placeholder: null,
|
||||||
|
warning: null,
|
||||||
},
|
},
|
||||||
'top-speed': {
|
'top-speed': {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
@@ -291,6 +284,9 @@ export const mockPatchData: DataModel = {
|
|||||||
range: '[-1000, 1000]',
|
range: '[-1000, 1000]',
|
||||||
integral: false,
|
integral: false,
|
||||||
units: 'm/s',
|
units: 'm/s',
|
||||||
|
placeholder: null,
|
||||||
|
warning: null,
|
||||||
|
default: null,
|
||||||
},
|
},
|
||||||
testnet: {
|
testnet: {
|
||||||
name: 'Testnet',
|
name: 'Testnet',
|
||||||
@@ -318,22 +314,33 @@ export const mockPatchData: DataModel = {
|
|||||||
name: 'Emergency Contact',
|
name: 'Emergency Contact',
|
||||||
type: 'object',
|
type: 'object',
|
||||||
description: 'The person to contact in case of emergency.',
|
description: 'The person to contact in case of emergency.',
|
||||||
|
warning: null,
|
||||||
spec: {
|
spec: {
|
||||||
name: {
|
name: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'Name',
|
name: 'Name',
|
||||||
|
description: null,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: false,
|
|
||||||
pattern: '^[a-zA-Z]+$',
|
pattern: '^[a-zA-Z]+$',
|
||||||
'pattern-description': 'Must contain only letters.',
|
'pattern-description': 'Must contain only letters.',
|
||||||
|
placeholder: null,
|
||||||
|
textarea: false,
|
||||||
|
warning: null,
|
||||||
|
default: null,
|
||||||
},
|
},
|
||||||
email: {
|
email: {
|
||||||
type: 'string',
|
type: 'string',
|
||||||
name: 'Email',
|
name: 'Email',
|
||||||
|
description: null,
|
||||||
nullable: false,
|
nullable: false,
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: true,
|
placeholder: null,
|
||||||
|
pattern: null,
|
||||||
|
'pattern-description': null,
|
||||||
|
textarea: false,
|
||||||
|
warning: null,
|
||||||
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -351,7 +358,7 @@ export const mockPatchData: DataModel = {
|
|||||||
pattern: '^[0-9]{1,3}([,.][0-9]{1,3})?$',
|
pattern: '^[0-9]{1,3}([,.][0-9]{1,3})?$',
|
||||||
'pattern-description': 'Must be a valid IP address',
|
'pattern-description': 'Must be a valid IP address',
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: false,
|
placeholder: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
bitcoinNode: {
|
bitcoinNode: {
|
||||||
@@ -375,7 +382,12 @@ export const mockPatchData: DataModel = {
|
|||||||
description: 'the lan address',
|
description: 'the lan address',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: false,
|
placeholder: null,
|
||||||
|
pattern: null,
|
||||||
|
'pattern-description': null,
|
||||||
|
textarea: false,
|
||||||
|
warning: null,
|
||||||
|
default: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
external: {
|
external: {
|
||||||
@@ -388,7 +400,9 @@ export const mockPatchData: DataModel = {
|
|||||||
pattern: '.*',
|
pattern: '.*',
|
||||||
'pattern-description': 'anything',
|
'pattern-description': 'anything',
|
||||||
masked: false,
|
masked: false,
|
||||||
copyable: true,
|
placeholder: null,
|
||||||
|
textarea: false,
|
||||||
|
warning: null,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -411,20 +425,26 @@ export const mockPatchData: DataModel = {
|
|||||||
started: '2021-06-14T20:49:17.774Z',
|
started: '2021-06-14T20:49:17.774Z',
|
||||||
health: {
|
health: {
|
||||||
'ephemeral-health-check': {
|
'ephemeral-health-check': {
|
||||||
|
name: 'Ephemeral Health Check',
|
||||||
result: HealthResult.Starting,
|
result: HealthResult.Starting,
|
||||||
},
|
},
|
||||||
'chain-state': {
|
'chain-state': {
|
||||||
|
name: 'Chain State',
|
||||||
result: HealthResult.Loading,
|
result: HealthResult.Loading,
|
||||||
message: 'Bitcoin is syncing from genesis',
|
message: 'Bitcoin is syncing from genesis',
|
||||||
},
|
},
|
||||||
'p2p-interface': {
|
'p2p-interface': {
|
||||||
|
name: 'P2P Interface',
|
||||||
result: HealthResult.Success,
|
result: HealthResult.Success,
|
||||||
|
message: 'the health check ran successfully',
|
||||||
},
|
},
|
||||||
'rpc-interface': {
|
'rpc-interface': {
|
||||||
|
name: 'RPC Interface',
|
||||||
result: HealthResult.Failure,
|
result: HealthResult.Failure,
|
||||||
error: 'RPC interface unreachable.',
|
error: 'RPC interface unreachable.',
|
||||||
},
|
},
|
||||||
'unnecessary-health-check': {
|
'unnecessary-health-check': {
|
||||||
|
name: 'Totally Unnecessary',
|
||||||
result: HealthResult.Disabled,
|
result: HealthResult.Disabled,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -461,7 +481,7 @@ export const mockPatchData: DataModel = {
|
|||||||
manifest: {
|
manifest: {
|
||||||
id: 'lnd',
|
id: 'lnd',
|
||||||
title: 'Lightning Network Daemon',
|
title: 'Lightning Network Daemon',
|
||||||
version: '0.11.0',
|
version: '0.11.1',
|
||||||
description: {
|
description: {
|
||||||
short: 'A bolt spec compliant client.',
|
short: 'A bolt spec compliant client.',
|
||||||
long: 'More info about LND. More info about LND. More info about LND.',
|
long: 'More info about LND. More info about LND. More info about LND.',
|
||||||
@@ -501,7 +521,6 @@ export const mockPatchData: DataModel = {
|
|||||||
'shm-size': '',
|
'shm-size': '',
|
||||||
'sigterm-timeout': '0.5s',
|
'sigterm-timeout': '0.5s',
|
||||||
},
|
},
|
||||||
'health-checks': {},
|
|
||||||
config: {
|
config: {
|
||||||
get: null,
|
get: null,
|
||||||
set: null,
|
set: null,
|
||||||
|
|||||||
@@ -155,10 +155,6 @@ export interface Manifest extends MarketplaceManifest<DependencyConfig | null> {
|
|||||||
scripts: string // path to scripts folder
|
scripts: string // path to scripts folder
|
||||||
}
|
}
|
||||||
main: ActionImpl
|
main: ActionImpl
|
||||||
'health-checks': Record<
|
|
||||||
string,
|
|
||||||
ActionImpl & { name: string; 'success-message': string | null }
|
|
||||||
>
|
|
||||||
config: ConfigActions | null
|
config: ConfigActions | null
|
||||||
volumes: Record<string, Volume>
|
volumes: Record<string, Volume>
|
||||||
'min-os-version': string
|
'min-os-version': string
|
||||||
@@ -295,7 +291,6 @@ export interface MainStatusStopping {
|
|||||||
|
|
||||||
export interface MainStatusStarting {
|
export interface MainStatusStarting {
|
||||||
status: PackageMainStatus.Starting
|
status: PackageMainStatus.Starting
|
||||||
restarting: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface MainStatusRunning {
|
export interface MainStatusRunning {
|
||||||
@@ -322,12 +317,13 @@ export enum PackageMainStatus {
|
|||||||
Restarting = 'restarting',
|
Restarting = 'restarting',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type HealthCheckResult =
|
export type HealthCheckResult = { name: string } & (
|
||||||
| HealthCheckResultStarting
|
| HealthCheckResultStarting
|
||||||
| HealthCheckResultLoading
|
| HealthCheckResultLoading
|
||||||
| HealthCheckResultDisabled
|
| HealthCheckResultDisabled
|
||||||
| HealthCheckResultSuccess
|
| HealthCheckResultSuccess
|
||||||
| HealthCheckResultFailure
|
| HealthCheckResultFailure
|
||||||
|
)
|
||||||
|
|
||||||
export enum HealthResult {
|
export enum HealthResult {
|
||||||
Starting = 'starting',
|
Starting = 'starting',
|
||||||
@@ -347,6 +343,7 @@ export interface HealthCheckResultDisabled {
|
|||||||
|
|
||||||
export interface HealthCheckResultSuccess {
|
export interface HealthCheckResultSuccess {
|
||||||
result: HealthResult.Success
|
result: HealthResult.Success
|
||||||
|
message: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface HealthCheckResultLoading {
|
export interface HealthCheckResultLoading {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { isEmptyObject } from '@start9labs/shared'
|
import { isEmptyObject } from '@start9labs/shared'
|
||||||
import {
|
import {
|
||||||
|
InstalledPackageDataEntry,
|
||||||
MainStatusStarting,
|
MainStatusStarting,
|
||||||
PackageDataEntry,
|
PackageDataEntry,
|
||||||
PackageMainStatus,
|
PackageMainStatus,
|
||||||
@@ -8,42 +9,39 @@ import {
|
|||||||
} from 'src/app/services/patch-db/data-model'
|
} from 'src/app/services/patch-db/data-model'
|
||||||
|
|
||||||
export interface PackageStatus {
|
export interface PackageStatus {
|
||||||
primary: PrimaryStatus
|
primary: PrimaryStatus | PackageState | PackageMainStatus
|
||||||
dependency: DependencyStatus | null
|
dependency: DependencyStatus | null
|
||||||
health: HealthStatus | null
|
health: HealthStatus | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderPkgStatus(pkg: PackageDataEntry): PackageStatus {
|
export function renderPkgStatus(pkg: PackageDataEntry): PackageStatus {
|
||||||
let primary: PrimaryStatus
|
let primary: PrimaryStatus | PackageState | PackageMainStatus
|
||||||
let dependency: DependencyStatus | null = null
|
let dependency: DependencyStatus | null = null
|
||||||
let health: HealthStatus | null = null
|
let health: HealthStatus | null = null
|
||||||
const hasHealthChecks = !isEmptyObject(pkg.manifest['health-checks'])
|
|
||||||
|
|
||||||
if (pkg.state === PackageState.Installed && pkg.installed) {
|
if (pkg.state === PackageState.Installed && pkg.installed) {
|
||||||
primary = getPrimaryStatus(pkg.installed.status)
|
primary = getPrimaryStatus(pkg.installed.status)
|
||||||
dependency = getDependencyStatus(pkg)
|
dependency = getDependencyStatus(pkg.installed)
|
||||||
health = getHealthStatus(pkg.installed.status, hasHealthChecks)
|
health = getHealthStatus(pkg.installed.status)
|
||||||
} else {
|
} else {
|
||||||
primary = pkg.state as string as PrimaryStatus
|
primary = pkg.state
|
||||||
}
|
}
|
||||||
|
|
||||||
return { primary, dependency, health }
|
return { primary, dependency, health }
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPrimaryStatus(status: Status): PrimaryStatus {
|
function getPrimaryStatus(status: Status): PrimaryStatus | PackageMainStatus {
|
||||||
if (!status.configured) {
|
if (!status.configured) {
|
||||||
return PrimaryStatus.NeedsConfig
|
return PrimaryStatus.NeedsConfig
|
||||||
} else if ((status.main as MainStatusStarting).restarting) {
|
|
||||||
return PrimaryStatus.Restarting
|
|
||||||
} else {
|
} else {
|
||||||
return status.main.status as any as PrimaryStatus
|
return status.main.status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDependencyStatus(pkg: PackageDataEntry): DependencyStatus | null {
|
function getDependencyStatus(
|
||||||
const installed = pkg.installed
|
installed: InstalledPackageDataEntry,
|
||||||
if (!installed || isEmptyObject(installed['current-dependencies']))
|
): DependencyStatus | null {
|
||||||
return null
|
if (isEmptyObject(installed['current-dependencies'])) return null
|
||||||
|
|
||||||
const depErrors = installed.status['dependency-errors']
|
const depErrors = installed.status['dependency-errors']
|
||||||
const depIds = Object.keys(depErrors).filter(key => !!depErrors[key])
|
const depIds = Object.keys(depErrors).filter(key => !!depErrors[key])
|
||||||
@@ -51,11 +49,8 @@ function getDependencyStatus(pkg: PackageDataEntry): DependencyStatus | null {
|
|||||||
return depIds.length ? DependencyStatus.Warning : DependencyStatus.Satisfied
|
return depIds.length ? DependencyStatus.Warning : DependencyStatus.Satisfied
|
||||||
}
|
}
|
||||||
|
|
||||||
function getHealthStatus(
|
function getHealthStatus(status: Status): HealthStatus | null {
|
||||||
status: Status,
|
if (status.main.status !== PackageMainStatus.Running) {
|
||||||
hasHealthChecks: boolean,
|
|
||||||
): HealthStatus | null {
|
|
||||||
if (status.main.status !== PackageMainStatus.Running || !status.main.health) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,10 +60,6 @@ function getHealthStatus(
|
|||||||
return HealthStatus.Failure
|
return HealthStatus.Failure
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!values.length && hasHealthChecks) {
|
|
||||||
return HealthStatus.Waiting
|
|
||||||
}
|
|
||||||
|
|
||||||
if (values.some(h => h.result === 'loading')) {
|
if (values.some(h => h.result === 'loading')) {
|
||||||
return HealthStatus.Loading
|
return HealthStatus.Loading
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user