wip frontend
This commit is contained in:
parent
db82f8564c
commit
756c31bad7
13 changed files with 193 additions and 49 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
34
templates/bases/app.html
Normal 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
11
templates/macros.html
Normal 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 %}
|
|
@ -1,4 +1,4 @@
|
||||||
{% extends "base.html" %}
|
{% extends "bases/base.html" %}
|
||||||
|
|
||||||
{% block title %}Login ⋅ {% endblock title %}
|
{% block title %}Login ⋅ {% endblock title %}
|
||||||
|
|
45
templates/pages/zone/records.html
Normal file
45
templates/pages/zone/records.html
Normal 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 %}
|
3
templates/pages/zones.html
Normal file
3
templates/pages/zones.html
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{% extends "bases/app.html" %}
|
||||||
|
|
||||||
|
{% block title %}Zones ⋅ {% endblock title %}
|
|
@ -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 %}
|
|
Loading…
Reference in a new issue