parent
8534407f14
commit
1d141740d5
@ -1,2 +1 @@ |
||||
# ADMIN_KEY=poop |
||||
# DATABASE_URL=postgres://localhost/mango |
||||
|
@ -1,67 +0,0 @@ |
||||
use crate::fairings::prelude::*; |
||||
|
||||
#[rocket::post("/login", data = "<key>")] |
||||
pub fn login(jar: &CookieJar<'_>, key: String) { |
||||
jar.add(Cookie::new("nothing", key)); |
||||
} |
||||
|
||||
#[derive(FromForm)] |
||||
pub struct HighlightProject { |
||||
id: String, |
||||
highlight: String, |
||||
} |
||||
|
||||
#[rocket::post("/highlights", data = "<data>")] |
||||
pub async fn highlight_project(_access: AdminAccess, db: Db, data: Form<Strict<HighlightProject>>) { |
||||
let highlight = if &data.highlight == "true" { |
||||
true |
||||
} else { |
||||
false |
||||
}; |
||||
|
||||
db.run(move |conn| { |
||||
let id = schema::highlights::id.eq(&data.id); |
||||
|
||||
if highlight { |
||||
diesel::insert_into(schema::highlights::table) |
||||
.values(id) |
||||
.execute(conn) |
||||
} else { |
||||
diesel::delete(schema::highlights::table.filter(id)) |
||||
.execute(conn) |
||||
} |
||||
}) |
||||
.await |
||||
.unwrap(); |
||||
} |
||||
|
||||
#[rocket::post("/projects", data = "<data>")] |
||||
pub async fn upsert_project(_access: AdminAccess, db: Db, data: Form<Strict<model::Project>>) { |
||||
db.run(move |conn| { |
||||
diesel::insert_into(schema::projects::table) |
||||
.values(&**data) |
||||
.on_conflict(schema::projects::id) |
||||
.do_update() |
||||
.set(&**data) |
||||
.execute(conn) |
||||
}) |
||||
.await |
||||
.unwrap(); |
||||
} |
||||
|
||||
#[rocket::post("/config/<name>", data = "<data>")] |
||||
pub async fn upsert_config(_access: AdminAccess, db: Db, name: String, data: Json<serde_json::Value>) { |
||||
db.run(move |conn| { |
||||
diesel::insert_into(schema::configs::table) |
||||
.values(model::PageConfig { |
||||
name: name.clone(), |
||||
data: data.0.clone(), |
||||
}) |
||||
.on_conflict(schema::configs::name) |
||||
.do_update() |
||||
.set(model::PageConfig { name, data: data.0 }) |
||||
.execute(conn) |
||||
}) |
||||
.await |
||||
.unwrap(); |
||||
} |
@ -1,51 +0,0 @@ |
||||
use crate::fairings::prelude::*; |
||||
|
||||
#[rocket::get("/")] |
||||
pub async fn index(_access: AdminAccess, db: Db) -> Template { |
||||
let projects = db |
||||
.run(|conn| schema::projects::table.load::<model::Project>(conn)) |
||||
.await |
||||
.expect("failed to load projects"); |
||||
let configs: Vec<_> = db |
||||
.run(|conn| schema::configs::table.load::<model::PageConfig>(conn)) |
||||
.await |
||||
.expect("failed to load configs") |
||||
.into_iter() |
||||
.map(|it| json!({ "name": it.name, "data": serde_json::to_string_pretty(&it.data).unwrap() })) |
||||
.collect(); |
||||
|
||||
Template::render( |
||||
"admin/index", |
||||
json!({ "projects": projects, "configs": configs }), |
||||
) |
||||
} |
||||
|
||||
#[rocket::get("/ip-log?<page>&<search>")] |
||||
pub async fn ip_log( |
||||
_access: AdminAccess, |
||||
db: Db, |
||||
page: Option<u32>, |
||||
search: Option<std::net::IpAddr>, |
||||
) -> Template { |
||||
let logs = db |
||||
.run(move |conn| { |
||||
let base = schema::ip_log::table |
||||
.order(schema::ip_log::timestamp.desc()) |
||||
.offset(page.unwrap_or(0) as i64 * 50) |
||||
.limit(50); |
||||
|
||||
if let Some(x) = search { |
||||
base.filter(schema::ip_log::addr.eq(ipnetwork::IpNetwork::from(x))) |
||||
.load::<model::IpLogEntry>(conn) |
||||
} else { |
||||
base.load::<model::IpLogEntry>(conn) |
||||
} |
||||
}) |
||||
.await |
||||
.expect("failed to load ip log"); |
||||
|
||||
Template::render( |
||||
"admin/ip-log", |
||||
json!({ "entries": logs, "nextPage": page.unwrap_or(0) + 1, "search": search }), |
||||
) |
||||
} |
@ -1,28 +0,0 @@ |
||||
use super::prelude::*; |
||||
|
||||
mod api; |
||||
mod frontend; |
||||
|
||||
#[rocket::catch(401)] |
||||
fn teapot() -> (Status, &'static str) { |
||||
(Status { code: 418 }, "🫖") |
||||
} |
||||
|
||||
pub fn fairing() -> impl Fairing { |
||||
AdHoc::on_ignite("Admin Frontend", |rocket| async { |
||||
rocket |
||||
.register("/admin", rocket::catchers![teapot]) |
||||
.mount( |
||||
"/admin", |
||||
rocket::routes![api::login, frontend::index, frontend::ip_log], |
||||
) |
||||
.mount( |
||||
"/admin/api", |
||||
rocket::routes![ |
||||
api::highlight_project, |
||||
api::upsert_project, |
||||
api::upsert_config |
||||
], |
||||
) |
||||
}) |
||||
} |
@ -1,25 +0,0 @@ |
||||
use super::prelude::*; |
||||
|
||||
pub struct AdminAccess; |
||||
|
||||
#[async_trait] |
||||
impl<'r> FromRequest<'r> for AdminAccess { |
||||
type Error = (); |
||||
|
||||
async fn from_request(request: &'r Request<'_>) -> Outcome<Self, Self::Error> { |
||||
let cookie = request |
||||
.cookies() |
||||
.get("nothing") |
||||
.map(|x| String::from(x.value())); |
||||
|
||||
if let Ok(key) = std::env::var("ADMIN_KEY") { |
||||
if cookie == Some(key) { |
||||
Outcome::Success(AdminAccess) |
||||
} else { |
||||
Outcome::Failure((Status::Unauthorized, ())) |
||||
} |
||||
} else { |
||||
Outcome::Failure((Status::Unauthorized, ())) |
||||
} |
||||
} |
||||
} |
@ -1,9 +1,7 @@ |
||||
mod prelude; |
||||
|
||||
mod admin; |
||||
mod db; |
||||
mod page_configs; |
||||
|
||||
pub use admin::*; |
||||
pub use db::*; |
||||
pub use page_configs::*; |
||||
|
@ -1,2 +0,0 @@ |
||||
User-agent: * |
||||
Disallow: /admin/ |
@ -1,75 +0,0 @@ |
||||
<html> |
||||
good morning |
||||
|
||||
<script> |
||||
window.onload = () => { |
||||
document.getElementById("highlight-project").onsubmit = async e => { |
||||
e.preventDefault(); |
||||
await fetch("admin/api/highlights", { method: "post", body: new FormData(e.target) }); |
||||
window.location = window.location; |
||||
}; |
||||
|
||||
document.getElementById("upsert-project").onsubmit = async e => { |
||||
e.preventDefault(); |
||||
await fetch("admin/api/projects", { method: "post", body: new FormData(e.target) }); |
||||
window.location = window.location; |
||||
}; |
||||
|
||||
document.getElementById("upsert-config").onsubmit = async e => { |
||||
e.preventDefault(); |
||||
const name = document.getElementById("upsert-config-name").value; |
||||
const data = document.getElementById("upsert-config-data").value; |
||||
await fetch(`admin/api/config/${name}`, { method: "post", body: data }); |
||||
window.location = window.location; |
||||
}; |
||||
}; |
||||
</script> |
||||
|
||||
<body> |
||||
<a href="/admin/ip-log?page=0">ip log</a> |
||||
|
||||
<h1>page configs</h1> |
||||
<h2>upsert</h2> |
||||
<form id="upsert-config" autocomplete="off"> |
||||
<label>name</label> |
||||
<input id="upsert-config-name" name="name"> |
||||
<label>data</label> |
||||
<input id="upsert-config-data" name="data"> |
||||
<input type="submit" value="send"> |
||||
</form> |
||||
|
||||
{{#each configs}} |
||||
<h2>{{name}}</h2> |
||||
<code>{{data}}</code> |
||||
{{/each}} |
||||
|
||||
<h1>projects</h1> |
||||
<h2>highlight</h2> |
||||
<form id="highlight-project" autocomplete="off"> |
||||
<label>id</label> |
||||
<input name="id"> |
||||
<label>highlight ("true" | _)</label> |
||||
<input name="highlight"> |
||||
<input type="submit" value="send"> |
||||
</form> |
||||
|
||||
<h2>upsert</h2> |
||||
<form id="upsert-project" autocomplete="off"> |
||||
<label>id</label> |
||||
<input name="id"> |
||||
<label>display name</label> |
||||
<input name="name"> |
||||
<label>desc</label> |
||||
<input name="description"> |
||||
<label>image</label> |
||||
<input name="image"> |
||||
<input type="submit" value="send"> |
||||
</form> |
||||
{{#each projects}} |
||||
<article> |
||||
<h2>{{name}} (id {{id}})</h2> |
||||
<span>{{description}}</span> |
||||
</article> |
||||
{{/each}} |
||||
</body> |
||||
</html> |
@ -1,24 +0,0 @@ |
||||
<html> |
||||
<a href="?page={{nextPage}}&search={{search}}">next page</a> |
||||
<div> |
||||
<form> |
||||
<label>ip</label> |
||||
<input name="search"> |
||||
<input type="submit" value="search"/> |
||||
</form> |
||||
</div> |
||||
<table> |
||||
<tr> |
||||
<th>addr</th> |
||||
<th>timestamp</th> |
||||
<th>path</th> |
||||
</tr> |
||||
{{#each entries}} |
||||
<tr> |
||||
<td>{{addr}}</td> |
||||
<td>{{timestamp}}</td> |
||||
<td>{{path}}</td> |
||||
</tr> |
||||
{{/each}} |
||||
</table> |
||||
</html> |
Loading…
Reference in new issue