Health checks
Health checks can be enabled for applications which will make an http request once per minute and color the application icon based on the response and status code.
This commit is contained in:
parent
fcc69d2a91
commit
bbee8c6460
|
@ -86,16 +86,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "actix-cors"
|
||||
version = "0.5.4"
|
||||
version = "0.6.0-beta.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36b133d8026a9f209a9aeeeacd028e7451bcca975f592881b305d37983f303d7"
|
||||
checksum = "8debd30414af03e9411186aac95e0230b0bb1e91146f48015dfab5c049940223"
|
||||
dependencies = [
|
||||
"actix-web 3.3.3",
|
||||
"actix-service 2.0.2",
|
||||
"actix-utils 3.0.0",
|
||||
"actix-web 4.0.0-rc.2",
|
||||
"derive_more",
|
||||
"futures-util",
|
||||
"log",
|
||||
"once_cell",
|
||||
"tinyvec",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -461,6 +463,23 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-actors"
|
||||
version = "4.0.0-beta.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fbf2ef3eae6001ac2fa6690b2f8b152c00b5b8b2248e3e30f82dd2ec1e941345"
|
||||
dependencies = [
|
||||
"actix",
|
||||
"actix-codec 0.4.2",
|
||||
"actix-http 3.0.0-rc.1",
|
||||
"actix-web 4.0.0-rc.2",
|
||||
"bytes 1.1.0",
|
||||
"bytestring",
|
||||
"futures-core",
|
||||
"pin-project-lite 0.2.8",
|
||||
"tokio 1.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-web-codegen"
|
||||
version = "0.4.0"
|
||||
|
@ -852,6 +871,22 @@ version = "0.1.5"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation-sys"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
|
@ -996,6 +1031,15 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
|
||||
dependencies = [
|
||||
"instant",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "firestorm"
|
||||
version = "0.5.0"
|
||||
|
@ -1032,6 +1076,21 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.0.1"
|
||||
|
@ -1308,6 +1367,17 @@ dependencies = [
|
|||
"itoa 1.0.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ff4f84919677303da5f147645dbea6b1881f368d03ac84e1dc09031ebd7b2c6"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"http",
|
||||
"pin-project-lite 0.2.8",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.5.1"
|
||||
|
@ -1320,6 +1390,43 @@ version = "1.0.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "0.14.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b7ec3e62bdc98a2f0393a5048e4c30ef659440ea6e0e572965103e72bd836f55"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.3.11",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa 0.4.8",
|
||||
"pin-project-lite 0.2.8",
|
||||
"socket2 0.4.4",
|
||||
"tokio 1.16.1",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
|
||||
dependencies = [
|
||||
"bytes 1.1.0",
|
||||
"hyper",
|
||||
"native-tls",
|
||||
"tokio 1.16.1",
|
||||
"tokio-native-tls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "0.2.3"
|
||||
|
@ -1368,9 +1475,15 @@ dependencies = [
|
|||
"socket2 0.3.19",
|
||||
"widestring",
|
||||
"winapi 0.3.9",
|
||||
"winreg",
|
||||
"winreg 0.6.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ipnet"
|
||||
version = "2.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.10.3"
|
||||
|
@ -1677,6 +1790,24 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "net2"
|
||||
version = "0.2.37"
|
||||
|
@ -1780,6 +1911,39 @@ version = "0.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.38"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c7ae222234c30df141154f159066c5093ff73b63204dcda7121eb082fc56a95"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if 1.0.0",
|
||||
"foreign-types",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.72"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e46109c383602735fa0a2e48dd2b7c892b048e1bf69e5c3b1d804b7d9c203cb"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cc",
|
||||
"libc",
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ouroboros"
|
||||
version = "0.14.0"
|
||||
|
@ -2099,6 +2263,51 @@ version = "0.6.25"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
|
||||
[[package]]
|
||||
name = "remove_dir_all"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.11.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"bytes 1.1.0",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.3.11",
|
||||
"http",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-tls",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"percent-encoding",
|
||||
"pin-project-lite 0.2.8",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"tokio 1.16.1",
|
||||
"tokio-native-tls",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"winreg 0.7.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "resolv-conf"
|
||||
version = "0.7.0"
|
||||
|
@ -2222,6 +2431,16 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
|
@ -2324,6 +2543,29 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dc14f172faf8a0194a3aded622712b0de276821addc574fa54fc0a1167e10dc"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0160a13a177a45bfb43ce71c01580998474f556ad854dcbca936dd2841a5c556"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
|
@ -2709,6 +2951,20 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"fastrand",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"remove_dir_all",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.30"
|
||||
|
@ -2864,9 +3120,31 @@ dependencies = [
|
|||
"parking_lot",
|
||||
"pin-project-lite 0.2.8",
|
||||
"signal-hook-registry",
|
||||
"tokio-macros",
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-macros"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b557f72f448c511a979e2564e55d74e6c4432fc96ff4f6241bc6bded342643b7"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio 1.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.22.0"
|
||||
|
@ -2917,6 +3195,12 @@ dependencies = [
|
|||
"tokio 1.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "360dfd1d6d30e05fda32ace2c8c70e9c0a9da713275777f5a4dbb8a1893930c6"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.30"
|
||||
|
@ -3074,6 +3358,12 @@ dependencies = [
|
|||
"trust-dns-proto",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "try-lock"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
|
@ -3158,6 +3448,7 @@ dependencies = [
|
|||
"actix-cors",
|
||||
"actix-rt 2.6.0",
|
||||
"actix-web 4.0.0-rc.2",
|
||||
"actix-web-actors",
|
||||
"actix-web-httpauth",
|
||||
"base64",
|
||||
"bcrypt",
|
||||
|
@ -3166,11 +3457,13 @@ dependencies = [
|
|||
"jsonwebtoken",
|
||||
"mime_guess",
|
||||
"rand 0.8.4",
|
||||
"reqwest",
|
||||
"rust-embed",
|
||||
"sea-orm",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sqlx",
|
||||
"tokio 1.16.1",
|
||||
"tracing",
|
||||
"tracing-actix-web",
|
||||
"tracing-subscriber",
|
||||
|
@ -3207,6 +3500,16 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
|
@ -3244,6 +3547,18 @@ dependencies = [
|
|||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-futures"
|
||||
version = "0.4.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2eb6ec270a31b1d3c7e266b999739109abce8b6c87e4b31fcfcd788b65267395"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
"web-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.79"
|
||||
|
@ -3360,6 +3675,15 @@ dependencies = [
|
|||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winreg"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69"
|
||||
dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ws2_32-sys"
|
||||
version = "0.2.1"
|
||||
|
|
|
@ -11,7 +11,8 @@ tracing = "0.1.30"
|
|||
tracing-unwrap = "0.9.2"
|
||||
tracing-subscriber = { version = "0.3.8", features = ["fmt"] }
|
||||
actix = "0.12.0"
|
||||
actix-cors = "0.5.4"
|
||||
actix-cors = "0.6.0-beta.10"
|
||||
actix-web-actors = "4.0.0-beta.6"
|
||||
chrono = { version = "0.4.19", features = ["serde"] }
|
||||
serde = { version = "1.0.136", features= [ "derive" ] }
|
||||
serde_json = "1.0.78"
|
||||
|
@ -25,8 +26,10 @@ bcrypt = "0.10.1"
|
|||
actix-web-httpauth = "0.6.0-beta.7"
|
||||
jsonwebtoken = "8.0.1"
|
||||
rand = "0.8.4"
|
||||
tokio = { verison = "1", features=["full"] }
|
||||
base64 = "0.13.0"
|
||||
sqlx = { version = "^0.5", features=["sqlite", "migrate"] }
|
||||
reqwest = "0.11.9"
|
||||
|
||||
[target.'cfg(all(target_env = "musl", target_pointer_width = "64"))'.dependencies.jemallocator]
|
||||
version = "0.3"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
FROM alpine:latest
|
||||
COPY target/x86_64-unknown-linux-musl/release/vade /app/vade
|
||||
EXPOSE 8080
|
||||
EXPOSE 8088
|
||||
WORKDIR app
|
||||
RUN touch data.db
|
||||
CMD ["./vade"]
|
||||
|
|
|
@ -6,7 +6,9 @@ CREATE TABLE application (
|
|||
description TEXT,
|
||||
active Boolean NOT NULL DEFAULT 1,
|
||||
glyph TEXT,
|
||||
application_category_id INTEGER
|
||||
application_category_id INTEGER,
|
||||
alive Boolean NOT NULL DEFAULT 1,
|
||||
enable_healthcheck Boolean NOT NULL DEFAULT 0
|
||||
);
|
||||
CREATE TABLE application_category (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
|
|
|
@ -3,11 +3,50 @@ use tracing::instrument;
|
|||
use crate::api::api_prelude::*;
|
||||
use crate::error::{Error, Result};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct ApiApplication {
|
||||
pub id: i32,
|
||||
pub app_name: String,
|
||||
pub url: String,
|
||||
pub description: Option<String>,
|
||||
pub active: bool,
|
||||
pub glyph: Option<String>,
|
||||
pub application_category_id: Option<i32>,
|
||||
pub enable_healthcheck: bool,
|
||||
pub healthcheck_status: Option<bool>,
|
||||
}
|
||||
|
||||
impl From<application::Model> for ApiApplication {
|
||||
fn from(model: application::Model) -> Self {
|
||||
Self {
|
||||
id: model.id,
|
||||
app_name: model.app_name,
|
||||
url: model.url,
|
||||
description: model.description,
|
||||
active: model.active,
|
||||
glyph: model.glyph,
|
||||
application_category_id: model.application_category_id,
|
||||
enable_healthcheck: model.enable_healthcheck,
|
||||
healthcheck_status: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[instrument]
|
||||
#[get("")]
|
||||
pub async fn list_applications(state: web::Data<AppState>) -> Result<HttpResponse> {
|
||||
let apps: Vec<application::Model> = Application::find().all(&state.db).await.unwrap();
|
||||
let count = apps.len();
|
||||
let mut apps = apps
|
||||
.into_iter()
|
||||
.map(|app| app.into())
|
||||
.collect::<Vec<ApiApplication>>();
|
||||
|
||||
for app in apps.iter_mut() {
|
||||
app.healthcheck_status = state.healthcheck_status.lock().await.get(&app.id).cloned();
|
||||
}
|
||||
|
||||
Ok(HttpResponse::Ok().json(ListObjects::new(apps, count)))
|
||||
}
|
||||
|
||||
|
@ -25,6 +64,7 @@ pub async fn new_application(
|
|||
active: Set(data.0.active),
|
||||
glyph: Set(data.0.glyph),
|
||||
application_category_id: Set(data.0.application_category_id),
|
||||
enable_healthcheck: Set(data.0.enable_healthcheck),
|
||||
};
|
||||
let app = model.insert(&state.db).await?;
|
||||
Ok(HttpResponse::Ok().json(app))
|
||||
|
@ -65,6 +105,7 @@ pub async fn update_applications(
|
|||
url: Set(data.url),
|
||||
application_category_id: Set(data.application_category_id),
|
||||
glyph: Set(data.glyph),
|
||||
enable_healthcheck: Set(data.enable_healthcheck),
|
||||
};
|
||||
let model = ret.update(&state.db).await?;
|
||||
Ok(HttpResponse::Ok().json(model))
|
||||
|
@ -168,6 +209,7 @@ mod tests {
|
|||
description: Some("Some Application".into()),
|
||||
active: true,
|
||||
application_category_id: None,
|
||||
enable_healthcheck: false,
|
||||
};
|
||||
|
||||
let state = setup_state().await?;
|
||||
|
|
|
@ -78,6 +78,8 @@ mod api_prelude {
|
|||
|
||||
#[cfg(test)]
|
||||
pub mod test_prelude {
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub use super::ListObjects;
|
||||
use crate::auth;
|
||||
pub use crate::entity::*;
|
||||
|
@ -96,6 +98,7 @@ pub mod test_prelude {
|
|||
entity::prelude::*, entity::*, tests_cfg::*, DatabaseBackend, MockDatabase, MockExecResult,
|
||||
Transaction,
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// Sets up a testing state with an in-memory database and creates the scheme.
|
||||
pub async fn setup_state() -> Result<actix_web::web::Data<AppState>> {
|
||||
|
@ -118,7 +121,10 @@ pub mod test_prelude {
|
|||
|
||||
auth::generate_secret(&db).await?;
|
||||
|
||||
Ok(actix_web::web::Data::new(AppState { db }))
|
||||
Ok(actix_web::web::Data::new(AppState {
|
||||
db,
|
||||
healthcheck_status: Mutex::new(HashMap::new()),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ pub struct Model {
|
|||
pub active: bool,
|
||||
pub glyph: Option<String>,
|
||||
pub application_category_id: Option<i32>,
|
||||
pub enable_healthcheck: bool,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||
|
@ -50,6 +51,7 @@ impl Default for Model {
|
|||
active: true,
|
||||
glyph: Default::default(),
|
||||
application_category_id: Default::default(),
|
||||
enable_healthcheck: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -62,7 +62,7 @@ impl actix_web::error::ResponseError for Error {
|
|||
fn status_code(&self) -> actix_web::http::StatusCode {
|
||||
match self.code {
|
||||
ErrorCode::UnAuthorized => StatusCode::UNAUTHORIZED,
|
||||
ErrorCode::NoSetup => StatusCode::UPGRADE_REQUIRED,
|
||||
ErrorCode::NoSetup => StatusCode::OK,
|
||||
_ => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
use actix::*;
|
||||
use rand::{self, rngs::ThreadRng, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub type ID = i64;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Session {
|
||||
pub addr: Recipient<Event>,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "ID")]
|
||||
pub struct Connect {
|
||||
pub addr: Recipient<Event>,
|
||||
}
|
||||
|
||||
#[derive(Message)]
|
||||
#[rtype(result = "()")]
|
||||
pub struct Disconnect {
|
||||
pub id: ID,
|
||||
}
|
||||
|
||||
#[derive(Message, Clone, Debug, Serialize, Deserialize)]
|
||||
#[rtype(result = "()")]
|
||||
pub enum Event {
|
||||
HealthcheckChange { app_id: i32, alive: bool },
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct EventBroker {
|
||||
rng: ThreadRng,
|
||||
sessions: HashMap<ID, Session>,
|
||||
}
|
||||
|
||||
impl Supervised for EventBroker {}
|
||||
|
||||
impl SystemService for EventBroker {}
|
||||
|
||||
impl Actor for EventBroker {
|
||||
type Context = Context<Self>;
|
||||
|
||||
fn started(&mut self, _: &mut Self::Context) {
|
||||
tracing::info!("EventBroker - Started");
|
||||
}
|
||||
|
||||
fn stopped(&mut self, _: &mut Self::Context) {
|
||||
tracing::error!("EventBroker - Shutdown");
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<Connect> for EventBroker {
|
||||
type Result = ID;
|
||||
fn handle(&mut self, msg: Connect, _: &mut Context<Self>) -> Self::Result {
|
||||
tracing::info!("Session connected");
|
||||
let id = self.rng.gen::<ID>();
|
||||
self.sessions.insert(id, Session { addr: msg.addr });
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<Disconnect> for EventBroker {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: Disconnect, _: &mut Context<Self>) {
|
||||
tracing::info!("Session disconnected");
|
||||
self.sessions.remove(&msg.id);
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<Event> for EventBroker {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: Event, _ctx: &mut Self::Context) -> Self::Result {
|
||||
tracing::info!("Event received");
|
||||
for (_, ses) in self.sessions.iter() {
|
||||
let _ = ses.addr.do_send(msg.clone());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -13,6 +13,11 @@ axios.interceptors.request.use(function (config) {
|
|||
return config;
|
||||
});
|
||||
|
||||
|
||||
axios.defaults.baseURL = process.env.NODE_ENV === 'development'
|
||||
? 'http://localhost:8088'
|
||||
: '';
|
||||
|
||||
axios.interceptors.response.use(function (response) {
|
||||
return response;
|
||||
}, function (error) {
|
||||
|
|
58
src/main.rs
58
src/main.rs
|
@ -1,8 +1,10 @@
|
|||
use std::path::Path;
|
||||
use std::{collections::HashMap, path::Path};
|
||||
|
||||
use actix::SystemService;
|
||||
use actix_web::{get, web, App, HttpResponse, HttpServer};
|
||||
use rust_embed::RustEmbed;
|
||||
use sea_orm::{Database, DatabaseConnection};
|
||||
use sea_orm::{prelude::*, Database};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::{info, instrument};
|
||||
use tracing_subscriber::prelude::*;
|
||||
|
||||
|
@ -10,6 +12,8 @@ mod api;
|
|||
mod auth;
|
||||
mod entity;
|
||||
mod error;
|
||||
mod events;
|
||||
mod socket_session;
|
||||
|
||||
#[cfg(all(target_env = "musl", target_pointer_width = "64"))]
|
||||
#[global_allocator]
|
||||
|
@ -18,32 +22,74 @@ static ALLOC: jemallocator::Jemalloc = jemallocator::Jemalloc;
|
|||
#[derive(Debug)]
|
||||
pub struct AppState {
|
||||
pub db: DatabaseConnection,
|
||||
pub healthcheck_status: Mutex<HashMap<i32, bool>>,
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
#[actix_rt::main]
|
||||
async fn main() {
|
||||
let subscriber = tracing_subscriber::registry().with(
|
||||
tracing_subscriber::fmt::Layer::new()
|
||||
.pretty()
|
||||
.with_writer(std::io::stdout)
|
||||
.with_ansi(true)
|
||||
.with_filter(tracing_subscriber::filter::LevelFilter::TRACE),
|
||||
.with_filter(tracing_subscriber::filter::LevelFilter::INFO),
|
||||
);
|
||||
tracing::subscriber::set_global_default(subscriber).expect("Unable to set a global collector");
|
||||
|
||||
let db = setup_database().await.unwrap();
|
||||
let state = web::Data::new(AppState { db });
|
||||
let state = web::Data::new(AppState {
|
||||
db,
|
||||
healthcheck_status: Mutex::new(HashMap::new()),
|
||||
});
|
||||
|
||||
info!("Starting http server on 8080");
|
||||
|
||||
let st = state.clone();
|
||||
actix_rt::spawn(async move {
|
||||
loop {
|
||||
tokio::time::sleep(tokio::time::Duration::from_secs(60)).await;
|
||||
let apps: Vec<entity::application::Model> = entity::application::Entity::find()
|
||||
.filter(entity::application::Column::EnableHealthcheck.eq(true))
|
||||
.all(&st.db)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
for app in apps {
|
||||
match reqwest::get(app.url).await {
|
||||
Ok(res) if res.status() == 200 => {
|
||||
st.healthcheck_status.lock().await.insert(app.id, true);
|
||||
let _ = events::EventBroker::from_registry()
|
||||
.send(events::Event::HealthcheckChange {
|
||||
app_id: app.id,
|
||||
alive: true,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
_ => {
|
||||
st.healthcheck_status.lock().await.insert(app.id, false);
|
||||
let _ = events::EventBroker::from_registry()
|
||||
.send(events::Event::HealthcheckChange {
|
||||
app_id: app.id,
|
||||
alive: false,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
HttpServer::new(move || {
|
||||
let cors = actix_cors::Cors::permissive();
|
||||
App::new()
|
||||
.app_data(state.clone())
|
||||
.wrap(tracing_actix_web::TracingLogger::default())
|
||||
.wrap(cors)
|
||||
.service(web::resource("/events/{token}").to(socket_session::event_session_index))
|
||||
.service(api::routes())
|
||||
.service(dist)
|
||||
})
|
||||
.bind("0.0.0.0:8080")
|
||||
.bind("0.0.0.0:8088")
|
||||
.unwrap()
|
||||
.run()
|
||||
.await
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
use super::events::{Connect, Disconnect, Event, EventBroker, ID};
|
||||
use crate::auth::AuthClaims;
|
||||
use actix::*;
|
||||
use actix_web::{
|
||||
web::{self, Data},
|
||||
Error, HttpRequest, HttpResponse,
|
||||
};
|
||||
use actix_web_actors::ws;
|
||||
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||
|
||||
pub struct EventSession {
|
||||
id: ID,
|
||||
}
|
||||
|
||||
impl Actor for EventSession {
|
||||
type Context = ws::WebsocketContext<Self>;
|
||||
|
||||
fn started(&mut self, ctx: &mut Self::Context) {
|
||||
let addr = ctx.address();
|
||||
EventBroker::from_registry()
|
||||
.send(Connect {
|
||||
addr: addr.recipient(),
|
||||
})
|
||||
.into_actor(self)
|
||||
.then(|res, act, ctx| {
|
||||
match res {
|
||||
Ok(res) => act.id = res,
|
||||
// something is wrong. Burn it with fire.
|
||||
_ => ctx.stop(),
|
||||
}
|
||||
fut::ready(())
|
||||
})
|
||||
.wait(ctx);
|
||||
}
|
||||
fn stopping(&mut self, _: &mut Self::Context) -> Running {
|
||||
// notify suprivisor we are leavin
|
||||
EventBroker::from_registry().do_send(Disconnect { id: self.id });
|
||||
Running::Stop
|
||||
}
|
||||
}
|
||||
|
||||
impl Handler<Event> for EventSession {
|
||||
type Result = ();
|
||||
|
||||
fn handle(&mut self, msg: Event, ctx: &mut Self::Context) -> Self::Result {
|
||||
let data = serde_json::to_string(&msg).unwrap_or_default();
|
||||
ctx.text(data);
|
||||
}
|
||||
}
|
||||
|
||||
impl StreamHandler<Result<ws::Message, ws::ProtocolError>> for EventSession {
|
||||
fn handle(&mut self, msg: Result<ws::Message, ws::ProtocolError>, ctx: &mut Self::Context) {
|
||||
match msg {
|
||||
Ok(ws::Message::Ping(msg)) => ctx.pong(&msg),
|
||||
Ok(ws::Message::Text(text)) => ctx.text(text),
|
||||
Ok(ws::Message::Binary(bin)) => ctx.binary(bin),
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn event_session_index(
|
||||
req: HttpRequest,
|
||||
stream: web::Payload,
|
||||
state: Data<crate::AppState>,
|
||||
path: web::Path<String>,
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let secret = crate::auth::get_secret(&state.db).await?;
|
||||
|
||||
let decoded = decode::<AuthClaims>(
|
||||
&path,
|
||||
&DecodingKey::from_secret(&secret),
|
||||
&Validation::default(),
|
||||
);
|
||||
match decoded {
|
||||
Ok(_) => ws::start(EventSession { id: 0 }, &req, stream),
|
||||
Err(e) => Err(actix_web::error::ErrorUnauthorized(e.to_string())),
|
||||
}
|
||||
}
|
|
@ -30,7 +30,6 @@ label {
|
|||
color: #fff;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<template #actions>
|
||||
<btn @click="delCategory" label="Delete" danger v-if="!!this.category.id" />
|
||||
<btn @click="close" label="Cancel" />
|
||||
<btn @click="submit" :label="saveLabel" />
|
||||
<btn primary @click="submit" :label="saveLabel" />
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
|
|
@ -13,12 +13,13 @@
|
|||
<text-field v-model="app.description" label="Description" />
|
||||
<text-field v-model="app.url" label="URL" />
|
||||
<icon-picker v-model="app.glyph" />
|
||||
<switch-field v-model="app.enableHealthcheck" label="Health check" />
|
||||
</form>
|
||||
</template>
|
||||
<template #actions>
|
||||
<btn @click="delApp" label="Delete" danger v-if="!!this.app.id" />
|
||||
<btn @click="close" label="Cancel" />
|
||||
<btn :label="saveLabel" type="button" @click="save" />
|
||||
<btn primary :label="saveLabel" type="button" @click="save" />
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
@ -29,9 +30,10 @@ import IconPicker from "./IconPicker.vue";
|
|||
import axios from "axios";
|
||||
import Btn from "./Button.vue";
|
||||
import SelectField from "./SelectField.vue";
|
||||
import SwitchField from './Switch.vue';
|
||||
|
||||
export default {
|
||||
components: { TextField, Modal, IconPicker, Btn, SelectField },
|
||||
components: { TextField, Modal, IconPicker, Btn, SelectField, SwitchField },
|
||||
props: ["open", "mode", "data", "categories"],
|
||||
watch: {
|
||||
data: function (next) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="tile" @click="click">
|
||||
<div :class="{tile: true, alive: appData.healthcheckStatus === true, dead: appData.healthcheckStatus === false}" @click="click">
|
||||
<font-awesome-icon :icon="appData.glyph" size="2x" />
|
||||
<div class="label">
|
||||
<div class="title">{{appData.appName}}</div>
|
||||
|
@ -43,6 +43,13 @@ svg {
|
|||
color: #fff;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.tile.alive svg {
|
||||
color: #009900;
|
||||
}
|
||||
.tile.dead svg {
|
||||
color: #900;
|
||||
}
|
||||
.title {
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
<template #actions>
|
||||
<btn @click="delCategory" label="Delete" danger v-if="!!this.category.id" />
|
||||
<btn @click="close" label="Cancel" />
|
||||
<btn @click="submit" :label="saveLabel" />
|
||||
<btn primary @click="submit" :label="saveLabel" />
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<template #actions>
|
||||
<btn @click="delBookmark" label="Delete" danger v-if="!!this.bookmark.id" />
|
||||
<btn @click="close" label="Cancel" />
|
||||
<btn :label="saveLabel" type="button" @click="save" />
|
||||
<btn primary :label="saveLabel" type="button" @click="save" />
|
||||
</template>
|
||||
</modal>
|
||||
</template>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<button :class="{danger: danger}">{{label}}</button>
|
||||
<button :class="{danger: danger, primary: primary}">{{label}}</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -7,6 +7,7 @@ export default {
|
|||
props: {
|
||||
label: String,
|
||||
danger: Boolean,
|
||||
primary: Boolean,
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
@ -16,26 +17,33 @@ button {
|
|||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
padding: 10px 18px;
|
||||
background: #333;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
border: 2px solid #222;
|
||||
background: #333;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
button.danger {
|
||||
color: #cc0000;
|
||||
background-color: #200;
|
||||
border: 2px solid #200;
|
||||
border: none;
|
||||
background: #21242B;
|
||||
box-shadow: rgba(0, 0, 0, 0.12) 0px 1px 3px, rgba(0, 0, 0, 0.24) 0px 1px 2px;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transition: all 0.2s;
|
||||
background: #444;
|
||||
box-shadow: rgba(0, 0, 0, 0.42) 0px 1px 3px, rgba(0, 0, 0, 0.64) 0px 1px 2px;
|
||||
}
|
||||
|
||||
button.danger {
|
||||
background: #41141B;
|
||||
}
|
||||
|
||||
|
||||
button.danger:hover {
|
||||
background: #400;
|
||||
background: #61141B;
|
||||
}
|
||||
|
||||
button.primary {
|
||||
background: #21244B;
|
||||
}
|
||||
|
||||
|
||||
button.primary:hover {
|
||||
background: #21246B;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -14,10 +14,15 @@
|
|||
}
|
||||
label {
|
||||
margin-right: 15px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
font-weight: 500;
|
||||
line-height: 35px;
|
||||
text-align: right;
|
||||
color: #999;
|
||||
width: 150px;
|
||||
}
|
||||
label::after {
|
||||
content: ':'
|
||||
}
|
||||
.form-field + .form-field {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
</form-field>
|
||||
<div class="icons">
|
||||
<font-awesome-icon
|
||||
:class="{selected: modelValue === icon}"
|
||||
:icon="icon"
|
||||
size="2x"
|
||||
v-for="icon in items"
|
||||
|
@ -1039,7 +1040,7 @@ export default {
|
|||
}
|
||||
return icons
|
||||
.filter((i) => i.includes(this.modelValue.toString().toLowerCase()))
|
||||
.slice(0, 21);
|
||||
.slice(0, 18);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
@ -1061,19 +1062,35 @@ export default {
|
|||
|
||||
input {
|
||||
color: #fff;
|
||||
background-color: #121212;
|
||||
background-color: transparent;
|
||||
outline: none;
|
||||
display: block;
|
||||
border: 1px solid #000;
|
||||
border: none;
|
||||
border-bottom: 1px solid #555;
|
||||
font-weight: bold;
|
||||
padding: 8px 15px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
transition: all 0.2s;
|
||||
border-bottom: 1px solid #336699;
|
||||
}
|
||||
|
||||
.icons {
|
||||
margin-bottom: 15px;
|
||||
height: 130px;
|
||||
background: #21242b;
|
||||
padding: 15px;
|
||||
display: grid;
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
width: calc(100% - 30px);
|
||||
margin-top: 15px;
|
||||
grid-template-columns: repeat(7, 1fr);
|
||||
grid-template-columns: repeat(6, 1fr);
|
||||
justify-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.icons .selected {
|
||||
color: #336699;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -80,7 +80,7 @@ export default {
|
|||
height 300ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1), 0 6px 6px rgba(0, 0, 0, 0.16);
|
||||
|
||||
background: #333;
|
||||
background: #282c33;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
|
@ -96,7 +96,7 @@ export default {
|
|||
}
|
||||
|
||||
.modal .actions {
|
||||
background: #3c3c3c;
|
||||
background: #21242b;
|
||||
border-top: 1px solid #2c2c2c;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<style>
|
||||
.panel {
|
||||
border-radius: 5px;
|
||||
background: #333;
|
||||
background: #21242b;
|
||||
padding: 25px;
|
||||
box-shadow: rgb(0, 0, 0) 0px 20px 30px -10px;
|
||||
display: flex;
|
||||
|
|
|
@ -30,7 +30,8 @@ export default {
|
|||
select {
|
||||
flex: 1;
|
||||
display: block;
|
||||
color: #ccc;
|
||||
color: #fff;
|
||||
font-weight: bold;
|
||||
line-height: 1.3;
|
||||
padding: 0.6em 1.4em 0.5em 0.8em;
|
||||
width: 100%;
|
||||
|
@ -38,10 +39,12 @@ select {
|
|||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
border: none;
|
||||
border-bottom: 1px solid #555;
|
||||
overflow: hideden;
|
||||
-moz-appearance: none;
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
background-color: #121212;
|
||||
background-color: transparent;
|
||||
}
|
||||
select::-ms-expand {
|
||||
display: none;
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
<template>
|
||||
<form-field>
|
||||
<label>{{label}}</label>
|
||||
<div :class="{switch: true, active: modelValue}" @click="handleClick">
|
||||
<div class="indicator"></div>
|
||||
</div>
|
||||
</form-field>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import FormField from './FormField.vue'
|
||||
|
||||
export default {
|
||||
props: ["label", "modelValue"],
|
||||
components: { FormField },
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$emit("update:modelValue", !this.modelValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.switch {
|
||||
background: #21242b;
|
||||
border-radius: 15px;
|
||||
height: 36px;
|
||||
width: 75px;
|
||||
}
|
||||
.indicator {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 13px;
|
||||
background: white;
|
||||
position: relative;
|
||||
top: 4px;
|
||||
left: 5px;
|
||||
background: #282c33;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.switch.active .indicator {
|
||||
transition: all 0.2s;
|
||||
top: 4px;
|
||||
left: 45px;
|
||||
background: #336699;
|
||||
}
|
||||
</style>
|
|
@ -29,11 +29,19 @@ export default {
|
|||
|
||||
<style scoped>
|
||||
input {
|
||||
transition: all 0.2s;
|
||||
color: #fff;
|
||||
background-color: #121212;
|
||||
background-color: transparent;
|
||||
font-weight: bold;
|
||||
outline: none;
|
||||
border: 1px solid #000;
|
||||
border: none;
|
||||
border-bottom: 1px solid #555;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
padding: 8px 15px;
|
||||
}
|
||||
input:focus {
|
||||
transition: all 0.2s;
|
||||
border-bottom: 1px solid #336699;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="dashboard">
|
||||
<div :class="{dashboard: true, 'edit-mode': editMode}">
|
||||
<div class="editmode-tiles">
|
||||
<new-item-tile title="New Application" v-if="editMode" @click="openNewApp" />
|
||||
<new-item-tile title="New App Category" v-if="editMode" @click="openNewAppCat" />
|
||||
|
@ -44,7 +44,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="container" v-for="cat in applicationCategories" :key="cat.id">
|
||||
<h1 @click="appCatClicked(cat)">
|
||||
<h1 @click="appCatClicked(cat)" class="editable">
|
||||
<font-awesome-icon v-if="cat.glyph" :icon="cat.glyph" />
|
||||
{{cat.categoryName}}
|
||||
</h1>
|
||||
|
@ -58,7 +58,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="container">
|
||||
<div class="container" v-if="bookmarks.length">
|
||||
<h1 class="bookmark">BOOKMARKS</h1>
|
||||
<div class="bookmark-container">
|
||||
<div class="bookmark-category">
|
||||
|
@ -71,7 +71,7 @@
|
|||
/>
|
||||
</div>
|
||||
<div class="bookmark-category" v-for="cat in bookmarkCategories" :key="cat.id">
|
||||
<h2 @click="bookmarkCatClicked(cat)">
|
||||
<h2 @click="bookmarkCatClicked(cat)" class="editable">
|
||||
<font-awesome-icon v-if="cat.glyph" :icon="cat.glyph" />
|
||||
{{cat.categoryName}}
|
||||
</h2>
|
||||
|
@ -102,7 +102,23 @@ import BookmarkCategoryModal from "../components/BookmarkCategoryModal.vue";
|
|||
|
||||
export default {
|
||||
name: "Dashboard",
|
||||
unmounted() {
|
||||
this.connection = null;
|
||||
},
|
||||
methods: {
|
||||
connect_ws() {
|
||||
const token = localStorage.getItem("token")
|
||||
this.connection = new WebSocket("ws://localhost:8088/events/" + encodeURIComponent(token));
|
||||
this.connection.onmessage = (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
const { app_id, alive } = data.HealthcheckChange;
|
||||
const idx = this.applications.findIndex((app) => app.id == app_id);
|
||||
this.applications[idx].healthcheckStatus = alive;
|
||||
}
|
||||
this.connection.onclose = () => {
|
||||
setTimeout(() => this.connect_ws(), 1000);
|
||||
};
|
||||
},
|
||||
appsForCategory(catId) {
|
||||
return this.applications.filter((i) => i.applicationCategoryId === catId);
|
||||
},
|
||||
|
@ -179,12 +195,16 @@ export default {
|
|||
this.bookmarkCategories = (
|
||||
await axios.get("/api/bookmark_categories")
|
||||
).data.items;
|
||||
|
||||
this.editApp = {};
|
||||
this.newAppOpen = false;
|
||||
this.editAppCat = {};
|
||||
this.appOpen = false;
|
||||
this.appCatOpen = false;
|
||||
|
||||
this.editBookmark = {};
|
||||
this.editBookmarkCat = {};
|
||||
this.bookmarkCatOpen = false;
|
||||
this.bookmarkOpen = false;
|
||||
this.editAppCat = {};
|
||||
},
|
||||
},
|
||||
components: {
|
||||
|
@ -212,9 +232,11 @@ export default {
|
|||
editBookmarkCat: {},
|
||||
bookmarks: [],
|
||||
bookmarkCategories: [],
|
||||
connection: null,
|
||||
};
|
||||
},
|
||||
async mounted() {
|
||||
this.connect_ws();
|
||||
this.reload();
|
||||
},
|
||||
};
|
||||
|
@ -237,6 +259,10 @@ h2 {
|
|||
font-weight: bold;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.edit-mode .editable {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
h2 svg {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export default {
|
|||
}
|
||||
input {
|
||||
background: #333;
|
||||
font-size: 40px;
|
||||
font-size: 20px;
|
||||
color: #fff;
|
||||
background: #121212;
|
||||
font-weight: bold;
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<panel>
|
||||
<h1>Setup a password</h1>
|
||||
<text-field v-model="password" password />
|
||||
<btn label="Setup" @click="setup" />
|
||||
<btn primary label="Setup" @click="setup" />
|
||||
</panel>
|
||||
</div>
|
||||
</template>
|
||||
|
|
Loading…
Reference in New Issue