wip frontend

This commit is contained in:
Hannaeko 2022-04-29 18:04:12 +02:00
parent db82f8564c
commit 756c31bad7
13 changed files with 193 additions and 49 deletions

View file

@ -9,12 +9,12 @@ function apiGet(url) {
throw new Error('Not ok'); throw new Error('Not ok');
} }
return res.json(); return res.json();
}) });
} }
function getRecords(zone) { function getRecords(zone) {
return apiGet(`zones/${zone}/records`) return apiGet(`zones/${zone}/records`);
} }
export { export {

View file

@ -11,7 +11,7 @@ const rdataInputProperties = {
MasterServerName: {label: 'serveur primaire', type: 'text'}, MasterServerName: {label: 'serveur primaire', type: 'text'},
Expire: {label: 'expiration', type: 'number'}, Expire: {label: 'expiration', type: 'number'},
Target: {label: 'cible', type: 'text'}, Target: {label: 'cible', type: 'text'},
} };
const Editable = createContext(false); const Editable = createContext(false);
@ -33,10 +33,10 @@ function RDataInput({ name, value = '', index = 0 }) {
} else { } else {
return html` return html`
<div> <div>
<span class=label>${label}:</span> <dt>${label}:</dt>
<span class=value>${value}</span> <dd>${value}</dd>
</div> </div>
` `;
} }
} }
} }
@ -56,7 +56,20 @@ function Record({name, ttl, type, rdata, index = 0}) {
<td class=domain>${name}</div> <td class=domain>${name}</div>
<td class=type>${type}</div> <td class=type>${type}</div>
<td class=ttl>${ttl}</div> <td class=ttl>${ttl}</div>
<td class=rdata><${RData} rdata=${rdata} index=${index}/></div> <td class=rdata>
<${Editable.Consumer}>
${
(editable) => {
if (editable) {
return html`<${RData} rdata=${rdata} index=${index}/>`
} else {
return html`<dl><${RData} rdata=${rdata} index=${index}/></dl>`
}
}
}
<//>
</div>
</tr> </tr>
`; `;
} }
@ -70,13 +83,13 @@ function RecordList({ zone }) {
useEffect(() => { useEffect(() => {
getRecords(zone) getRecords(zone)
.then((res) => setRecords(res)); .then((res) => setRecords(res));
}, []) }, []);
return html` return html`
<${Editable.Provider} value=${editable}> <${Editable.Provider} value=${editable}>
<button onclick=${toggleEdit}>${ editable ? 'Save' : 'Edit'}</button> <button onclick=${toggleEdit}>${ editable ? 'Save' : 'Edit'}</button>
<table class=record-list> <table>
<thead> <thead>
<tr> <tr>
<th>Nom</th> <th>Nom</th>

View file

@ -1,33 +1,43 @@
body { body {
color: #2e2033; color: #2e2033;
} display: flex;
.record-list { height: 100%;
border-collapse: collapse; width: 100%;
margin: 0;
} }
.record-list .rdata { main {
flex-grow: 1;
}
zone-content table {
border-collapse: collapse;
width: 100%;
}
zone-content .rdata {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
} }
.record-list th, .record-list td { zone-content th, .zone-content td {
font-weight: normal; font-weight: normal;
text-align: left; text-align: left;
vertical-align: top; vertical-align: top;
padding: 0.25rem; padding: 0.25rem;
} }
.record-list thead { zone-content thead {
background: #ccb9ff; background: #ccb9ff;
color: #39004d; color: #39004d;
} }
.record-list tbody tr:nth-child(even) td { zone-content tbody tr:nth-child(even) td {
background: #ccb9ff3d; background: #ccb9ff3d;
} }
.record-list tbody tr .rdata span.label, zone-content tbody tr .rdata dt,
.record-list tbody tr .rdata label { zone-content tbody tr .rdata label {
display: inline-block; display: inline-block;
padding: 0.1em 0.5em; padding: 0.1em 0.5em;
background: #cecece; background: #cecece;
@ -35,7 +45,18 @@ body {
border-radius: 0.5em; border-radius: 0.5em;
margin-right: 0.1rem; margin-right: 0.1rem;
} }
zone-content tbody tr .rdata dd {
.record-list tbody tr .rdata > div { margin: 0;
margin: 0.1rem 0.5rem 0.1rem 0;
} }
zone-content tbody tr .rdata dl {
display: flex;
flex-wrap: wrap;
margin: 0;
}
zone-content tbody tr .rdata div {
margin: 0.1rem 0.5rem 0.1rem 0;
display: flex;
align-items: baseline;
}

View file

@ -102,6 +102,7 @@ impl NomiloCommand for RunServerCommand {
.mount("/", routes![ .mount("/", routes![
ui::get_login_page, ui::get_login_page,
ui::post_login_page, ui::post_login_page,
ui::get_zones_page,
ui::get_zone_records_page, ui::get_zone_records_page,
]) ])
.mount("/", static_files) .mount("/", static_files)

View file

@ -18,7 +18,7 @@ pub struct LoginPage {
#[get("/login")] #[get("/login")]
pub async fn get_login_page() -> Template<'static, LoginPage> { pub async fn get_login_page() -> Template<'static, LoginPage> {
Template::new("login.html", LoginPage { error: None }) Template::new("pages/login.html", LoginPage { error: None })
} }
#[post("/login", data = "<auth_request>")] #[post("/login", data = "<auth_request>")]
@ -38,7 +38,7 @@ pub async fn post_login_page(
match res { match res {
Ok(_) => Ok(Redirect::to(uri!("/zones"))), Ok(_) => Ok(Redirect::to(uri!("/zones"))),
Err(models::UserError::BadCreds) => Err(Either::Left(Template::new( Err(models::UserError::BadCreds) => Err(Either::Left(Template::new(
"login.html", "pages/login.html",
LoginPage { LoginPage {
error: Some("Identifants incorrects".to_string()) error: Some("Identifants incorrects".to_string())
} }

View file

@ -1,5 +1,7 @@
use serde_json::{Value, json};
use serde::Serialize; use serde::Serialize;
use rocket::http::{Status}; use rocket::http::{Status};
use rocket::http::uri::Origin;
use crate::template::Template; use crate::template::Template;
use crate::models; use crate::models;
@ -11,20 +13,52 @@ pub struct RecordsPage {
zone: String zone: String
} }
// TODO: Check if origin changes if application mounted on different path
#[get("/zone/<zone>/records")] #[get("/zone/<zone>/records")]
pub async fn get_zone_records_page(user_info: models::UserInfo, zone: models::AbsoluteName, conn: DbConn) -> Result<Template<'static, RecordsPage>, Status> { pub async fn get_zone_records_page(user_info: models::UserInfo, zone: models::AbsoluteName, conn: DbConn, origin: &Origin<'_>) -> Result<Template<'static, Value>, Status> {
let zone_name = zone.to_utf8(); let zone_name = zone.to_utf8();
conn.run(move |c| { let zones = conn.run(move |c| {
if user_info.is_admin() { if user_info.is_admin() {
models::Zone::get_by_name(c, &zone_name) models::Zone::get_by_name(c, &zone_name)?;
models::Zone::get_all(c)
} else { } else {
user_info.get_zone(c, &zone_name) user_info.get_zone(c, &zone_name)?;
user_info.get_zones(c)
} }
}).await.map_err(|e| models::ErrorResponse::from(e).status)?; }).await.map_err(|e| models::ErrorResponse::from(e).status)?;
Ok(Template::new("zone/records.html", RecordsPage { Ok(Template::new(
zone: zone.to_utf8(), "pages/zone/records.html",
})) json!({
"current_zone": zone.to_utf8(),
"zones": zones,
"nav_page": origin.clone().into_normalized().path().as_str(),
"nav_sections": vec!["zones", zone.to_utf8().as_str()],
})
))
}
#[get("/zones")]
pub async fn get_zones_page(user_info: models::UserInfo, conn: DbConn, origin: &Origin<'_>) -> Result<Template<'static, Value>, Status> {
let zones = conn.run(move |c| {
if user_info.is_admin() {
models::Zone::get_all(c)
} else {
user_info.get_zones(c)
}
}).await.map_err(|e| models::ErrorResponse::from(e).status)?;
Ok(Template::new(
"pages/zones.html",
json!({
"zones": zones,
"nav_page": origin.clone().into_normalized().path().as_str(),
"nav_sections": vec!["zones"],
})
))
} }

34
templates/bases/app.html Normal file
View file

@ -0,0 +1,34 @@
{% extends "bases/base.html" %}
{% import "macros.html" as macros %}
{% block content %}
<nav aria-label="Principal">
<ul>
<li><a href="/profil">Mon profile</a></li>
<li>
{{ macros::nav_link(
content="Mes zones",
href="/zones",
current_page=nav_page,
section="zones",
current_sections=nav_sections,
) }}
<ul>
{% for zone in zones %}
<li>
{{ macros::nav_link(
content=zone.name,
href="/zone/" ~ zone.name,
current_page=nav_page,
section=zone.name,
current_sections=nav_sections,
) }}
{% endfor %}
</ul>
</li>
</ul>
</nav>
<main>
{% block main %}{% endblock main %}
</main>
{% endblock content %}

11
templates/macros.html Normal file
View file

@ -0,0 +1,11 @@
{% macro nav_link(content, href, current_page, section=False, current_sections=False, props='') %}
<a
href="{{ href }}"
{{ props }}
{% if current_page == href %}
aria-current="page"
{% elif section and section in current_sections %}
aria-current="location"
{% endif %}
>{{ content }}</a>
{% endmacro nav_link %}

View file

@ -1,4 +1,4 @@
{% extends "base.html" %} {% extends "bases/base.html" %}
{% block title %}Login ⋅ {% endblock title %} {% block title %}Login ⋅ {% endblock title %}

View file

@ -0,0 +1,45 @@
{% extends "bases/app.html" %}
{% import "macros.html" as macros %}
{% block title %}{{ current_zone }} ⋅ Records ⋅ {% endblock title %}
{% block main %}
<h1>Gestion la zone {{ current_zone }}</h1>
<nav aria-label="Secondaire">
<ul>
<li>
{{ macros::nav_link(
content="Enregistrements",
href="/zone/" ~ current_zone ~ "/records",
current_page=nav_page,
) }}
</li>
<li>
{{ macros::nav_link(
content="Membres",
href="/zone/" ~ current_zone ~ "/members",
current_page=nav_page,
) }}
</li>
</ul>
</nav>
<section>
<h2>Enregistrements</h2>
<zone-content>
</zone-content>
</section>
<aside>
<h2>Aide</h2>
</aside>
{% endblock main %}
{% block scripts %}
<script type="module">
const zoneName = '{{ current_zone }}';
import { RecordList } from '/scripts/records.js';
import { html, render } from 'https://unpkg.com/htm/preact/standalone.mjs';
render(html`<${RecordList} zone=${zoneName} />`, document.querySelector('zone-content'));
</script>
{% endblock scripts %}

View file

@ -0,0 +1,3 @@
{% extends "bases/app.html" %}
{% block title %}Zones ⋅ {% endblock title %}

View file

@ -1,18 +0,0 @@
{% extends "base.html" %}
{% block title %}{{ zone }} ⋅ Records ⋅ {% endblock title %}
{% block content %}
<main></main>
{% endblock content %}
{% block scripts %}
<script type="module">
const zoneName = "{{ zone }}";
import { RecordList } from '/scripts/records.js';
import { html, render } from 'https://unpkg.com/htm/preact/standalone.mjs';
render(html`<${RecordList} zone=${zoneName} />`, document.querySelector('main'));
</script>
{% endblock scripts %}