add create zone page
gg# modified: src/models/name.rs
This commit is contained in:
parent
1cd58f6ff7
commit
3c5de4cab6
14 changed files with 265 additions and 78 deletions
|
@ -1,10 +1,5 @@
|
|||
main {
|
||||
margin-top: 20vh;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: 25rem;
|
||||
margin: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
|
|
@ -7,6 +7,8 @@ body {
|
|||
|
||||
main {
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
input {
|
||||
|
@ -14,14 +16,31 @@ input {
|
|||
font-size: 1rem;
|
||||
}
|
||||
|
||||
:root {
|
||||
--color-primary: #712da0;
|
||||
--color-hightlight-1: #ffbac6;
|
||||
--color-hightlight-2: #f560f5;
|
||||
--color-contrast: white;
|
||||
}
|
||||
|
||||
p.feedback {
|
||||
padding: .35rem;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
p.feedback.error {
|
||||
background: #fddede;
|
||||
color: #710000;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
margin-top: 2rem;
|
||||
background: #712da0;
|
||||
color: white;
|
||||
border-left: 5px solid #ffbac6;
|
||||
border-top: 5px solid #ffbac6;
|
||||
border-right: 5px solid #f560f5;
|
||||
border-bottom: 5px solid #f560f5;
|
||||
background: var(--color-primary);
|
||||
color: var(--color-contrast);
|
||||
border-left: 5px solid var(--color-hightlight-1);
|
||||
border-top: 5px solid var(--color-hightlight-1);
|
||||
border-right: 5px solid var(--color-hightlight-2);
|
||||
border-bottom: 5px solid var(--color-hightlight-2);
|
||||
}
|
||||
|
||||
input[type="submit"]:hover {
|
||||
|
@ -36,6 +55,38 @@ form label {
|
|||
margin-top: .75em;
|
||||
}
|
||||
|
||||
form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
nav {
|
||||
background: var(--color-primary);
|
||||
max-width: 10vw;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
padding: 1rem;
|
||||
border-right: 5px solid var(--color-hightlight-1);
|
||||
}
|
||||
|
||||
nav a {
|
||||
color: var(--color-contrast);
|
||||
}
|
||||
|
||||
nav ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
nav ul li {
|
||||
margin-top: .35rem;
|
||||
}
|
||||
|
||||
nav ul ul {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
zone-content table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
|
|
|
@ -104,6 +104,8 @@ impl NomiloCommand for RunServerCommand {
|
|||
ui::post_login_page,
|
||||
ui::get_zones_page,
|
||||
ui::get_zone_records_page,
|
||||
ui::get_create_zone_page,
|
||||
ui::post_create_zone_page,
|
||||
])
|
||||
.mount("/", static_files)
|
||||
.launch().await;
|
||||
|
|
|
@ -2,6 +2,7 @@ use rocket::http::{Cookie, SameSite, CookieJar};
|
|||
use rocket::State;
|
||||
|
||||
use crate::config::Config;
|
||||
use crate::dns::ZoneConnector;
|
||||
use crate::DbConn;
|
||||
use crate::models;
|
||||
|
||||
|
@ -39,3 +40,35 @@ pub async fn do_login(
|
|||
|
||||
Ok(session)
|
||||
}
|
||||
|
||||
|
||||
pub async fn create_zone(
|
||||
conn: &DbConn,
|
||||
mut dns_api: Box<dyn ZoneConnector>,
|
||||
user_info: models::UserInfo,
|
||||
zone_request: models::CreateZoneRequest,
|
||||
) -> Result<models::Zone, models::ErrorResponse> {
|
||||
user_info.check_admin()?;
|
||||
|
||||
dns_api.zone_exists(zone_request.name.clone(), models::DNSClass::IN.into()).await?;
|
||||
|
||||
let zone = conn.run(move |c| {
|
||||
models::Zone::create_zone(c, zone_request)
|
||||
}).await?;
|
||||
|
||||
Ok(zone)
|
||||
}
|
||||
|
||||
pub async fn get_zones(
|
||||
conn: &DbConn,
|
||||
user_info: models::UserInfo,
|
||||
) -> Result<Vec<models::Zone>, models::ErrorResponse> {
|
||||
let zones = conn.run(move |c| {
|
||||
if user_info.is_admin() {
|
||||
models::Zone::get_all(c)
|
||||
} else {
|
||||
user_info.get_zones(c)
|
||||
}
|
||||
}).await?;
|
||||
Ok(zones)
|
||||
}
|
||||
|
|
|
@ -31,8 +31,10 @@ impl DerefMut for DnsClient {
|
|||
|
||||
impl DnsClient {
|
||||
pub async fn from_config(dns_config: &DnsClientConfig) -> Result<Self, ProtoError> {
|
||||
info!("Creating DNS client for {}", dns_config.server);
|
||||
let (stream, handle) = TcpClientStream::<AsyncIoTokioAsStd<TokioTcpStream>>::new(dns_config.server);
|
||||
let signer = if let Some(tsig_config) = dns_config.tsig.as_ref() {
|
||||
info!("Client configured with TSIG authentication");
|
||||
Some(Arc::new(TSigner::new(
|
||||
tsig_config.key.clone(),
|
||||
tsig_config.algorithm.clone(),
|
||||
|
@ -40,6 +42,7 @@ impl DnsClient {
|
|||
60,
|
||||
)?.into()))
|
||||
} else {
|
||||
info!("Client configured without authentication");
|
||||
None
|
||||
};
|
||||
|
||||
|
@ -77,4 +80,3 @@ where
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -250,6 +250,7 @@ impl ZoneConnector for DnsConnectorClient {
|
|||
async fn zone_exists(&mut self, zone: Name, class: DNSClass) -> ConnectorResult<()>
|
||||
{
|
||||
let response = {
|
||||
info!("Querying SOA for name {}", zone);
|
||||
let query = self.client.query(zone.clone(), class, RecordType::SOA);
|
||||
match query.await.map_err(|e| Box::new(DnsConnectorError::ClientError(e))) {
|
||||
Err(e) => return Err(e),
|
||||
|
|
|
@ -2,6 +2,7 @@ use std::ops::Deref;
|
|||
|
||||
|
||||
use rocket::request::FromParam;
|
||||
use rocket::form::{self, FromFormField, ValueField};
|
||||
use serde::{Deserialize, Serialize, Deserializer, Serializer};
|
||||
use trust_dns_proto::error::ProtoError;
|
||||
|
||||
|
@ -48,6 +49,13 @@ impl SerdeName {
|
|||
}
|
||||
}
|
||||
|
||||
fn parse_absolute_name(name: &str) -> Result<AbsoluteName, ProtoError> {
|
||||
let mut name = Name::from_utf8(name)?;
|
||||
if !name.is_fqdn() {
|
||||
name.set_fqdn(true);
|
||||
}
|
||||
Ok(AbsoluteName(SerdeName(name)))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AbsoluteName(SerdeName);
|
||||
|
@ -56,14 +64,22 @@ impl<'r> FromParam<'r> for AbsoluteName {
|
|||
type Error = ProtoError;
|
||||
|
||||
fn from_param(param: &'r str) -> Result<Self, Self::Error> {
|
||||
let mut name = Name::from_utf8(¶m)?;
|
||||
if !name.is_fqdn() {
|
||||
name.set_fqdn(true);
|
||||
}
|
||||
Ok(AbsoluteName(SerdeName(name)))
|
||||
let name = parse_absolute_name(param)?;
|
||||
Ok(name)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl<'v> FromFormField<'v> for AbsoluteName {
|
||||
fn from_value(field: ValueField<'v>) -> form::Result<'v, Self> {
|
||||
let name = parse_absolute_name(field.value)
|
||||
.map_err(|_| form::Error::validation("Invalid name"))?;
|
||||
|
||||
Ok(name)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Deref for AbsoluteName {
|
||||
type Target = Name;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
|
|
|
@ -60,7 +60,7 @@ pub struct CreateUserRequest {
|
|||
pub role: Option<Role>
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserInfo {
|
||||
pub id: String,
|
||||
pub role: Role,
|
||||
|
|
|
@ -24,7 +24,7 @@ pub struct AddZoneMemberRequest {
|
|||
pub id: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, FromForm)]
|
||||
pub struct CreateZoneRequest {
|
||||
pub name: AbsoluteName,
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ use crate::DbConn;
|
|||
use crate::dns::{RecordConnector, ZoneConnector};
|
||||
use crate::models;
|
||||
use crate::models::{ParseRecordList};
|
||||
use crate::controllers;
|
||||
|
||||
|
||||
#[get("/zones/<zone>/records")]
|
||||
|
@ -130,33 +131,27 @@ pub async fn get_zones(
|
|||
) -> Result<Json<Vec<models::Zone>>, models::ErrorResponse> {
|
||||
let user_info = user_info?;
|
||||
|
||||
let zones = conn.run(move |c| {
|
||||
if user_info.is_admin() {
|
||||
models::Zone::get_all(c)
|
||||
} else {
|
||||
user_info.get_zones(c)
|
||||
}
|
||||
}).await?;
|
||||
|
||||
Ok(Json(zones))
|
||||
controllers::get_zones(
|
||||
&conn,
|
||||
user_info
|
||||
).await.map(|zones| Json(zones))
|
||||
}
|
||||
|
||||
#[post("/zones", data = "<zone_request>")]
|
||||
pub async fn create_zone(
|
||||
conn: DbConn,
|
||||
mut dns_api: Box<dyn ZoneConnector>,
|
||||
dns_api: Box<dyn ZoneConnector>,
|
||||
user_info: Result<models::UserInfo, models::ErrorResponse>,
|
||||
zone_request: Json<models::CreateZoneRequest>,
|
||||
) -> Result<Json<models::Zone>, models::ErrorResponse> {
|
||||
user_info?.check_admin()?;
|
||||
let user_info = user_info?;
|
||||
|
||||
dns_api.zone_exists(zone_request.name.clone(), models::DNSClass::IN.into()).await?;
|
||||
|
||||
let zone = conn.run(move |c| {
|
||||
models::Zone::create_zone(c, zone_request.into_inner())
|
||||
}).await?;
|
||||
|
||||
Ok(Json(zone))
|
||||
controllers::create_zone(
|
||||
&conn,
|
||||
dns_api,
|
||||
user_info,
|
||||
zone_request.into_inner()
|
||||
).await.map(|zone| Json(zone))
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2,10 +2,13 @@ use serde_json::{Value, json};
|
|||
use serde::Serialize;
|
||||
use rocket::http::{Status};
|
||||
use rocket::http::uri::Origin;
|
||||
use rocket::form::Form;
|
||||
|
||||
use crate::template::Template;
|
||||
use crate::models;
|
||||
use crate::controllers;
|
||||
use crate::DbConn;
|
||||
use crate::dns::ZoneConnector;
|
||||
|
||||
|
||||
#[derive(Serialize)]
|
||||
|
@ -44,14 +47,10 @@ pub async fn get_zone_records_page(user_info: models::UserInfo, zone: models::Ab
|
|||
|
||||
#[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)?;
|
||||
|
||||
let zones = controllers::get_zones(
|
||||
&conn,
|
||||
user_info
|
||||
).await.map_err(|e| e.status)?;
|
||||
|
||||
Ok(Template::new(
|
||||
"pages/zones.html",
|
||||
|
@ -62,3 +61,68 @@ pub async fn get_zones_page(user_info: models::UserInfo, conn: DbConn, origin: &
|
|||
})
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
#[get("/zones/new")]
|
||||
pub async fn get_create_zone_page(
|
||||
conn: DbConn,
|
||||
user_info: models::UserInfo,
|
||||
origin: &Origin<'_>
|
||||
) -> Result<Template<'static, Value>, Status> {
|
||||
|
||||
user_info
|
||||
.check_admin()
|
||||
.map_err(|e| models::ErrorResponse::from(e).status)?;
|
||||
|
||||
let zones = controllers::get_zones(
|
||||
&conn,
|
||||
user_info
|
||||
).await.map_err(|e| e.status)?;
|
||||
|
||||
Ok(Template::new(
|
||||
"pages/zones/new.html",
|
||||
json!({
|
||||
"zone": None::<models::Zone>,
|
||||
"zones": zones,
|
||||
"error": None::<String>,
|
||||
"nav_page": origin.clone().into_normalized().path().as_str(),
|
||||
"nav_sections": vec!["zones", "_new-zone"],
|
||||
})
|
||||
))
|
||||
}
|
||||
|
||||
#[post("/zones/new", data = "<zone_request>")]
|
||||
pub async fn post_create_zone_page(
|
||||
conn: DbConn,
|
||||
dns_api: Box<dyn ZoneConnector>,
|
||||
user_info: models::UserInfo,
|
||||
zone_request: Form<models::CreateZoneRequest>,
|
||||
origin: &Origin<'_>
|
||||
) -> Result<Template<'static, Value>, Status> {
|
||||
user_info
|
||||
.check_admin()
|
||||
.map_err(|e| models::ErrorResponse::from(e).status)?;
|
||||
|
||||
let zone = controllers::create_zone(
|
||||
&conn,
|
||||
dns_api,
|
||||
user_info.clone(),
|
||||
zone_request.into_inner()
|
||||
).await.map_err(|e| e.status)?;
|
||||
|
||||
let zones = controllers::get_zones(
|
||||
&conn,
|
||||
user_info
|
||||
).await.map_err(|e| e.status)?;
|
||||
|
||||
Ok(Template::new(
|
||||
"pages/zones/new.html",
|
||||
json!({
|
||||
"zone": Some(zone),
|
||||
"zones": zones,
|
||||
"error": None::<String>,
|
||||
"nav_page": origin.clone().into_normalized().path().as_str(),
|
||||
"nav_sections": vec!["zones", "_new-zone"],
|
||||
})
|
||||
))
|
||||
}
|
||||
|
|
|
@ -2,33 +2,43 @@
|
|||
{% 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>
|
||||
<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,
|
||||
) }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
<li>
|
||||
{{ macros::nav_link(
|
||||
content="Ajouter une zone",
|
||||
href="/zones/new",
|
||||
current_page=nav_page,
|
||||
section="_new-zone",
|
||||
current_sections=nav_sections,
|
||||
) }}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<main>
|
||||
{% block main %}{% endblock main %}
|
||||
{% block main %}{% endblock main %}
|
||||
</main>
|
||||
{% endblock content %}
|
||||
|
|
|
@ -7,12 +7,13 @@
|
|||
|
||||
{% block content %}
|
||||
<main>
|
||||
{% if error %}
|
||||
<p>
|
||||
{{ error }}
|
||||
</p>
|
||||
{% endif %}
|
||||
<form method="POST" action="/login">
|
||||
{% if error %}
|
||||
<p class="feedback error" role="alert">
|
||||
{{ error }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<label for="email">Adresse e-mail</label>
|
||||
<input type="email" id="email" name="email">
|
||||
<label for="password">Mot de passe</label>
|
||||
|
|
17
templates/pages/zones/new.html
Normal file
17
templates/pages/zones/new.html
Normal file
|
@ -0,0 +1,17 @@
|
|||
{% extends "bases/app.html" %}
|
||||
|
||||
{% block title %}Créer une zone ⋅ {% endblock title %}
|
||||
|
||||
{% block main %}
|
||||
<form method="POST" action="/zones/new">
|
||||
{% if error %}
|
||||
<p class="feedback error" role="alert">
|
||||
{{ error }}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
<label for="zone_name">Nom de la zone</label>
|
||||
<input type="text" id="zone_name" name="name">
|
||||
<input type="submit" value="Créer la zone">
|
||||
</form>
|
||||
{% endblock main %}
|
Loading…
Reference in a new issue