TRUSS-2 working on moving to Rust
This commit is contained in:
15
.gitignore
vendored
Normal file
15
.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
# Rust build output
|
||||||
|
/target
|
||||||
|
|
||||||
|
# Generated static output (dynamic server mode is the intended workflow)
|
||||||
|
/output
|
||||||
|
|
||||||
|
# Python artifacts left over from the previous implementation
|
||||||
|
__pycache__/
|
||||||
|
*.py[cod]
|
||||||
|
|
||||||
|
# Editor/OS noise
|
||||||
|
.DS_Store
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
746
Cargo.lock
generated
Normal file
746
Cargo.lock
generated
Normal file
@@ -0,0 +1,746 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "aho-corasick"
|
||||||
|
version = "1.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba"
|
||||||
|
dependencies = [
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "atomic-waker"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "31b698c5f9a010f6573133b09e0de5408834d0c82f8d7475a89fc1867a71cd90"
|
||||||
|
dependencies = [
|
||||||
|
"axum-core",
|
||||||
|
"bytes",
|
||||||
|
"form_urlencoded",
|
||||||
|
"futures-util",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"hyper",
|
||||||
|
"hyper-util",
|
||||||
|
"itoa",
|
||||||
|
"matchit",
|
||||||
|
"memchr",
|
||||||
|
"mime",
|
||||||
|
"percent-encoding",
|
||||||
|
"pin-project-lite",
|
||||||
|
"serde_core",
|
||||||
|
"serde_json",
|
||||||
|
"serde_path_to_error",
|
||||||
|
"serde_urlencoded",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-core"
|
||||||
|
version = "0.5.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bumpalo"
|
||||||
|
version = "3.20.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytes"
|
||||||
|
version = "1.12.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cfg-if"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "errno"
|
||||||
|
version = "0.3.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "form_urlencoded"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-channel"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"r-efi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"itoa",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-body"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "http-body-util"
|
||||||
|
version = "0.1.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"pin-project-lite",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httparse"
|
||||||
|
version = "1.10.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "httpdate"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d22053281f852e11534f5198498373cbb59295120a20771d90f7ed1897490a72"
|
||||||
|
dependencies = [
|
||||||
|
"atomic-waker",
|
||||||
|
"bytes",
|
||||||
|
"futures-channel",
|
||||||
|
"futures-core",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"httparse",
|
||||||
|
"httpdate",
|
||||||
|
"itoa",
|
||||||
|
"pin-project-lite",
|
||||||
|
"smallvec",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hyper-util"
|
||||||
|
version = "0.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"http",
|
||||||
|
"http-body",
|
||||||
|
"hyper",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
"tower-service",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "itoa"
|
||||||
|
version = "1.0.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.104"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0e0c1080212aad755ea003d18543e8768dd432c48819efd73a7bf1e39b7a5a3a"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"futures-util",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.189"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lock_api"
|
||||||
|
version = "0.4.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965"
|
||||||
|
dependencies = [
|
||||||
|
"scopeguard",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.33"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "matchit"
|
||||||
|
version = "0.8.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memchr"
|
||||||
|
version = "2.8.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "memo-map"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "38d1115007560874e373613744c6fba374c17688327a71c1476d1a5954cc857b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mime"
|
||||||
|
version = "0.3.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "minijinja"
|
||||||
|
version = "2.24.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "86886cf6dbf4e614b19c9a1eec9775f021869d7eadde0fc73921a81b90c9b4c9"
|
||||||
|
dependencies = [
|
||||||
|
"memo-map",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mio"
|
||||||
|
version = "1.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "30d65c71f1ce40ab09135ce117d742b9f8a19ff91a41a8b57ed50bc2de59c427"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"wasi",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot"
|
||||||
|
version = "0.12.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a"
|
||||||
|
dependencies = [
|
||||||
|
"lock_api",
|
||||||
|
"parking_lot_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "parking_lot_core"
|
||||||
|
version = "0.9.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"libc",
|
||||||
|
"redox_syscall",
|
||||||
|
"smallvec",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "percent-encoding"
|
||||||
|
version = "2.3.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "proc-macro2"
|
||||||
|
version = "1.0.107"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "quote"
|
||||||
|
version = "1.0.47"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "r-efi"
|
||||||
|
version = "6.0.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_syscall"
|
||||||
|
version = "0.5.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-automata",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-automata"
|
||||||
|
version = "0.4.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2"
|
||||||
|
dependencies = [
|
||||||
|
"aho-corasick",
|
||||||
|
"memchr",
|
||||||
|
"regex-syntax",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "regex-syntax"
|
||||||
|
version = "0.8.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustversion"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ryu"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scopeguard"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba"
|
||||||
|
dependencies = [
|
||||||
|
"serde_core",
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_core"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48"
|
||||||
|
dependencies = [
|
||||||
|
"serde_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.229"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_json"
|
||||||
|
version = "1.0.151"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"memchr",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
"zmij",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_path_to_error"
|
||||||
|
version = "0.1.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"serde",
|
||||||
|
"serde_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_urlencoded"
|
||||||
|
version = "0.7.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||||
|
dependencies = [
|
||||||
|
"form_urlencoded",
|
||||||
|
"itoa",
|
||||||
|
"ryu",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||||
|
dependencies = [
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.15.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "socket2"
|
||||||
|
version = "0.6.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "2.0.119"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "syn"
|
||||||
|
version = "3.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53e9bae58849f64dfa4f5d5ae372c8341f7305f82a3868709269343628b659a3"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "sync_wrapper"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio"
|
||||||
|
version = "1.53.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed"
|
||||||
|
dependencies = [
|
||||||
|
"bytes",
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"parking_lot",
|
||||||
|
"pin-project-lite",
|
||||||
|
"signal-hook-registry",
|
||||||
|
"socket2",
|
||||||
|
"tokio-macros",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "78773a2a397f451582ce068015985c33193cf6dea8b74d2a639fe457b2f07b0e"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 3.0.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower"
|
||||||
|
version = "0.5.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-util",
|
||||||
|
"pin-project-lite",
|
||||||
|
"sync_wrapper",
|
||||||
|
"tokio",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-layer"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tower-service"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing"
|
||||||
|
version = "0.1.44"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tracing-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-core"
|
||||||
|
version = "0.1.36"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "truss"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"minijinja",
|
||||||
|
"regex",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
|
"tokio",
|
||||||
|
"uuid",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-ident"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "uuid"
|
||||||
|
version = "1.24.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2cefc03fd367c0c6d4305de1b312cf00248c4114f4a0418ce6a6af769e3b0bd9"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"js-sys",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.11.1+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen"
|
||||||
|
version = "0.2.127"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b70935747edd64d89de3efa29d73789b806c15798f8e7dca4d8ac356b50ce70"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"once_cell",
|
||||||
|
"rustversion",
|
||||||
|
"wasm-bindgen-macro",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro"
|
||||||
|
version = "0.2.127"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "77775f8f3f7217702089053b94958f8f54061a3f663417df76e19cbdcca29bc1"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"wasm-bindgen-macro-support",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-macro-support"
|
||||||
|
version = "0.2.127"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e11d33f857dc2fb11b8bc75aee111aa9cbeb12cd9f25efd3d4c2a3dd4e235284"
|
||||||
|
dependencies = [
|
||||||
|
"bumpalo",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.119",
|
||||||
|
"wasm-bindgen-shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasm-bindgen-shared"
|
||||||
|
version = "0.2.127"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ef64dbcc55df09c7e5a46182d181c2cfa3e925f3da937ea764728b4bbb9dcbf"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-ident",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.61.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zmij"
|
||||||
|
version = "1.0.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b"
|
||||||
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "truss"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
axum = "0.8"
|
||||||
|
tokio = { version = "1", features = ["full"] }
|
||||||
|
serde = { version = "1", features = ["derive"] }
|
||||||
|
serde_json = "1"
|
||||||
|
uuid = { version = "1", features = ["v4"] }
|
||||||
|
regex = "1"
|
||||||
|
minijinja = { version = "2", features = ["loader", "json"] }
|
||||||
|
|
||||||
45
README.md
Normal file
45
README.md
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
# Truss
|
||||||
|
|
||||||
|
A small, dynamically-rendered kanban server for the Arcline TODO board.
|
||||||
|
|
||||||
|
The server reads `TODO.md` and the per-track board files, renders the board with
|
||||||
|
Rust + Axum + Minijinja, and persists card changes back to the same Markdown files.
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo run
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open <http://localhost:5000>.
|
||||||
|
|
||||||
|
## Board files
|
||||||
|
|
||||||
|
- `TODO.md` is the master board.
|
||||||
|
- `TODO-msp.md` is the MSP / IT track.
|
||||||
|
- `TODO-os.md` is the OS / open-source track.
|
||||||
|
|
||||||
|
The MSP and OS board files are auto-generated from `TODO.md` on startup if they
|
||||||
|
do not already exist.
|
||||||
|
|
||||||
|
## HTTP API
|
||||||
|
|
||||||
|
| Method | Route | Description |
|
||||||
|
| ------ | ----- | ----------- |
|
||||||
|
| `GET` | `/` | Board index |
|
||||||
|
| `GET` | `/{board}` | Rendered board (`msp` or `os`) |
|
||||||
|
| `GET` | `/api/{board}/board` | Board data as JSON |
|
||||||
|
| `POST` | `/api/{board}/cards` | Create a card |
|
||||||
|
| `PUT` | `/api/{board}/cards/{card_id}` | Update a card |
|
||||||
|
| `DELETE` | `/api/{board}/cards/{card_id}` | Delete a card |
|
||||||
|
| `POST` | `/api/{board}/cards/{card_id}/move` | Move a card to another column |
|
||||||
|
| `POST` | `/api/{board}/reload` | Reload a board from disk |
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo test
|
||||||
|
cargo clippy --all-targets --all-features -- -D warnings
|
||||||
|
cargo fmt --check
|
||||||
|
```
|
||||||
|
|
||||||
21
TODO-msp.md
21
TODO-msp.md
@@ -22,6 +22,10 @@ Codenames are architectural/masonry terms — fitting for a company named after
|
|||||||
|
|
||||||
| Codename | Initiative | Track | Status |
|
| Codename | Initiative | Track | Status |
|
||||||
|
|
||||||
|
| Codename | Initiative | Track | Status |
|
||||||
|
|
||||||
|
| Codename | Initiative | Track | Status |
|
||||||
|
|
||||||
| Codename | Initiative | Track | Status |
|
| Codename | Initiative | Track | Status |
|
||||||
|-------------|-----------------------------------------------|-------|-------------|
|
|-------------|-----------------------------------------------|-------|-------------|
|
||||||
| Keystone | Customer portal + billing overhaul | IT | In Progress |
|
| Keystone | Customer portal + billing overhaul | IT | In Progress |
|
||||||
@@ -42,6 +46,19 @@ Codenames are architectural/masonry terms — fitting for a company named after
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -57,17 +74,17 @@ Codenames are architectural/masonry terms — fitting for a company named after
|
|||||||
- [ ] `[IT]` **Lintel** — restructure docs.arcline.it into per-tool sections <!-- id:85851b3d-ecd3-46ea-9870-4cc515670b13 -->
|
- [ ] `[IT]` **Lintel** — restructure docs.arcline.it into per-tool sections <!-- id:85851b3d-ecd3-46ea-9870-4cc515670b13 -->
|
||||||
- [ ] `[IT]` **Plinth** — build referral program signup + tracking flow <!-- id:b4c0421a-3201-424f-adce-f8399d848c05 -->
|
- [ ] `[IT]` **Plinth** — build referral program signup + tracking flow <!-- id:b4c0421a-3201-424f-adce-f8399d848c05 -->
|
||||||
- [ ] `[IT/OS]` **Voussoir** — spec `arcline-migrate` cPanel import mapping <!-- id:86df261e-e1c8-48af-9aa9-551a0da4fe14 -->
|
- [ ] `[IT/OS]` **Voussoir** — spec `arcline-migrate` cPanel import mapping <!-- id:86df261e-e1c8-48af-9aa9-551a0da4fe14 -->
|
||||||
- [ ] `[IT]` — Case studies page — draft first 2 client write-ups <!-- id:88242e2b-7218-42bd-9790-bd33e7649f3e -->
|
|
||||||
- [ ] `[IT]` — Game server plans — add automated backup scheduling to portal <!-- id:c4aae5af-f5f9-418b-b078-a6534c69f27e -->
|
- [ ] `[IT]` — Game server plans — add automated backup scheduling to portal <!-- id:c4aae5af-f5f9-418b-b078-a6534c69f27e -->
|
||||||
|
- [ ] `[IT]` — Case studies page — draft first 2 client write-ups <!-- id:88242e2b-7218-42bd-9790-bd33e7649f3e -->
|
||||||
|
|
||||||
## In Progress
|
## In Progress
|
||||||
|
|
||||||
- [ ] `[IT]` **Keystone** — customer portal (`arcline-portal`) SSL + ticketing UI <!-- id:d4ccf112-a52e-42c4-9ff5-0b986969cfb5 -->
|
|
||||||
- [ ] `[IT]` **Keystone** — Stripe billing (`arcline-billing`) subscription sync <!-- id:e059813f-0c1f-4294-859d-b0b7d36ec5db -->
|
- [ ] `[IT]` **Keystone** — Stripe billing (`arcline-billing`) subscription sync <!-- id:e059813f-0c1f-4294-859d-b0b7d36ec5db -->
|
||||||
|
|
||||||
## In Review
|
## In Review
|
||||||
|
|
||||||
- [ ] `[IT]` **Springer** — status page generator (`arcline-status`) — awaiting Blake's sign-off <!-- id:cfb2ee5e-ffe4-4c0a-9a2e-9e4204979ffd -->
|
- [ ] `[IT]` **Springer** — status page generator (`arcline-status`) — awaiting Blake's sign-off <!-- id:cfb2ee5e-ffe4-4c0a-9a2e-9e4204979ffd -->
|
||||||
|
- [ ] `[IT]` **Keystone** — customer portal (`arcline-portal`) SSL + ticketing UI <!-- id:d4ccf112-a52e-42c4-9ff5-0b986969cfb5 -->
|
||||||
|
|
||||||
## Done
|
## Done
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
601
build.py
601
build.py
@@ -1,601 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
"""Arcline Kanban — multi-board Kanban with CRUD over markdown files."""
|
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import uuid
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from flask import Flask, jsonify, redirect, render_template_string, request, url_for
|
|
||||||
from markupsafe import Markup
|
|
||||||
|
|
||||||
ROOT = Path(__file__).resolve().parent
|
|
||||||
TEMPLATES_DIR = ROOT / "templates"
|
|
||||||
OUTPUT_DIR = ROOT / "output"
|
|
||||||
|
|
||||||
# Board registry — name → file path
|
|
||||||
BOARDS: dict[str, Path] = {
|
|
||||||
"msp": ROOT / "TODO-msp.md",
|
|
||||||
"os": ROOT / "TODO-os.md",
|
|
||||||
}
|
|
||||||
|
|
||||||
app = Flask(__name__, template_folder=str(TEMPLATES_DIR))
|
|
||||||
|
|
||||||
# Per-board in-memory state — loaded on first access, synced on write
|
|
||||||
_boards_data: dict[str, dict] = {}
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Board helpers
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _get_board(name: str) -> dict | None:
|
|
||||||
"""Return in-memory data for *name*, loading from its file if needed."""
|
|
||||||
if name not in BOARDS:
|
|
||||||
return None
|
|
||||||
if name not in _boards_data:
|
|
||||||
_boards_data[name] = parse_todo(BOARDS[name])
|
|
||||||
return _boards_data[name]
|
|
||||||
|
|
||||||
|
|
||||||
def _find_card(board: str, card_id: str) -> tuple[dict, list[dict]] | None:
|
|
||||||
"""Return (card, parent_column_cards_list) or None within *board*."""
|
|
||||||
data = _get_board(board)
|
|
||||||
if data is None:
|
|
||||||
return None
|
|
||||||
for col in data["columns"]:
|
|
||||||
for card in col["cards"]:
|
|
||||||
if card["id"] == card_id:
|
|
||||||
return card, col["cards"]
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def _ensure_board_files() -> None:
|
|
||||||
"""Create any missing board markdown files from TODO.md."""
|
|
||||||
original = ROOT / "TODO.md"
|
|
||||||
if not original.exists():
|
|
||||||
return
|
|
||||||
|
|
||||||
for name, path in BOARDS.items():
|
|
||||||
if path.exists():
|
|
||||||
continue
|
|
||||||
data = parse_todo(original)
|
|
||||||
|
|
||||||
# Customize title per board
|
|
||||||
if name == "msp":
|
|
||||||
data["_header_raw"] = data["_header_raw"].replace(
|
|
||||||
"# Arcline — TODO", "# Arcline MSP — TODO"
|
|
||||||
).replace(
|
|
||||||
"In-house Kanban board for **Arcline IT** ",
|
|
||||||
"Kanban board for **Arcline MSP** — IT / managed-services track. ",
|
|
||||||
)
|
|
||||||
# Filter cards: keep IT and IT/OS
|
|
||||||
for col in data["columns"]:
|
|
||||||
col["cards"] = [
|
|
||||||
c for c in col["cards"]
|
|
||||||
if "IT" in c.get("track", "")
|
|
||||||
]
|
|
||||||
# Filter legend: keep IT, IT/OS
|
|
||||||
data["legend"] = [
|
|
||||||
r for r in data["legend"]
|
|
||||||
if "IT" in r.get("track", "") or "OS" not in r.get("track", "")
|
|
||||||
]
|
|
||||||
elif name == "os":
|
|
||||||
data["_header_raw"] = data["_header_raw"].replace(
|
|
||||||
"# Arcline — TODO", "# Arcline OS — TODO"
|
|
||||||
).replace(
|
|
||||||
"In-house Kanban board for **Arcline IT** ",
|
|
||||||
"Kanban board for **Arcline OS** — open-source / GPL track. ",
|
|
||||||
)
|
|
||||||
# Filter cards: keep OS and IT/OS
|
|
||||||
for col in data["columns"]:
|
|
||||||
col["cards"] = [
|
|
||||||
c for c in col["cards"]
|
|
||||||
if "OS" in c.get("track", "")
|
|
||||||
]
|
|
||||||
# Filter legend: keep OS, IT/OS
|
|
||||||
data["legend"] = [
|
|
||||||
r for r in data["legend"]
|
|
||||||
if "OS" in r.get("track", "") or "IT" not in r.get("track", "")
|
|
||||||
]
|
|
||||||
|
|
||||||
# Regenerate legend raw from filtered legend list
|
|
||||||
if data["legend"]:
|
|
||||||
legend_header = "| Codename | Initiative | Track | Status |"
|
|
||||||
legend_sep = "|-------------|-----------------------------------------------|-------|-------------|"
|
|
||||||
legend_rows = [
|
|
||||||
f"| {r['codename']:<12} | {r['initiative']:<45} | {r['track']:<5} | {r['status']:<11} |"
|
|
||||||
for r in data["legend"]
|
|
||||||
]
|
|
||||||
data["_legend_raw"] = "\n".join([legend_header, legend_sep] + legend_rows)
|
|
||||||
|
|
||||||
# Update notes
|
|
||||||
data["_notes_raw"] = "## Notes\n\n- Board auto-generated from the master TODO.md\n"
|
|
||||||
|
|
||||||
_save_board(data, path)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Parser (parses a TODO.md file into structured data)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def parse_todo(path: Path) -> dict:
|
|
||||||
"""Parse a TODO.md file into a structured dictionary."""
|
|
||||||
text = path.read_text()
|
|
||||||
return {
|
|
||||||
"header": _parse_header(text),
|
|
||||||
"legend": _parse_legend(text),
|
|
||||||
"columns": _parse_columns(text),
|
|
||||||
"notes": _parse_notes(text),
|
|
||||||
"_header_raw": _extract_header_raw(text),
|
|
||||||
"_pre_legend_raw": _extract_pre_legend_raw(text),
|
|
||||||
"_legend_raw": _extract_legend_raw(text),
|
|
||||||
"_post_legend_raw": _extract_post_legend_raw(text),
|
|
||||||
"_notes_raw": _extract_notes_raw(text),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_header(text: str) -> dict:
|
|
||||||
lines = text.splitlines()
|
|
||||||
title = ""
|
|
||||||
subtitle = ""
|
|
||||||
flow_line = ""
|
|
||||||
|
|
||||||
for i, line in enumerate(lines):
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("# ") and not title:
|
|
||||||
title = stripped[2:].strip()
|
|
||||||
for j in range(i + 1, min(i + 8, len(lines))):
|
|
||||||
nxt = lines[j].strip()
|
|
||||||
if nxt.startswith("#") or nxt.startswith("---"):
|
|
||||||
break
|
|
||||||
if not nxt:
|
|
||||||
if subtitle:
|
|
||||||
break
|
|
||||||
continue
|
|
||||||
if not subtitle:
|
|
||||||
subtitle = nxt
|
|
||||||
else:
|
|
||||||
subtitle += " " + nxt
|
|
||||||
elif "Backlog" in stripped and "In Progress" in stripped:
|
|
||||||
flow_line = stripped
|
|
||||||
break
|
|
||||||
|
|
||||||
return {"title": title, "subtitle": subtitle, "flow_line": flow_line}
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_legend(text: str) -> list[dict]:
|
|
||||||
legends: list[dict] = []
|
|
||||||
in_table = False
|
|
||||||
|
|
||||||
for line in text.splitlines():
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("| Codename"):
|
|
||||||
in_table = True
|
|
||||||
continue
|
|
||||||
if in_table:
|
|
||||||
if not stripped.startswith("|"):
|
|
||||||
break
|
|
||||||
if "---" in stripped:
|
|
||||||
continue
|
|
||||||
cells = [c.strip() for c in stripped.split("|")[1:-1]]
|
|
||||||
if len(cells) >= 4:
|
|
||||||
legends.append({
|
|
||||||
"codename": cells[0],
|
|
||||||
"initiative": cells[1],
|
|
||||||
"track": cells[2],
|
|
||||||
"status": cells[3],
|
|
||||||
})
|
|
||||||
return legends
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_columns(text: str) -> list[dict]:
|
|
||||||
columns: list[dict] = []
|
|
||||||
current_col: dict | None = None
|
|
||||||
|
|
||||||
for line in text.splitlines():
|
|
||||||
stripped = line.strip()
|
|
||||||
|
|
||||||
if stripped.startswith("## Backlog"):
|
|
||||||
current_col = {"name": "Backlog", "cards": []}
|
|
||||||
columns.append(current_col)
|
|
||||||
elif stripped.startswith("## In Progress"):
|
|
||||||
current_col = {"name": "In Progress", "cards": []}
|
|
||||||
columns.append(current_col)
|
|
||||||
elif stripped.startswith("## In Review"):
|
|
||||||
current_col = {"name": "In Review", "cards": []}
|
|
||||||
columns.append(current_col)
|
|
||||||
elif stripped.startswith("## Done"):
|
|
||||||
current_col = {"name": "Done", "cards": []}
|
|
||||||
columns.append(current_col)
|
|
||||||
elif stripped.startswith("## ") and current_col and current_col["name"] != "Done":
|
|
||||||
break
|
|
||||||
|
|
||||||
if current_col and _is_card_line(stripped):
|
|
||||||
card = _parse_card(stripped)
|
|
||||||
if card:
|
|
||||||
current_col["cards"].append(card)
|
|
||||||
|
|
||||||
return columns
|
|
||||||
|
|
||||||
|
|
||||||
def _is_card_line(line: str) -> bool:
|
|
||||||
return line.startswith("- [ ]") or line.startswith("- [x]")
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_card(line: str) -> dict | None:
|
|
||||||
id_match = re.search(r"<!--\s*id:(\S+)\s*-->", line)
|
|
||||||
card_id = id_match.group(1) if id_match else str(uuid.uuid4())
|
|
||||||
clean_line = re.sub(r"<!--\s*id:\S+\s*-->", "", line).strip()
|
|
||||||
|
|
||||||
checked = clean_line.startswith("- [x]")
|
|
||||||
rest = re.sub(r"^- \[[ x]\]\s*", "", clean_line)
|
|
||||||
|
|
||||||
track_match = re.match(r"`\[([^\]]+)\]`", rest)
|
|
||||||
track = track_match.group(1) if track_match else ""
|
|
||||||
if track_match:
|
|
||||||
rest = rest[track_match.end():].strip()
|
|
||||||
|
|
||||||
codename_match = re.match(r"\*\*([^*]+)\*\*", rest)
|
|
||||||
codename = codename_match.group(1) if codename_match else ""
|
|
||||||
if codename_match:
|
|
||||||
rest = rest[codename_match.end():].strip()
|
|
||||||
|
|
||||||
rest = re.sub(r"^—\s*", "", rest)
|
|
||||||
description = rest.strip()
|
|
||||||
|
|
||||||
return {
|
|
||||||
"id": card_id,
|
|
||||||
"track": track,
|
|
||||||
"codename": codename,
|
|
||||||
"description": description,
|
|
||||||
"checked": checked,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_notes(text: str) -> list[str]:
|
|
||||||
notes: list[str] = []
|
|
||||||
in_notes = False
|
|
||||||
|
|
||||||
for line in text.splitlines():
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("## Notes"):
|
|
||||||
in_notes = True
|
|
||||||
continue
|
|
||||||
if in_notes:
|
|
||||||
if stripped.startswith("## "):
|
|
||||||
break
|
|
||||||
if stripped.startswith("- "):
|
|
||||||
notes.append(stripped[2:].strip())
|
|
||||||
|
|
||||||
return notes
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Raw-section extractors
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _extract_header_raw(text: str) -> str:
|
|
||||||
lines = text.splitlines()
|
|
||||||
out: list[str] = []
|
|
||||||
for line in lines:
|
|
||||||
if line.strip().startswith("## Codename Legend"):
|
|
||||||
break
|
|
||||||
out.append(line)
|
|
||||||
return "\n".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def _extract_pre_legend_raw(text: str) -> str:
|
|
||||||
lines = text.splitlines()
|
|
||||||
out: list[str] = []
|
|
||||||
in_section = False
|
|
||||||
for line in lines:
|
|
||||||
if line.strip().startswith("## Codename Legend"):
|
|
||||||
in_section = True
|
|
||||||
if in_section:
|
|
||||||
out.append(line)
|
|
||||||
if line.strip().startswith("|"):
|
|
||||||
break
|
|
||||||
return "\n".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def _extract_legend_raw(text: str) -> str:
|
|
||||||
lines = text.splitlines()
|
|
||||||
out: list[str] = []
|
|
||||||
in_table = False
|
|
||||||
for line in lines:
|
|
||||||
stripped = line.strip()
|
|
||||||
if stripped.startswith("| Codename"):
|
|
||||||
in_table = True
|
|
||||||
out.append(line)
|
|
||||||
continue
|
|
||||||
if in_table:
|
|
||||||
if stripped.startswith("|"):
|
|
||||||
out.append(line)
|
|
||||||
else:
|
|
||||||
if stripped == "":
|
|
||||||
out.append(line)
|
|
||||||
else:
|
|
||||||
break
|
|
||||||
return "\n".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def _extract_post_legend_raw(text: str) -> str:
|
|
||||||
lines = text.splitlines()
|
|
||||||
out: list[str] = []
|
|
||||||
found_legend_end = False
|
|
||||||
found_sep = False
|
|
||||||
for line in lines:
|
|
||||||
stripped = line.strip()
|
|
||||||
if found_legend_end and not found_sep:
|
|
||||||
if stripped == "---":
|
|
||||||
found_sep = True
|
|
||||||
out.append(line)
|
|
||||||
elif stripped == "":
|
|
||||||
out.append(line)
|
|
||||||
continue
|
|
||||||
if found_sep:
|
|
||||||
if stripped.startswith("## "):
|
|
||||||
break
|
|
||||||
out.append(line)
|
|
||||||
continue
|
|
||||||
if stripped.startswith("## Codename Legend"):
|
|
||||||
found_legend_end = False
|
|
||||||
if stripped.startswith("|"):
|
|
||||||
found_legend_end = True
|
|
||||||
return "\n".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
def _extract_notes_raw(text: str) -> str:
|
|
||||||
lines = text.splitlines()
|
|
||||||
out: list[str] = []
|
|
||||||
in_notes = False
|
|
||||||
for line in lines:
|
|
||||||
if line.strip().startswith("## Notes"):
|
|
||||||
in_notes = True
|
|
||||||
if in_notes:
|
|
||||||
out.append(line)
|
|
||||||
return "\n".join(out)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Serializer
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def _card_to_md(card: dict) -> str:
|
|
||||||
checked = "x" if card["checked"] else " "
|
|
||||||
parts = [f"- [{checked}]"]
|
|
||||||
if card.get("track"):
|
|
||||||
parts.append(f"`[{card['track']}]`")
|
|
||||||
if card.get("codename"):
|
|
||||||
parts.append(f"**{card['codename']}**")
|
|
||||||
if card.get("description"):
|
|
||||||
parts.append(f"— {card['description']}")
|
|
||||||
line = " ".join(parts)
|
|
||||||
if card.get("id"):
|
|
||||||
line += f" <!-- id:{card['id']} -->"
|
|
||||||
return line
|
|
||||||
|
|
||||||
|
|
||||||
def _save_board(data: dict, path: Path | None = None) -> None:
|
|
||||||
"""Persist board data to its markdown file."""
|
|
||||||
if path is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
sections: list[str] = []
|
|
||||||
|
|
||||||
sections.append(data["_header_raw"].rstrip())
|
|
||||||
sections.append("")
|
|
||||||
sections.append(data["_pre_legend_raw"].rstrip())
|
|
||||||
sections.append("")
|
|
||||||
sections.append(data["_legend_raw"].rstrip())
|
|
||||||
sections.append("")
|
|
||||||
sections.append(data["_post_legend_raw"].rstrip())
|
|
||||||
|
|
||||||
for col in data["columns"]:
|
|
||||||
sections.append("")
|
|
||||||
sections.append(f"## {col['name']}")
|
|
||||||
sections.append("")
|
|
||||||
for card in col["cards"]:
|
|
||||||
sections.append(_card_to_md(card))
|
|
||||||
|
|
||||||
sections.append("")
|
|
||||||
sections.append(data["_notes_raw"].rstrip())
|
|
||||||
sections.append("")
|
|
||||||
|
|
||||||
path.write_text("\n".join(sections) + "\n")
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Jinja2 filter
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@app.template_filter("inline_md")
|
|
||||||
def _inline_markdown(text: str) -> Markup:
|
|
||||||
text = re.sub(r"\*\*([^*]+)\*\*", r"<strong>\1</strong>", text)
|
|
||||||
text = re.sub(r"\*([^*]+)\*", r"<em>\1</em>", text)
|
|
||||||
text = re.sub(r"\[([^\]]+)]\(([^)]+)\)", r'<a href="\2">\1</a>', text)
|
|
||||||
return Markup(text)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Routes — pages
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@app.route("/")
|
|
||||||
def home():
|
|
||||||
"""Board selector."""
|
|
||||||
board_list = [
|
|
||||||
{"slug": slug, "title": _get_board(slug)["header"]["title"] if _get_board(slug) else slug.upper()}
|
|
||||||
for slug in BOARDS
|
|
||||||
]
|
|
||||||
return render_template_string(
|
|
||||||
(TEMPLATES_DIR / "home.html").read_text(),
|
|
||||||
boards=board_list,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/<board>")
|
|
||||||
def board_page(board: str):
|
|
||||||
"""Render a specific Kanban board."""
|
|
||||||
data = _get_board(board)
|
|
||||||
if data is None:
|
|
||||||
return redirect(url_for("home"))
|
|
||||||
return render_template_string(
|
|
||||||
(TEMPLATES_DIR / "board.html").read_text(),
|
|
||||||
board_slug=board,
|
|
||||||
**{k: v for k, v in data.items() if not k.startswith("_")},
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Routes — REST API (per-board)
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
@app.route("/api/<board>/board")
|
|
||||||
def api_board(board: str):
|
|
||||||
data = _get_board(board)
|
|
||||||
if data is None:
|
|
||||||
return jsonify({"ok": False, "error": "Unknown board"}), 404
|
|
||||||
return jsonify({
|
|
||||||
"header": data["header"],
|
|
||||||
"legend": data["legend"],
|
|
||||||
"columns": data["columns"],
|
|
||||||
"notes": data["notes"],
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/<board>/cards", methods=["POST"])
|
|
||||||
def create_card(board: str):
|
|
||||||
data = _get_board(board)
|
|
||||||
if data is None:
|
|
||||||
return jsonify({"ok": False, "error": "Unknown board"}), 404
|
|
||||||
|
|
||||||
payload = request.get_json(silent=True) or {}
|
|
||||||
column_name = payload.get("column", "Backlog")
|
|
||||||
card = {
|
|
||||||
"id": str(uuid.uuid4()),
|
|
||||||
"track": payload.get("track", ""),
|
|
||||||
"codename": payload.get("codename", ""),
|
|
||||||
"description": payload.get("description", ""),
|
|
||||||
"checked": bool(payload.get("checked", False)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for col in data["columns"]:
|
|
||||||
if col["name"] == column_name:
|
|
||||||
col["cards"].append(card)
|
|
||||||
_save_board(data, BOARDS[board])
|
|
||||||
return jsonify({"ok": True, "card": card}), 201
|
|
||||||
|
|
||||||
return jsonify({"ok": False, "error": f"Unknown column: {column_name}"}), 400
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/<board>/cards/<card_id>", methods=["PUT"])
|
|
||||||
def update_card(board: str, card_id: str):
|
|
||||||
data = _get_board(board)
|
|
||||||
if data is None:
|
|
||||||
return jsonify({"ok": False, "error": "Unknown board"}), 404
|
|
||||||
|
|
||||||
payload = request.get_json(silent=True) or {}
|
|
||||||
result = _find_card(board, card_id)
|
|
||||||
if result is None:
|
|
||||||
return jsonify({"ok": False, "error": "Card not found"}), 404
|
|
||||||
|
|
||||||
card, _cards_list = result
|
|
||||||
for field in ("track", "codename", "description", "checked"):
|
|
||||||
if field in payload:
|
|
||||||
card[field] = payload[field]
|
|
||||||
|
|
||||||
_save_board(data, BOARDS[board])
|
|
||||||
return jsonify({"ok": True, "card": card})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/<board>/cards/<card_id>/move", methods=["POST"])
|
|
||||||
def move_card(board: str, card_id: str):
|
|
||||||
data = _get_board(board)
|
|
||||||
if data is None:
|
|
||||||
return jsonify({"ok": False, "error": "Unknown board"}), 404
|
|
||||||
|
|
||||||
payload = request.get_json(silent=True) or {}
|
|
||||||
target_column = payload.get("column")
|
|
||||||
if not target_column:
|
|
||||||
return jsonify({"ok": False, "error": "Missing 'column' field"}), 400
|
|
||||||
|
|
||||||
result = _find_card(board, card_id)
|
|
||||||
if result is None:
|
|
||||||
return jsonify({"ok": False, "error": "Card not found"}), 404
|
|
||||||
|
|
||||||
card, source_list = result
|
|
||||||
target_list = None
|
|
||||||
for col in data["columns"]:
|
|
||||||
if col["name"] == target_column:
|
|
||||||
target_list = col["cards"]
|
|
||||||
break
|
|
||||||
|
|
||||||
if target_list is None:
|
|
||||||
return jsonify({"ok": False, "error": f"Unknown column: {target_column}"}), 400
|
|
||||||
|
|
||||||
source_list.remove(card)
|
|
||||||
target_list.append(card)
|
|
||||||
_save_board(data, BOARDS[board])
|
|
||||||
return jsonify({"ok": True, "card": card})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/<board>/cards/<card_id>", methods=["DELETE"])
|
|
||||||
def delete_card(board: str, card_id: str):
|
|
||||||
data = _get_board(board)
|
|
||||||
if data is None:
|
|
||||||
return jsonify({"ok": False, "error": "Unknown board"}), 404
|
|
||||||
|
|
||||||
result = _find_card(board, card_id)
|
|
||||||
if result is None:
|
|
||||||
return jsonify({"ok": False, "error": "Card not found"}), 404
|
|
||||||
|
|
||||||
card, cards_list = result
|
|
||||||
cards_list.remove(card)
|
|
||||||
_save_board(data, BOARDS[board])
|
|
||||||
return jsonify({"ok": True})
|
|
||||||
|
|
||||||
|
|
||||||
@app.route("/api/<board>/reload", methods=["POST"])
|
|
||||||
def reload_board(board: str):
|
|
||||||
if board not in BOARDS:
|
|
||||||
return jsonify({"ok": False, "error": "Unknown board"}), 404
|
|
||||||
_boards_data.pop(board, None)
|
|
||||||
_get_board(board)
|
|
||||||
return jsonify({"ok": True})
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
# Main
|
|
||||||
# ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
def main() -> None:
|
|
||||||
_ensure_board_files()
|
|
||||||
|
|
||||||
if "--build" in sys.argv:
|
|
||||||
env = app.jinja_env
|
|
||||||
template = env.from_string((TEMPLATES_DIR / "board.html").read_text())
|
|
||||||
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
|
|
||||||
for slug in BOARDS:
|
|
||||||
data = _get_board(slug)
|
|
||||||
if data is None:
|
|
||||||
continue
|
|
||||||
html = template.render(
|
|
||||||
board_slug=slug,
|
|
||||||
**{k: v for k, v in data.items() if not k.startswith("_")},
|
|
||||||
)
|
|
||||||
(OUTPUT_DIR / f"{slug}.html").write_text(html)
|
|
||||||
print(f"✅ Rendered {slug} → {OUTPUT_DIR / f'{slug}.html'}")
|
|
||||||
else:
|
|
||||||
print("🚀 Serving Kanban boards at http://127.0.0.1:5000")
|
|
||||||
for slug in BOARDS:
|
|
||||||
print(f" /{slug}")
|
|
||||||
app.run(debug=True, host="127.0.0.1", port=5000)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
|
|
||||||
@@ -1,789 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>Arcline — TODO</title>
|
|
||||||
<style>
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Reset & base */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--bg: #f4f5f7;
|
|
||||||
--surface: #ffffff;
|
|
||||||
--border: #dfe1e6;
|
|
||||||
--text: #172b4d;
|
|
||||||
--muted: #6b778c;
|
|
||||||
--track-it: #0052cc;
|
|
||||||
--track-os: #36b37e;
|
|
||||||
--track-both: #6554c0;
|
|
||||||
--backlog: #dfe1e6;
|
|
||||||
--inprogress: #ffc400;
|
|
||||||
--inreview: #00b8d9;
|
|
||||||
--done: #36b37e;
|
|
||||||
--card-shadow: 0 1px 3px rgba(9,30,66,0.08);
|
|
||||||
--card-hover: 0 2px 6px rgba(9,30,66,0.14);
|
|
||||||
--radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
|
||||||
"Helvetica Neue", Arial, sans-serif;
|
|
||||||
background: var(--bg);
|
|
||||||
color: var(--text);
|
|
||||||
line-height: 1.5;
|
|
||||||
min-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Header */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
header {
|
|
||||||
background: var(--surface);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
padding: 24px 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header h1 {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
header h1 .logo {
|
|
||||||
background: linear-gradient(135deg, #0052cc, #6554c0);
|
|
||||||
color: #fff;
|
|
||||||
width: 32px; height: 32px;
|
|
||||||
border-radius: 6px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
header .subtitle {
|
|
||||||
color: var(--muted);
|
|
||||||
margin-top: 4px;
|
|
||||||
font-size: 0.95rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Legend */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
.legend-bar {
|
|
||||||
background: var(--surface);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
padding: 12px 32px;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 16px;
|
|
||||||
align-items: center;
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-bar strong { color: var(--text); }
|
|
||||||
|
|
||||||
.track-badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 3px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.track-it { background: var(--track-it); }
|
|
||||||
.track-os { background: var(--track-os); }
|
|
||||||
.track-both { background: var(--track-both); }
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Legend table */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
.legend-table-wrap {
|
|
||||||
padding: 20px 32px 16px;
|
|
||||||
background: var(--surface);
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.legend-table-wrap h2 {
|
|
||||||
font-size: 1rem;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
color: var(--muted);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.legend {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.legend th {
|
|
||||||
text-align: left;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-bottom: 2px solid var(--border);
|
|
||||||
color: var(--muted);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.legend td {
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-bottom: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
table.legend tr:last-child td { border-bottom: none; }
|
|
||||||
|
|
||||||
.status-pill {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 2px 10px;
|
|
||||||
border-radius: 12px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
.status-backlog { background: var(--backlog); color: #42526e; }
|
|
||||||
.status-inprogress { background: var(--inprogress); color: #172b4d; }
|
|
||||||
.status-inreview { background: var(--inreview); }
|
|
||||||
.status-done { background: var(--done); }
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Board */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
.board {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
padding: 24px 32px;
|
|
||||||
overflow-x: auto;
|
|
||||||
min-height: calc(100vh - 340px);
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 280px;
|
|
||||||
max-width: 380px;
|
|
||||||
background: #ebecf0;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
padding: 12px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column.done { background: #e3fcef; }
|
|
||||||
|
|
||||||
.column-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
padding: 4px 8px 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.column-header h2 {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
font-weight: 600;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
color: var(--muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.column-count {
|
|
||||||
background: rgba(9,30,66,0.08);
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-weight: 600;
|
|
||||||
padding: 2px 8px;
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Cards */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
.card-list {
|
|
||||||
list-style: none;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
min-height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card {
|
|
||||||
background: var(--surface);
|
|
||||||
border-radius: 6px;
|
|
||||||
padding: 12px 14px;
|
|
||||||
box-shadow: var(--card-shadow);
|
|
||||||
cursor: default;
|
|
||||||
transition: box-shadow 0.15s ease, transform 0.15s ease;
|
|
||||||
border-left: 3px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card:hover {
|
|
||||||
box-shadow: var(--card-hover);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card.checked {
|
|
||||||
opacity: 0.65;
|
|
||||||
text-decoration: line-through;
|
|
||||||
text-decoration-color: var(--muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.card.track-it { border-left-color: var(--track-it); }
|
|
||||||
.card.track-os { border-left-color: var(--track-os); }
|
|
||||||
.card.track-both { border-left-color: var(--track-both); }
|
|
||||||
|
|
||||||
.card .card-meta {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 6px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card .codename {
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card .description {
|
|
||||||
font-size: 0.82rem;
|
|
||||||
color: var(--muted);
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Notes */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
.notes {
|
|
||||||
padding: 20px 32px 32px;
|
|
||||||
background: var(--surface);
|
|
||||||
border-top: 1px solid var(--border);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notes h2 {
|
|
||||||
font-size: 0.95rem;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
color: var(--muted);
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.05em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notes ul {
|
|
||||||
list-style: disc inside;
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Footer */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
footer {
|
|
||||||
text-align: center;
|
|
||||||
padding: 16px;
|
|
||||||
font-size: 0.78rem;
|
|
||||||
color: var(--muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
footer a {
|
|
||||||
color: var(--track-it);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
/* Responsive */
|
|
||||||
/* ------------------------------------------------------------------ */
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.board { flex-direction: column; padding: 16px; }
|
|
||||||
.column { max-width: 100%; }
|
|
||||||
header, .legend-bar, .legend-table-wrap, .notes { padding-left: 16px; padding-right: 16px; }
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
|
|
||||||
<!-- ================================================================= -->
|
|
||||||
<!-- Header -->
|
|
||||||
<!-- ================================================================= -->
|
|
||||||
<header>
|
|
||||||
<h1>
|
|
||||||
<span class="logo">⌂</span>
|
|
||||||
Arcline — TODO
|
|
||||||
</h1>
|
|
||||||
<p class="subtitle">In-house Kanban board for <strong>Arcline IT</strong> (<a href="https://arcline.it">arcline.it</a>) and <strong>The Arcline Project</strong> (<a href="https://arclineproject.org">arclineproject.org</a>).</p>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- ================================================================= -->
|
|
||||||
<!-- Legend bar -->
|
|
||||||
<!-- ================================================================= -->
|
|
||||||
<div class="legend-bar">
|
|
||||||
<span><strong>Track:</strong></span>
|
|
||||||
<span class="track-badge track-it">IT</span> arcline.it
|
|
||||||
<span class="track-badge track-os">OS</span> arclineproject.org
|
|
||||||
<span class="track-badge track-both">IT/OS</span> shared
|
|
||||||
<span style="margin-left:auto">Move cards between sections as they progress: `Backlog → In Progress → In Review → Done`.</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ================================================================= -->
|
|
||||||
<!-- Codename Legend table -->
|
|
||||||
<!-- ================================================================= -->
|
|
||||||
|
|
||||||
<div class="legend-table-wrap">
|
|
||||||
<h2>Codename Legend</h2>
|
|
||||||
<table class="legend">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Codename</th>
|
|
||||||
<th>Initiative</th>
|
|
||||||
<th>Track</th>
|
|
||||||
<th>Status</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>Keystone</strong></td>
|
|
||||||
<td>Customer portal + billing overhaul</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="status-pill status-inprogress">In Progress</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>Cornerstone</strong></td>
|
|
||||||
<td>Arcline OS alpha → public ISO release</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="status-pill status-inprogress">In Progress</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>Buttress</strong></td>
|
|
||||||
<td>`arcline-audit` hardening & CVE scan integration</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="status-pill status-backlog">Backlog</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>Voussoir</strong></td>
|
|
||||||
<td>Migration tool (cPanel/Plesk → Arcline)</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="track-badge track-both">IT/OS</span>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="status-pill status-backlog">Backlog</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>Springer</strong></td>
|
|
||||||
<td>Status page + uptime monitoring rollout</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="status-pill status-inreview">In Review</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>Rampart</strong></td>
|
|
||||||
<td>Firewall/nftables default-deny profile pass</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="status-pill status-backlog">Backlog</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>Lintel</strong></td>
|
|
||||||
<td>Docs site (docs.arcline.it) restructure</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="status-pill status-backlog">Backlog</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>Plinth</strong></td>
|
|
||||||
<td>Referral program launch</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="status-pill status-backlog">Backlog</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<tr>
|
|
||||||
<td><strong>Abutment</strong></td>
|
|
||||||
<td>Workstation (KDE) build target</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
|
|
||||||
<span class="status-pill status-backlog">Backlog</span>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- ================================================================= -->
|
|
||||||
<!-- Board columns -->
|
|
||||||
<!-- ================================================================= -->
|
|
||||||
<div class="board">
|
|
||||||
|
|
||||||
<div class="column">
|
|
||||||
<div class="column-header">
|
|
||||||
<h2>Backlog</h2>
|
|
||||||
<span class="column-count">9</span>
|
|
||||||
</div>
|
|
||||||
<ul class="card-list">
|
|
||||||
|
|
||||||
<li class="card track-os">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="codename">Buttress</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">wire `arcline-audit` into CI for nightly CVE scans</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-os">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="codename">Rampart</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">finalize default-deny nftables ruleset, document opt-in exceptions</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-os">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="codename">Abutment</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">scope KDE Plasma workstation ISO target</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-it">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="codename">Lintel</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">restructure docs.arcline.it into per-tool sections</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-it">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="codename">Plinth</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">build referral program signup + tracking flow</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-both">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-both">IT/OS</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="codename">Voussoir</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">spec `arcline-migrate` cPanel import mapping</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-it">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">Case studies page — draft first 2 client write-ups</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-os">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">Grafana dashboard pack — ship default node_exporter + Loki dashboards</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-it">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">Game server plans — add automated backup scheduling to portal</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column">
|
|
||||||
<div class="column-header">
|
|
||||||
<h2>In Progress</h2>
|
|
||||||
<span class="column-count">4</span>
|
|
||||||
</div>
|
|
||||||
<ul class="card-list">
|
|
||||||
|
|
||||||
<li class="card track-it">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="codename">Keystone</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">customer portal (`arcline-portal`) SSL + ticketing UI</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-it">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="codename">Keystone</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">Stripe billing (`arcline-billing`) subscription sync</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-os">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="codename">Cornerstone</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">ISO build pipeline (Debian Bookworm base)</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-os">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="codename">Cornerstone</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">btrfs snapshot rollback QA pass</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column">
|
|
||||||
<div class="column-header">
|
|
||||||
<h2>In Review</h2>
|
|
||||||
<span class="column-count">2</span>
|
|
||||||
</div>
|
|
||||||
<ul class="card-list">
|
|
||||||
|
|
||||||
<li class="card track-it">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
|
|
||||||
<span class="codename">Springer</span>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">status page generator (`arcline-status`) — awaiting Blake's sign-off</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-os">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">`arcline-dns` — multi-resolver propagation checker — code review open</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="column done">
|
|
||||||
<div class="column-header">
|
|
||||||
<h2>Done</h2>
|
|
||||||
<span class="column-count">4</span>
|
|
||||||
</div>
|
|
||||||
<ul class="card-list">
|
|
||||||
|
|
||||||
<li class="card track-it checked">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">Site relaunch — arcline.it copy + pricing page</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-os checked">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">Site launch — arclineproject.org landing page</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-os checked">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-os">OS</span>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">Toolchain announcement — 11 Go tools listed publicly</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="card track-it checked">
|
|
||||||
<div class="card-meta">
|
|
||||||
|
|
||||||
|
|
||||||
<span class="track-badge track-it">IT</span>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<p class="description">Acquired Saibeo Solutions, onboarded Ed Do as CTO</p>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- ================================================================= -->
|
|
||||||
<!-- Notes -->
|
|
||||||
<!-- ================================================================= -->
|
|
||||||
|
|
||||||
<div class="notes">
|
|
||||||
<h2>Notes</h2>
|
|
||||||
<ul>
|
|
||||||
|
|
||||||
<li>Keep `[IT]` (arcline.it, revenue-facing) and `[OS]` (arclineproject.org, GPL/open-source)</li>
|
|
||||||
|
|
||||||
<li>New codenames go in the legend table before the card is created, not after.</li>
|
|
||||||
|
|
||||||
<li>Source repos live on `git.arcline.it` — link commits/PRs in card text once work starts.</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- ================================================================= -->
|
|
||||||
<!-- Footer -->
|
|
||||||
<!-- ================================================================= -->
|
|
||||||
<footer>
|
|
||||||
Built with Python & Jinja2 ·
|
|
||||||
<a href="https://arcline.it">arcline.it</a> ·
|
|
||||||
<a href="https://arclineproject.org">arclineproject.org</a>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
1037
src/main.rs
Normal file
1037
src/main.rs
Normal file
File diff suppressed because it is too large
Load Diff
@@ -585,7 +585,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
{% set status_lower = item.status.lower().replace(' ', '') %}
|
{% set status_lower = item.status | lower | replace(' ', '') %}
|
||||||
<span class="status-pill status-{{ status_lower }}">{{ item.status }}</span>
|
<span class="status-pill status-{{ status_lower }}">{{ item.status }}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -611,7 +611,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<ul class="card-list" id="list-{{ col.name | replace(' ', '-') | lower }}">
|
<ul class="card-list" id="list-{{ col.name | replace(' ', '-') | lower }}">
|
||||||
{% for card in col.cards %}
|
{% for card in col.cards %}
|
||||||
<li class="card track-{{ ('both' if '/' in card.track else card.track.lower()) }}{% if card.checked %} checked{% endif %}"
|
<li class="card track-{{ ('both' if '/' in card.track else card.track | lower) }}{% if card.checked %} checked{% endif %}"
|
||||||
data-id="{{ card.id }}"
|
data-id="{{ card.id }}"
|
||||||
data-column="{{ col.name }}"
|
data-column="{{ col.name }}"
|
||||||
draggable="true"
|
draggable="true"
|
||||||
@@ -623,7 +623,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-meta">
|
<div class="card-meta">
|
||||||
{% if card.track %}
|
{% if card.track %}
|
||||||
{% set track_class = 'both' if '/' in card.track else card.track.lower() %}
|
{% set track_class = 'both' if '/' in card.track else card.track | lower %}
|
||||||
<span class="track-badge track-{{ track_class }}">{{ card.track }}</span>
|
<span class="track-badge track-{{ track_class }}">{{ card.track }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if card.codename %}
|
{% if card.codename %}
|
||||||
@@ -659,7 +659,7 @@
|
|||||||
<!-- Footer -->
|
<!-- Footer -->
|
||||||
<!-- ================================================================= -->
|
<!-- ================================================================= -->
|
||||||
<footer>
|
<footer>
|
||||||
Built with Python & Jinja2 ·
|
Built with Rust & Minijinja ·
|
||||||
<a href="https://arcline.it">arcline.it</a> ·
|
<a href="https://arcline.it">arcline.it</a> ·
|
||||||
<a href="https://arclineproject.org">arclineproject.org</a>
|
<a href="https://arclineproject.org">arclineproject.org</a>
|
||||||
</footer>
|
</footer>
|
||||||
@@ -978,7 +978,7 @@ async function handleDrop(e) {
|
|||||||
if (sourceColumn === targetColumn) return;
|
if (sourceColumn === targetColumn) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await api(`/api/cards/${cardId}/move`, {
|
await api(`/api/{{ board_slug }}/cards/${cardId}/move`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ column: targetColumn })
|
body: JSON.stringify({ column: targetColumn })
|
||||||
|
|||||||
Reference in New Issue
Block a user