-
-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
30 changed files
with
426 additions
and
149 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
[general] | ||
|
||
[database] | ||
name = "rwf_bg_jobs" | ||
name = "rwf_admin" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,13 @@ | ||
[package] | ||
name = "admin" | ||
name = "rwf-admin" | ||
version = "0.1.0" | ||
edition = "2021" | ||
|
||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
|
||
[dependencies] | ||
rwf = { path = "../../rwf" } | ||
rwf = { path = "../rwf" } | ||
tokio = { version = "1", features = ["full"] } | ||
serde = { version = "1", features = ["derive"] } | ||
serde_json = "1" | ||
time = { version = "0.3", features = ["serde"] } |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
use rwf::job::JobModel; | ||
use rwf::prelude::*; | ||
use rwf::serde::Serialize; | ||
|
||
#[derive(Default)] | ||
pub struct Index; | ||
|
||
#[async_trait] | ||
impl Controller for Index { | ||
async fn handle(&self, _request: &Request) -> Result<Response, Error> { | ||
Ok(Response::new().redirect("jobs")) | ||
} | ||
} | ||
|
||
#[derive(Default)] | ||
pub struct Jobs; | ||
|
||
#[derive(macros::Context)] | ||
struct JobsContext { | ||
queued: i64, | ||
running: i64, | ||
errors: i64, | ||
latency: i64, | ||
jobs: Vec<JobModel>, | ||
} | ||
|
||
impl JobsContext { | ||
pub async fn load() -> Result<Self, Error> { | ||
let mut conn = Pool::connection().await?; | ||
let queued = JobModel::queued().count(&mut conn).await?; | ||
let errors = JobModel::errors().count(&mut conn).await?; | ||
let running = JobModel::running().count(&mut conn).await?; | ||
|
||
let jobs = JobModel::all() | ||
.order(("id", "DESC")) | ||
.limit(25) | ||
.fetch_all(&mut conn) | ||
.await?; | ||
|
||
let latency = JobModel::queued() | ||
.order("created_at") | ||
.take_one() | ||
.fetch_optional(&mut conn) | ||
.await?; | ||
|
||
let latency = if let Some(latency) = latency { | ||
(OffsetDateTime::now_utc() - latency.created_at).whole_seconds() | ||
} else { | ||
Duration::seconds(0).whole_seconds() | ||
}; | ||
|
||
Ok(Self { | ||
queued, | ||
errors, | ||
running, | ||
jobs, | ||
latency, | ||
}) | ||
} | ||
} | ||
|
||
#[async_trait] | ||
impl Controller for Jobs { | ||
async fn handle(&self, _request: &Request) -> Result<Response, Error> { | ||
let template = Template::load("templates/rwf_admin/jobs.html")?; | ||
Ok(Response::new().html(template.render(JobsContext::load().await?)?)) | ||
} | ||
} | ||
|
||
#[derive(Clone, macros::Model, Serialize)] | ||
struct RequestByCode { | ||
count: i64, | ||
code: String, | ||
#[serde(with = "time::serde::rfc2822")] | ||
created_at: OffsetDateTime, | ||
} | ||
|
||
impl RequestByCode { | ||
fn count(minutes: i64) -> Scope<Self> { | ||
Self::find_by_sql( | ||
"WITH timestamps AS ( | ||
SELECT date_trunc('minute', now() - (n || ' minute')::interval) AS created_at FROM generate_series(0, $1::bigint) n | ||
) | ||
SELECT | ||
'ok' AS code, | ||
COALESCE(e2.count, 0) AS count, | ||
timestamps.created_at AS created_at | ||
FROM timestamps | ||
LEFT JOIN LATERAL ( | ||
SELECT | ||
COUNT(*) AS count, | ||
DATE_TRUNC('minute', created_at) AS created_at | ||
FROM rwf_requests | ||
WHERE | ||
created_at BETWEEN timestamps.created_at AND timestamps.created_at + INTERVAL '1 minute' | ||
AND code BETWEEN 100 AND 299 | ||
GROUP BY 2 | ||
) e2 ON true | ||
UNION ALL | ||
SELECT | ||
'warn' AS code, | ||
COALESCE(e2.count, 0) AS count, | ||
timestamps.created_at AS created_at | ||
FROM timestamps | ||
LEFT JOIN LATERAL ( | ||
SELECT | ||
COUNT(*) AS count, | ||
DATE_TRUNC('minute', created_at) AS created_at | ||
FROM rwf_requests | ||
WHERE | ||
created_at BETWEEN timestamps.created_at AND timestamps.created_at + INTERVAL '1 minute' | ||
AND code BETWEEN 300 AND 499 | ||
GROUP BY 2 | ||
) e2 ON true | ||
UNION ALL | ||
SELECT | ||
'error' AS code, | ||
COALESCE(e2.count, 0) AS coount, | ||
timestamps.created_at AS created_at | ||
FROM timestamps | ||
LEFT JOIN LATERAL ( | ||
SELECT | ||
COUNT(*) AS count, | ||
DATE_TRUNC('minute', created_at) AS created_at | ||
FROM rwf_requests | ||
WHERE | ||
created_at BETWEEN timestamps.created_at AND timestamps.created_at + INTERVAL '1 minute' | ||
AND code BETWEEN 500 AND 599 | ||
GROUP BY 2 | ||
) e2 ON true | ||
ORDER BY 3;", | ||
&[minutes.to_value()], | ||
) | ||
} | ||
} | ||
|
||
#[derive(Default)] | ||
pub struct Requests; | ||
|
||
#[async_trait] | ||
impl Controller for Requests { | ||
async fn handle(&self, _request: &Request) -> Result<Response, Error> { | ||
let requests = { | ||
let mut conn = Pool::connection().await?; | ||
RequestByCode::count(60).fetch_all(&mut conn).await? | ||
}; | ||
let requests = serde_json::to_string(&requests)?; | ||
|
||
render!("templates/rwf_admin/requests.html", "requests" => requests) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
use rwf::controller::Engine; | ||
use rwf::prelude::*; | ||
|
||
mod controllers; | ||
use controllers::*; | ||
|
||
pub fn engine() -> Engine { | ||
Engine::new(vec![ | ||
route!("/" => Index), | ||
route!("/jobs" => Jobs), | ||
route!("/requests" => Requests), | ||
]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import { Controller } from "hotwired/stimulus"; | ||
|
||
export default class extends Controller { | ||
static targets = ["requestsOk", "chart"]; | ||
|
||
connect() { | ||
const data = JSON.parse(this.requestsOkTarget.innerHTML); | ||
const labels = Array.from( | ||
new Set( | ||
data.map((item) => new Date(item.created_at).toLocaleTimeString()), | ||
), | ||
); | ||
const ok = data | ||
.filter((item) => item.code === "ok") | ||
.map((item) => item.count); | ||
const warn = data | ||
.filter((item) => item.code === "warn") | ||
.map((item) => item.count); | ||
const error = data | ||
.filter((item) => item.code === "error") | ||
.map((item) => item.count); | ||
|
||
const options = { | ||
scales: { | ||
x: { | ||
ticks: { | ||
callback: (t, i) => (i % 10 === 0 ? labels[i] : null), | ||
}, | ||
stacked: true, | ||
}, | ||
y: { | ||
stacked: true, | ||
}, | ||
}, | ||
}; | ||
|
||
const chartData = { | ||
labels, | ||
datasets: [ | ||
{ | ||
label: "100-299", | ||
data: ok, | ||
}, | ||
{ | ||
label: "500-599", | ||
data: error, | ||
// backgroundColor: "red", | ||
}, | ||
{ | ||
label: "300-499", | ||
data: warn, | ||
}, | ||
], | ||
}; | ||
|
||
new Chart(this.chartTarget, { | ||
type: "bar", | ||
data: chartData, | ||
options, | ||
}); | ||
} | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
Oops, something went wrong.