init: skeleton for dashboard configuration state

This commit is contained in:
Jeremy Wall 2024-02-03 16:31:41 -06:00
parent e607ce53ea
commit d4b38830e6
5 changed files with 99 additions and 9 deletions

View File

@ -7,13 +7,15 @@ license = "Apache-2.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
anyhow = "1.0.79"
async-io = "2.3.1" async-io = "2.3.1"
axum = "0.7.4" axum = { version = "0.7.4", features = [ "ws" ] }
clap = { version = "4.4.18", features = ["derive"] } clap = { version = "4.4.18", features = ["derive"] }
maud = { version = "0.26.0", features = ["axum"] } maud = { version = "0.26.0", features = ["axum"] }
prometheus-http-api = "0.2.0" prometheus-http-api = "0.2.0"
serde = { version = "1.0.196", features = ["derive"] } serde = { version = "1.0.196", features = ["derive"] }
serde_json = "1.0.113" serde_json = "1.0.113"
serde_yaml = "0.9.31"
smol = "2.0.0" smol = "2.0.0"
smol-axum = "0.1.0" smol-axum = "0.1.0"
smol-macros = "0.1.0" smol-macros = "0.1.0"

34
src/dashboard.rs Normal file
View File

@ -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<Graph>,
}
#[derive(Deserialize)]
pub struct Graph {
title: String,
query: String,
}
pub fn read_dashboard_list(path: &Path) -> anyhow::Result<Vec<Dashboard>> {
let f = std::fs::File::open(path)?;
Ok(serde_yaml::from_reader(f)?)
}

View File

@ -11,15 +11,18 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::io;
use std::net::TcpListener; use std::net::TcpListener;
use std::path::PathBuf;
use std::sync::Arc; use std::sync::Arc;
use anyhow;
use async_io::Async; use async_io::Async;
use axum::{self, routing::*, Router}; use axum::{self, routing::*, Router};
use clap::{self, Parser}; use clap::{self, Parser};
use smol_macros::main; use smol_macros::main;
mod dashboard;
mod query;
mod routes; mod routes;
#[derive(clap::Parser)] #[derive(clap::Parser)]
@ -27,20 +30,25 @@ mod routes;
struct Cli { struct Cli {
#[arg(long)] #[arg(long)]
listen: Option<std::net::SocketAddr>, listen: Option<std::net::SocketAddr>,
#[arg(long)]
config: PathBuf,
} }
main! { main! {
async fn main(ex: &Arc<smol_macros::Executor<'_>>) -> io::Result<()> { async fn main(ex: &Arc<smol_macros::Executor<'_>>) -> anyhow::Result<()> {
let args = Cli::parse(); let args = Cli::parse();
let config = std::sync::Arc::new(dashboard::read_dashboard_list(args.config.as_path())?);
let router = Router::new() let router = Router::new()
.with_state(config)
// JSON api endpoints // JSON api endpoints
.nest("/api", routes::mk_api_routes()) .nest("/api", routes::mk_api_routes())
// HTMX ui component endpoints // HTMX ui component endpoints
.nest("/ui", routes::mk_ui_routes()) .nest("/ui", routes::mk_ui_routes())
.route("/", get(routes::index)); .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 // TODO(jwall): Take this from clap arguments
let listener = Async::<TcpListener>::bind(socket_addr).unwrap(); let listener = Async::<TcpListener>::bind(socket_addr)?;
smol_axum::serve(ex.clone(), listener, router).await smol_axum::serve(ex.clone(), listener, router).await?;
Ok(())
} }
} }

28
src/query.rs Normal file
View File

@ -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<S: Into<DataSource>, Q: Into<Query>>(src: S, qry: Q) -> Self {
Self {
source: src.into(),
query: qry.into(),
}
}
}

View File

@ -11,10 +11,17 @@
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
use std::sync::Arc;
use maud::{html, Markup}; use maud::{html, Markup};
use axum::Router; use axum::{extract::State, Router};
use crate::dashboard::Dashboard;
type Config = State<Arc<Vec<Dashboard>>>;
pub fn mk_api_routes() -> Router { pub fn mk_api_routes() -> Router {
// Query routes
Router::new() Router::new()
} }
@ -22,15 +29,26 @@ pub fn mk_ui_routes() -> Router {
Router::new() Router::new()
} }
pub async fn index() -> Markup { pub async fn index(State(config): Config) -> Markup {
html! { html! {
html { html {
head { head {
title { ("Heracles - Prometheus Unshackled") } title { ("Heracles - Prometheus Unshackled") }
} }
body { body {
("hello world") (app(State(config.clone())).await)
} }
} }
} }
} }
pub async fn app(State(config): Config) -> Markup {
html! {
div {
// Header menu
div { }
// dashboard display
div { }
}
}
}