diff --git a/Cargo.toml b/Cargo.toml index bb86556..8e8d4e8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,13 +7,15 @@ license = "Apache-2.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +anyhow = "1.0.79" async-io = "2.3.1" -axum = "0.7.4" +axum = { version = "0.7.4", features = [ "ws" ] } clap = { version = "4.4.18", features = ["derive"] } maud = { version = "0.26.0", features = ["axum"] } prometheus-http-api = "0.2.0" serde = { version = "1.0.196", features = ["derive"] } serde_json = "1.0.113" +serde_yaml = "0.9.31" smol = "2.0.0" smol-axum = "0.1.0" smol-macros = "0.1.0" diff --git a/src/dashboard.rs b/src/dashboard.rs new file mode 100644 index 0000000..a4b29a5 --- /dev/null +++ b/src/dashboard.rs @@ -0,0 +1,34 @@ +// Copyright 2021 Jeremy Wall +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use std::path::Path; + +use serde::Deserialize; +use serde_yaml; + +#[derive(Deserialize)] +pub struct Dashboard { + title: String, + graphs: Vec, +} + +#[derive(Deserialize)] +pub struct Graph { + title: String, + query: String, +} + +pub fn read_dashboard_list(path: &Path) -> anyhow::Result> { + let f = std::fs::File::open(path)?; + Ok(serde_yaml::from_reader(f)?) +} diff --git a/src/main.rs b/src/main.rs index 378e610..e540093 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,15 +11,18 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use std::io; use std::net::TcpListener; +use std::path::PathBuf; use std::sync::Arc; +use anyhow; use async_io::Async; use axum::{self, routing::*, Router}; use clap::{self, Parser}; use smol_macros::main; +mod dashboard; +mod query; mod routes; #[derive(clap::Parser)] @@ -27,20 +30,25 @@ mod routes; struct Cli { #[arg(long)] listen: Option, + #[arg(long)] + config: PathBuf, } main! { - async fn main(ex: &Arc>) -> io::Result<()> { + async fn main(ex: &Arc>) -> anyhow::Result<()> { let args = Cli::parse(); + let config = std::sync::Arc::new(dashboard::read_dashboard_list(args.config.as_path())?); let router = Router::new() + .with_state(config) // JSON api endpoints .nest("/api", routes::mk_api_routes()) // HTMX ui component endpoints .nest("/ui", routes::mk_ui_routes()) .route("/", get(routes::index)); - let socket_addr = args.listen.unwrap_or("127.0.0.1:3000".parse().unwrap()); + let socket_addr = args.listen.unwrap_or("127.0.0.1:3000".parse()?); // TODO(jwall): Take this from clap arguments - let listener = Async::::bind(socket_addr).unwrap(); - smol_axum::serve(ex.clone(), listener, router).await + let listener = Async::::bind(socket_addr)?; + smol_axum::serve(ex.clone(), listener, router).await?; + Ok(()) } } diff --git a/src/query.rs b/src/query.rs new file mode 100644 index 0000000..f6a8889 --- /dev/null +++ b/src/query.rs @@ -0,0 +1,28 @@ +// Copyright 2021 Jeremy Wall +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +use prometheus_http_api::{DataSource, Query}; + +pub struct QueryConn { + source: DataSource, + query: Query, +} + +impl QueryConn { + pub fn new, Q: Into>(src: S, qry: Q) -> Self { + Self { + source: src.into(), + query: qry.into(), + } + } +} diff --git a/src/routes.rs b/src/routes.rs index 91b9285..81f17e3 100644 --- a/src/routes.rs +++ b/src/routes.rs @@ -11,10 +11,17 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +use std::sync::Arc; + use maud::{html, Markup}; -use axum::Router; +use axum::{extract::State, Router}; + +use crate::dashboard::Dashboard; + +type Config = State>>; pub fn mk_api_routes() -> Router { + // Query routes Router::new() } @@ -22,15 +29,26 @@ pub fn mk_ui_routes() -> Router { Router::new() } -pub async fn index() -> Markup { +pub async fn index(State(config): Config) -> Markup { html! { html { head { title { ("Heracles - Prometheus Unshackled") } } body { - ("hello world") + (app(State(config.clone())).await) } } } } + +pub async fn app(State(config): Config) -> Markup { + html! { + div { + // Header menu + div { } + // dashboard display + div { } + } + } +}