change record data structure
This commit is contained in:
parent
91bffe153a
commit
cfdd9afc0e
10 changed files with 440 additions and 422 deletions
|
@ -11,18 +11,18 @@ zone-content-section-mail-header = E-mail
|
|||
zone-content-section-services-header = Services
|
||||
zone-content-section-general-header = General
|
||||
|
||||
zone-content-record-type-address =
|
||||
zone-content-record-type-addresses =
|
||||
.type-name = IP addresses
|
||||
|
||||
zone-content-record-type-mailserver =
|
||||
zone-content-record-type-mailservers =
|
||||
.type-name = E-mail servers
|
||||
.data-preference = Preference: { $preference }
|
||||
|
||||
zone-content-record-type-nameserver =
|
||||
zone-content-record-type-nameservers =
|
||||
.type-name = Name servers
|
||||
|
||||
zone-content-record-type-service =
|
||||
.type-name = Services
|
||||
.type-name = Service
|
||||
.data-priority = Priority: { $priority }
|
||||
.data-weight = Weight: { $weight }
|
||||
|
||||
|
|
|
@ -1,190 +1,134 @@
|
|||
use std::{collections::HashMap, fmt};
|
||||
use std::{collections::HashMap, hash::Hash};
|
||||
|
||||
use serde::{Deserialize, Serialize, Serializer, ser::SerializeStruct};
|
||||
use serde::{Deserialize, Serialize, Serializer};
|
||||
|
||||
use crate::resources::dns::internal;
|
||||
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, PartialEq)]
|
||||
#[derive(Debug, Deserialize, Serialize, Hash, Eq, PartialEq)]
|
||||
#[serde(rename_all="lowercase")]
|
||||
pub enum FriendlyRType {
|
||||
Address,
|
||||
Addresses,
|
||||
Alias,
|
||||
MailServer,
|
||||
NameServer,
|
||||
MailServers,
|
||||
NameServers,
|
||||
Service,
|
||||
Spf,
|
||||
TextData,
|
||||
}
|
||||
|
||||
impl fmt::Display for FriendlyRType {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
FriendlyRType::Address => write!(f, "address"),
|
||||
FriendlyRType::Alias => write!(f, "alias"),
|
||||
FriendlyRType::MailServer => write!(f, "mailserver"),
|
||||
FriendlyRType::NameServer => write!(f, "nameserver"),
|
||||
FriendlyRType::Service => write!(f, "service"),
|
||||
FriendlyRType::Spf => write!(f, "spf"),
|
||||
FriendlyRType::TextData => write!(f, "textdata"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait HasRType {
|
||||
fn rtype(&self) -> FriendlyRType;
|
||||
Texts,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all="lowercase")]
|
||||
pub enum RecordSection {
|
||||
Mail,
|
||||
Web,
|
||||
Services,
|
||||
Miscellaneous,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(tag = "_type")]
|
||||
pub enum FriendlyRData {
|
||||
Address(Address),
|
||||
Alias(Alias),
|
||||
MailServer(MailServer),
|
||||
NameServer(NameServer),
|
||||
Service(Service),
|
||||
Service(ServiceSingleTarget),
|
||||
Spf(Spf),
|
||||
TextData(TextData),
|
||||
}
|
||||
|
||||
impl HasRType for FriendlyRData {
|
||||
fn rtype(&self) -> FriendlyRType {
|
||||
match self {
|
||||
FriendlyRData::Address(_) => FriendlyRType::Address,
|
||||
FriendlyRData::Alias(_) => FriendlyRType::Alias,
|
||||
FriendlyRData::MailServer(_) => FriendlyRType::MailServer,
|
||||
FriendlyRData::NameServer(_) => FriendlyRType::NameServer,
|
||||
FriendlyRData::Service(_) => FriendlyRType::Service,
|
||||
FriendlyRData::Spf(_) => FriendlyRType::Spf,
|
||||
FriendlyRData::TextData(_) => FriendlyRType::TextData,
|
||||
}
|
||||
}
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum FriendlyRDataAggregated {
|
||||
Addresses(Addresses),
|
||||
MailServers(MailServers),
|
||||
NameServers(NameServers),
|
||||
Service(Service),
|
||||
Spf(Spf),
|
||||
Texts(Texts),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FriendlyRecord<T> {
|
||||
pub struct FriendlyRecord {
|
||||
ttl: i64,
|
||||
data: T,
|
||||
data: FriendlyRDataAggregated,
|
||||
}
|
||||
|
||||
impl<T: Serialize + HasRType> Serialize for FriendlyRecord<T> {
|
||||
impl Serialize for FriendlyRecord {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_struct("FriendlyRecord", 3)?;
|
||||
state.serialize_field("ttl", &self.ttl)?;
|
||||
state.serialize_field("data", &self.data)?;
|
||||
state.serialize_field("rtype", &self.data.rtype())?;
|
||||
state.end()
|
||||
#[derive(Serialize)]
|
||||
struct ExtendedRecord<'a> {
|
||||
ttl: i64,
|
||||
record_type: FriendlyRType,
|
||||
record_section: RecordSection,
|
||||
#[serde(flatten)]
|
||||
data: &'a FriendlyRDataAggregated,
|
||||
}
|
||||
|
||||
let extended_record = ExtendedRecord {
|
||||
ttl: self.ttl,
|
||||
data: &self.data,
|
||||
record_type: self.record_type(),
|
||||
record_section: self.record_section(),
|
||||
};
|
||||
|
||||
extended_record.serialize(serializer)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FriendlyRecord<T> {
|
||||
fn new(ttl: u32, data: T) -> Self {
|
||||
impl FriendlyRecord {
|
||||
pub fn new(ttl: u32, data: FriendlyRDataAggregated) -> Self {
|
||||
FriendlyRecord {
|
||||
ttl: ttl.into(),
|
||||
data,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FriendlyRecordSet<T> {
|
||||
ttl: i64,
|
||||
data: Vec<T>,
|
||||
}
|
||||
|
||||
impl<T: Serialize + HasRType> Serialize for FriendlyRecordSet<T> {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut state = serializer.serialize_struct("FriendlyRecordSet", 3)?;
|
||||
state.serialize_field("ttl", &self.ttl)?;
|
||||
state.serialize_field("data", &self.data)?;
|
||||
state.serialize_field("rtype", &self.data.first().map(|d| d.rtype().to_string()))?;
|
||||
state.end()
|
||||
pub fn record_type(&self) -> FriendlyRType {
|
||||
match self.data {
|
||||
FriendlyRDataAggregated::Addresses(_) => FriendlyRType::Addresses,
|
||||
FriendlyRDataAggregated::MailServers(_) => FriendlyRType::MailServers,
|
||||
FriendlyRDataAggregated::NameServers(_) => FriendlyRType::NameServers,
|
||||
FriendlyRDataAggregated::Service(_) => FriendlyRType::Service,
|
||||
FriendlyRDataAggregated::Spf(_) => FriendlyRType::Spf,
|
||||
FriendlyRDataAggregated::Texts(_) => FriendlyRType::Texts,
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> FriendlyRecordSet<T> {
|
||||
fn new(ttl: u32) -> Self {
|
||||
FriendlyRecordSet {
|
||||
ttl: ttl.into(),
|
||||
data: Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
#[serde(rename_all="lowercase")]
|
||||
pub enum ConfigurationType {
|
||||
Mail,
|
||||
Web,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct MailConfiguration {
|
||||
pub servers: Option<FriendlyRecordSet<MailServer>>,
|
||||
pub spf: Option<FriendlyRecord<Spf>>,
|
||||
}
|
||||
|
||||
impl MailConfiguration {
|
||||
pub fn new() -> Self {
|
||||
MailConfiguration {
|
||||
servers: None,
|
||||
spf: None
|
||||
pub fn record_section(&self) -> RecordSection {
|
||||
match self.data {
|
||||
FriendlyRDataAggregated::Addresses(_) => RecordSection::Web,
|
||||
FriendlyRDataAggregated::MailServers(_) => RecordSection::Mail,
|
||||
FriendlyRDataAggregated::NameServers(_) => RecordSection::Miscellaneous,
|
||||
FriendlyRDataAggregated::Service(_) => RecordSection::Services,
|
||||
FriendlyRDataAggregated::Spf(_) => RecordSection::Mail,
|
||||
FriendlyRDataAggregated::Texts(_) => RecordSection::Miscellaneous,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct WebConfiguration {
|
||||
pub addresses: Option<FriendlyRecordSet<Address>>,
|
||||
pub struct Node {
|
||||
pub name: String,
|
||||
pub records: Vec<FriendlyRecord>,
|
||||
}
|
||||
|
||||
|
||||
impl WebConfiguration {
|
||||
pub fn new() -> Self {
|
||||
WebConfiguration {
|
||||
addresses: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct FriendlyRecordSetGroup {
|
||||
owner: String,
|
||||
general_records: Vec<FriendlyRecordSet<FriendlyRData>>,
|
||||
#[serde(serialize_with = "as_vector")]
|
||||
services: HashMap<ServiceType, FriendlyRecordSet<Service>>,
|
||||
mail: Option<MailConfiguration>,
|
||||
web: Option<WebConfiguration>,
|
||||
}
|
||||
|
||||
fn as_vector<S>(services: &HashMap<ServiceType, FriendlyRecordSet<Service>>, ser: S) -> Result<S::Ok, S::Error>
|
||||
where S: Serializer
|
||||
{
|
||||
let container: Vec<_> = services.iter().collect();
|
||||
serde::Serialize::serialize(&container, ser)
|
||||
}
|
||||
|
||||
impl FriendlyRecordSetGroup {
|
||||
pub fn new(owner: String) -> Self {
|
||||
FriendlyRecordSetGroup {
|
||||
owner,
|
||||
general_records: Vec::new(),
|
||||
services: HashMap::new(),
|
||||
mail: None,
|
||||
web: None,
|
||||
impl Node {
|
||||
fn new(name: String) -> Self {
|
||||
Node {
|
||||
name,
|
||||
records: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct FriendlyRecords {
|
||||
records: Vec<FriendlyRecordSetGroup>,
|
||||
records: Vec<Node>,
|
||||
aliases: Vec<Alias>,
|
||||
}
|
||||
|
||||
|
@ -201,6 +145,8 @@ impl From<internal::RecordList> for FriendlyRecords {
|
|||
fn from(value: internal::RecordList) -> Self {
|
||||
|
||||
let mut records = FriendlyRecords::new();
|
||||
let mut name_mapping: HashMap<String, HashMap<FriendlyRType, FriendlyRecord>> = HashMap::new();
|
||||
let mut service_mapping: HashMap<(String, ServiceType), FriendlyRecord> = HashMap::new();
|
||||
|
||||
for record in value.records {
|
||||
let internal::Record { name, ttl, rdata } = record;
|
||||
|
@ -216,100 +162,148 @@ impl From<internal::RecordList> for FriendlyRecords {
|
|||
if let FriendlyRData::Alias(alias) = rdata {
|
||||
records.aliases.push(alias)
|
||||
} else {
|
||||
|
||||
if records.records.is_empty() {
|
||||
records.records.push(FriendlyRecordSetGroup::new(name));
|
||||
} else {
|
||||
let group = records.records.last().unwrap();
|
||||
|
||||
if group.owner != name {
|
||||
records.records.push(FriendlyRecordSetGroup::new(name));
|
||||
}
|
||||
}
|
||||
|
||||
let current_group = records.records.last_mut().unwrap();
|
||||
let node = name_mapping.entry(name.clone()).or_default();
|
||||
|
||||
match rdata {
|
||||
FriendlyRData::Address(address) => {
|
||||
let web = current_group.web.get_or_insert_with(WebConfiguration::new);
|
||||
let addresses = web.addresses.get_or_insert_with(|| FriendlyRecordSet::new(ttl));
|
||||
let addresses = node.entry(FriendlyRType::Addresses).or_insert_with(|| {
|
||||
FriendlyRecord::new(ttl, FriendlyRDataAggregated::Addresses(Addresses {
|
||||
addresses: Vec::new()
|
||||
}))
|
||||
});
|
||||
|
||||
addresses.data.push(address);
|
||||
match addresses.data {
|
||||
FriendlyRDataAggregated::Addresses(ref mut addresses) => addresses.addresses.push(address),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
},
|
||||
FriendlyRData::MailServer(mailserver) => {
|
||||
let mail: &mut MailConfiguration = current_group.mail.get_or_insert_with(MailConfiguration::new);
|
||||
let servers = mail.servers.get_or_insert_with(|| FriendlyRecordSet::new(ttl));
|
||||
let mailservers = node.entry(FriendlyRType::MailServers).or_insert_with(|| {
|
||||
FriendlyRecord::new(ttl, FriendlyRDataAggregated::MailServers(MailServers {
|
||||
mailservers: Vec::new()
|
||||
}))
|
||||
});
|
||||
|
||||
servers.data.push(mailserver);
|
||||
match mailservers.data {
|
||||
FriendlyRDataAggregated::MailServers(ref mut mailservers) => mailservers.mailservers.push(mailserver),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
},
|
||||
FriendlyRData::Spf(spf) => {
|
||||
let mail: &mut MailConfiguration = current_group.mail.get_or_insert_with(MailConfiguration::new);
|
||||
|
||||
mail.spf = Some(FriendlyRecord::new(ttl, spf));
|
||||
node.insert(FriendlyRType::Spf, FriendlyRecord::new(ttl, FriendlyRDataAggregated::Spf(spf)));
|
||||
},
|
||||
FriendlyRData::Service(service) => {
|
||||
let services = current_group.services.entry(service.service_type.clone()).or_insert(FriendlyRecordSet::new(ttl));
|
||||
FriendlyRData::Service(service_single) => {
|
||||
let service = service_mapping.entry((name.clone(), service_single.service_type.clone()))
|
||||
.or_insert_with(|| {
|
||||
FriendlyRecord::new(ttl, FriendlyRDataAggregated::Service(Service {
|
||||
service_type: service_single.service_type,
|
||||
service_targets: Vec::new(),
|
||||
|
||||
services.data.push(service)
|
||||
}
|
||||
FriendlyRData::Alias(_) => {},
|
||||
data => {
|
||||
}))
|
||||
});
|
||||
|
||||
match service.data {
|
||||
FriendlyRDataAggregated::Service(ref mut service) => service.service_targets.push(service_single.service_target),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
},
|
||||
FriendlyRData::NameServer(nameserver) => {
|
||||
// TODO: NS -> Skip if NS for zone (authority), create Delegation section with glue + DS for others (how to check if record is glue?)
|
||||
let nameservers = node.entry(FriendlyRType::NameServers).or_insert_with(|| {
|
||||
FriendlyRecord::new(ttl, FriendlyRDataAggregated::NameServers(NameServers {
|
||||
nameservers: Vec::new()
|
||||
}))
|
||||
});
|
||||
|
||||
if current_group.general_records.is_empty() {
|
||||
current_group.general_records.push(FriendlyRecordSet::new(ttl));
|
||||
} else {
|
||||
let rrset = current_group.general_records.last().unwrap();
|
||||
if rrset.data.last().unwrap().rtype() != data.rtype() {
|
||||
current_group.general_records.push(FriendlyRecordSet::new(ttl));
|
||||
match nameservers.data {
|
||||
FriendlyRDataAggregated::NameServers(ref mut nameservers) => nameservers.nameservers.push(nameserver),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
},
|
||||
FriendlyRData::TextData(text) => {
|
||||
let texts = node.entry(FriendlyRType::Texts).or_insert_with(|| {
|
||||
FriendlyRecord::new(ttl, FriendlyRDataAggregated::Texts(Texts {
|
||||
texts: Vec::new()
|
||||
}))
|
||||
});
|
||||
|
||||
match texts.data {
|
||||
FriendlyRDataAggregated::Texts(ref mut texts) => texts.texts.push(text),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
},
|
||||
FriendlyRData::Alias(_) => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let current_rrset = current_group.general_records.last_mut().unwrap();
|
||||
current_rrset.data.push(data)
|
||||
}
|
||||
}
|
||||
let mut nodes: HashMap<String, Node> = HashMap::new();
|
||||
|
||||
for ((name, _), service) in service_mapping {
|
||||
let node = nodes.entry(name.clone())
|
||||
.or_insert_with(|| Node::new(name));
|
||||
node.records.push(service);
|
||||
}
|
||||
|
||||
for (name, node_records) in name_mapping {
|
||||
let node = nodes.entry(name.clone())
|
||||
.or_insert_with(|| Node::new(name));
|
||||
for (_, record) in node_records {
|
||||
node.records.push(record);
|
||||
}
|
||||
}
|
||||
|
||||
records.records = nodes.into_values().collect();
|
||||
|
||||
records.records.sort_by_key(|node| node.name.clone());
|
||||
|
||||
records
|
||||
}
|
||||
}
|
||||
|
||||
/* --------- RDATA --------- */
|
||||
|
||||
/* --------- Address --------- */
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Address {
|
||||
pub address: String
|
||||
}
|
||||
|
||||
|
||||
impl HasRType for Address {
|
||||
fn rtype(&self) -> FriendlyRType {
|
||||
FriendlyRType::Address
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Addresses {
|
||||
pub addresses: Vec<Address>,
|
||||
}
|
||||
|
||||
/* --------- Service --------- */
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ServiceSingleTarget {
|
||||
pub service_type: ServiceType,
|
||||
pub service_target: ServiceTarget,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Service {
|
||||
pub service_type: ServiceType,
|
||||
pub service_targets: Vec<ServiceTarget>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct ServiceTarget {
|
||||
pub port: i64,
|
||||
pub weight: i64,
|
||||
pub priority: i64,
|
||||
pub server: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Hash, Eq, PartialEq, Clone)]
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "lowercase", tag = "service_type")]
|
||||
pub enum ServiceType {
|
||||
Other { protocol: String, name: String },
|
||||
}
|
||||
|
||||
impl HasRType for Service {
|
||||
fn rtype(&self) -> FriendlyRType {
|
||||
FriendlyRType::Service
|
||||
}
|
||||
}
|
||||
/* --------- MailServer --------- */
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct MailServer {
|
||||
|
@ -317,36 +311,47 @@ pub struct MailServer {
|
|||
pub mail_exchanger: String,
|
||||
}
|
||||
|
||||
impl HasRType for MailServer {
|
||||
fn rtype(&self) -> FriendlyRType {
|
||||
FriendlyRType::MailServer
|
||||
}
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct MailServers {
|
||||
pub mailservers: Vec<MailServer>
|
||||
}
|
||||
|
||||
/* --------- NameServer --------- */
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct NameServer {
|
||||
pub target: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct NameServers {
|
||||
pub nameservers: Vec<NameServer>
|
||||
}
|
||||
|
||||
/* --------- TextData --------- */
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct TextData {
|
||||
pub text: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Texts {
|
||||
pub texts: Vec<TextData>,
|
||||
}
|
||||
|
||||
/* --------- Spf --------- */
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Spf {
|
||||
pub policy: String
|
||||
}
|
||||
|
||||
/* --------- Alias --------- */
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Alias {
|
||||
pub from: String,
|
||||
pub target: String,
|
||||
pub ttl: i64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize)]
|
||||
pub struct Spf {
|
||||
pub policy: String
|
||||
}
|
||||
|
||||
impl HasRType for Spf {
|
||||
fn rtype(&self) -> FriendlyRType {
|
||||
FriendlyRType::Spf
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,15 +147,17 @@ impl Srv {
|
|||
let service_name = labels[0]. strip_prefix('_');
|
||||
let protocol = labels[1]. strip_prefix('_');
|
||||
if let (Some(service_name), Some(protocol)) = (service_name, protocol) {
|
||||
Some((labels[2].to_string(), friendly::FriendlyRData::Service(friendly::Service {
|
||||
Some((labels[2].to_string(), friendly::FriendlyRData::Service(friendly::ServiceSingleTarget {
|
||||
service_type: friendly::ServiceType::Other {
|
||||
name: service_name.into(),
|
||||
protocol: protocol.into()
|
||||
},
|
||||
service_target: friendly::ServiceTarget {
|
||||
port: self.port.into(),
|
||||
weight: self.weight.into(),
|
||||
priority: self.priority.into(),
|
||||
server: self.server.to_string(),
|
||||
server: self.server.to_string()
|
||||
}
|
||||
})))
|
||||
} else {
|
||||
None
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use axum::extract::{Query, Path, State, OriginalUri};
|
||||
use axum::Extension;
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::{Value, json};
|
||||
use unic_langid::LanguageIdentifier;
|
||||
|
||||
|
@ -35,10 +35,17 @@ pub async fn get_records_page(
|
|||
#[derive(Deserialize)]
|
||||
pub struct NewRecordQuery {
|
||||
subdomain: Option<String>,
|
||||
config: Option<friendly::ConfigurationType>,
|
||||
config: Option<ConfigurationType>,
|
||||
rtype: Option<friendly::FriendlyRType>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum ConfigurationType {
|
||||
Mail,
|
||||
Web,
|
||||
}
|
||||
|
||||
pub async fn get_new_record_page(
|
||||
Path(zone_name): Path<String>,
|
||||
State(app): State<AppState>,
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
{% macro rrset(rtype, ttl, data, zone, lang, service_type="") %}
|
||||
{% macro rrset(record, zone, lang) %}
|
||||
<li class="rrset">
|
||||
<div class="rtype">
|
||||
{% if rtype == "service" %}
|
||||
{% if service_type.service_type == "other" %}
|
||||
{{ service_type.name }}/{{ service_type.protocol }}
|
||||
{% if record.record_type == "service" %}
|
||||
{% if record.service.service_type.service_type == "other" %}
|
||||
{{ record.service.service_type.name }}/{{ record.service.service_type.protocol }}
|
||||
{% else %}
|
||||
{{ service_type.service_type }}
|
||||
{{ record.srvice.service_type.service_type }}
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{ tr(msg="zone-content-record-type-" ~ rtype, attr="type-name", lang=lang) }}
|
||||
{{ tr(msg="zone-content-record-type-" ~ record.record_type, attr="type-name", lang=lang) }}
|
||||
{% endif %}
|
||||
<div class="action">
|
||||
<a class="button icon" href="#">
|
||||
|
@ -20,40 +20,53 @@
|
|||
</div>
|
||||
|
||||
<ul>
|
||||
{% for data in data %}
|
||||
{% if record.record_type == "addresses" %}
|
||||
{% for address in record.addresses.addresses %}
|
||||
<li>
|
||||
<div class="rdata">
|
||||
{% if rtype == "address" %}
|
||||
<div class="rdata-main">
|
||||
<span class="pill">
|
||||
{{ data.address }}
|
||||
{{ address.address }}
|
||||
</span>
|
||||
</div>
|
||||
{% elif rtype == "mailserver" %}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% elif record.record_type == "mailservers" %}
|
||||
{% for mailserver in record.mailservers.mailservers %}
|
||||
<li>
|
||||
<div class="rdata-main">
|
||||
<span class="pill">
|
||||
{{ data.mail_exchanger }}
|
||||
{{ mailserver.mail_exchanger }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="rdata-complementary">
|
||||
<span class="pill">
|
||||
{{ tr(
|
||||
msg="zone-content-record-type-mailserver",
|
||||
msg="zone-content-record-type-mailservers",
|
||||
attr="data-preference",
|
||||
preference=data.preference,
|
||||
preference=mailserver.preference,
|
||||
lang=lang) }}
|
||||
</span>
|
||||
</div>
|
||||
{% elif rtype == "nameserver" %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% elif record.record_type == "nameservers" %}
|
||||
{% for nameserver in record.nameservers.nameservers %}
|
||||
<li>
|
||||
<div class="rdata-main">
|
||||
<span class="pill">
|
||||
{{ data.target }}
|
||||
{{ nameserver.target }}
|
||||
</span>
|
||||
</div>
|
||||
{% elif rtype == "service" %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% elif record.record_type == "service" %}
|
||||
{% for service_target in record.service.service_targets %}
|
||||
<li>
|
||||
<div class="rdata-main">
|
||||
<span class="pill">
|
||||
{{ data.server ~ ":" ~ data.port }}
|
||||
{{ service_target.server ~ ":" ~ service_target.port }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="rdata-complementary">
|
||||
|
@ -61,21 +74,20 @@
|
|||
{{ tr(
|
||||
msg="zone-content-record-type-service",
|
||||
attr="data-priority",
|
||||
priority=data.priority,
|
||||
priority=service_target.priority,
|
||||
lang=lang) }}
|
||||
</span>
|
||||
<span class="pill">
|
||||
{{ tr(
|
||||
msg="zone-content-record-type-service",
|
||||
attr="data-weight",
|
||||
weight=data.weight,
|
||||
weight=service_target.weight,
|
||||
lang=lang) }}
|
||||
</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endmacro rrset %}
|
||||
|
|
|
@ -4,131 +4,13 @@
|
|||
|
||||
{% block main %}
|
||||
<h1>Create a new record in zone {{ current_zone }}</h1>
|
||||
|
||||
{% if not new_record_name or (new_record_name and domain_error) %}
|
||||
<h2>Choose the name of the new record</h2>
|
||||
<form action="" method="GET">
|
||||
<label for="subdomain">Name of the new record</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
name="subdomain"
|
||||
id="subdomain"
|
||||
{% if domain_error %}aria-invalid="true"{% endif %}
|
||||
aria-describedby="subdomain-help{% if domain_error %} subdomain-error{% endif %}"
|
||||
>
|
||||
<span>.{{ current_zone }}</span>
|
||||
</div>
|
||||
{% if domain_error %}
|
||||
<p class="error" id="subdomain-error">{{ domain_error.description }}</p>
|
||||
{% endif %}
|
||||
<p id="subdomain-help">Only the subdomain, without the parent domain. For instance, "www" to create the subdomain "www.{{ current_zone }}".</p>
|
||||
<button type="submit">Next step</button>
|
||||
</form>
|
||||
{% include "pages/new_record/choose_name.html" %}
|
||||
{% elif not config and not rtype %}
|
||||
|
||||
<h2>Configure the domain {{ new_record_name }}...</h2>
|
||||
<ul>
|
||||
<li><a href="{{ url }}&config=web">Web site</a></li>
|
||||
<li><a href="{{ url }}&config=mail">E-mails</a></li>
|
||||
</ul>
|
||||
<h2>...or create a new record for the domain {{ new_record_name }}</h2>
|
||||
<h3>General</h3>
|
||||
<ul>
|
||||
<li><a href="{{ url }}&rtype=address">Address (A or AAAA)</a></li>
|
||||
<li><a href="{{ url }}&rtype=alias">Alias (CNAME)</a></li>
|
||||
<li><a href="{{ url }}&rtype=text">Text (TXT)</a></li>
|
||||
<li><a href="{{ url }}&rtype=service">Service (SRV)</a></li>
|
||||
</ul>
|
||||
<h3>E-mails</h3>
|
||||
<ul>sdv
|
||||
<li><a href="{{ url }}&rtype=service">Mail servers (MX)</a></li>
|
||||
<li><a href="{{ url }}&rtype=spf">Sender policy (SPF)</a></li>
|
||||
<li><a href="{{ url }}&rtype=dkim">Cryptographic signature (DKIM)</a></li>
|
||||
<li><a href="{{ url }}&rtype=dmarc">Error reporting (DMARC)</a></li>
|
||||
</ul>
|
||||
<h3>Security</h3>
|
||||
<ul>
|
||||
<li><a href="{{ url }}&rtype=dane">Domain authentication (TLSA)</a></li>
|
||||
<li><a href="{{ url }}&rtype=sshfp">SSH keys fingerprint (SSHFP)</a></li>
|
||||
</ul>
|
||||
<h3>DNS Delegation</h3>
|
||||
<ul>
|
||||
<li><a href="{{ url }}&rtype=nameserver">Nameserver (NS)</a></li>
|
||||
<li><a href="{{ url }}&rtype=ds">Delegation signer (DS)</a></li>
|
||||
</ul>
|
||||
{% include "pages/new_record/choose_record.html" %}
|
||||
{% else %}
|
||||
|
||||
{% if config == "web" %}
|
||||
|
||||
<h2>Configure a web site for the domain <strong>{{ new_record_name }}</strong></h2>
|
||||
|
||||
<form>
|
||||
<h3>Web servers</h3>
|
||||
<div class="form-input">
|
||||
<label for="address">IP Address #1</label>
|
||||
<input name="address" id="address" type="text">
|
||||
</div>
|
||||
|
||||
<button type="submit">Save configuration</button>
|
||||
</form>
|
||||
|
||||
{% elif config == "mail" %}
|
||||
|
||||
<h2>Configure e-mails for the domain <strong>{{ new_record_name }}</strong></h2>
|
||||
|
||||
<form>
|
||||
<h3>Mail servers</h3>
|
||||
<fieldset>
|
||||
<legend>Mail server #1</legend>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-input">
|
||||
<label for="server">Server name</label>
|
||||
<input name="server" id="server" type="text">
|
||||
</div>
|
||||
|
||||
<div class="form-input">
|
||||
<label for="preference">Preference</label>
|
||||
<input name="preference" id="preference" type="text">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<h3>Security</h3>
|
||||
|
||||
<div class="form-input">
|
||||
<label for="spf">Sender policy (SPF)</label>
|
||||
<input name="spf" id="spf" type="text">
|
||||
</div>
|
||||
|
||||
<div class="form-input">
|
||||
<label for="dmarc">Error reporting policy (DMARC)</label>
|
||||
<input name="dmarc" id="dmarc" type="text">
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Cryptographic signature (DKIM) #1</legend>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-input">
|
||||
<label for="dkim-selector">Selector</label>
|
||||
<input name="dkim-selector" id="dkim-selector" type="text">
|
||||
</div>
|
||||
|
||||
<div class="form-input">
|
||||
<label for="dkim-key">Signing key</label>
|
||||
<textarea name="dkim-key" id="dkim-key"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<button type="submit">Save configuration</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
|
||||
|
||||
{% include "pages/new_record/configure_record.html" %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
|
19
templates/pages/new_record/choose_name.html
Normal file
19
templates/pages/new_record/choose_name.html
Normal file
|
@ -0,0 +1,19 @@
|
|||
<h2>Choose the name of the new record</h2>
|
||||
<form action="" method="GET">
|
||||
<label for="subdomain">Name of the new record</label>
|
||||
<div class="input-group">
|
||||
<input
|
||||
type="text"
|
||||
name="subdomain"
|
||||
id="subdomain"
|
||||
{% if domain_error %}aria-invalid="true"{% endif %}
|
||||
aria-describedby="subdomain-help{% if domain_error %} subdomain-error{% endif %}"
|
||||
>
|
||||
<span>.{{ current_zone }}</span>
|
||||
</div>
|
||||
{% if domain_error %}
|
||||
<p class="error" id="subdomain-error">{{ domain_error.description }}</p>
|
||||
{% endif %}
|
||||
<p id="subdomain-help">Only the subdomain, without the parent domain. For instance, "www" to create the subdomain "www.{{ current_zone }}".</p>
|
||||
<button type="submit">Next step</button>
|
||||
</form>
|
30
templates/pages/new_record/choose_record.html
Normal file
30
templates/pages/new_record/choose_record.html
Normal file
|
@ -0,0 +1,30 @@
|
|||
<h2>Configure the domain {{ new_record_name }}...</h2>
|
||||
<ul>
|
||||
<li><a href="{{ url }}&config=web">Web site</a></li>
|
||||
<li><a href="{{ url }}&config=mail">E-mails</a></li>
|
||||
</ul>
|
||||
<h2>...or create a new record for the domain {{ new_record_name }}</h2>
|
||||
<h3>General</h3>
|
||||
<ul>
|
||||
<li><a href="{{ url }}&rtype=address">Address (A or AAAA)</a></li>
|
||||
<li><a href="{{ url }}&rtype=alias">Alias (CNAME)</a></li>
|
||||
<li><a href="{{ url }}&rtype=text">Text (TXT)</a></li>
|
||||
<li><a href="{{ url }}&rtype=service">Service (SRV)</a></li>
|
||||
</ul>
|
||||
<h3>E-mails</h3>
|
||||
<ul>sdv
|
||||
<li><a href="{{ url }}&rtype=service">Mail servers (MX)</a></li>
|
||||
<li><a href="{{ url }}&rtype=spf">Sender policy (SPF)</a></li>
|
||||
<li><a href="{{ url }}&rtype=dkim">Cryptographic signature (DKIM)</a></li>
|
||||
<li><a href="{{ url }}&rtype=dmarc">Error reporting (DMARC)</a></li>
|
||||
</ul>
|
||||
<h3>Security</h3>
|
||||
<ul>
|
||||
<li><a href="{{ url }}&rtype=dane">Domain authentication (TLSA)</a></li>
|
||||
<li><a href="{{ url }}&rtype=sshfp">SSH keys fingerprint (SSHFP)</a></li>
|
||||
</ul>
|
||||
<h3>DNS Delegation</h3>
|
||||
<ul>
|
||||
<li><a href="{{ url }}&rtype=nameserver">Nameserver (NS)</a></li>
|
||||
<li><a href="{{ url }}&rtype=ds">Delegation signer (DS)</a></li>
|
||||
</ul>
|
68
templates/pages/new_record/configure_record.html
Normal file
68
templates/pages/new_record/configure_record.html
Normal file
|
@ -0,0 +1,68 @@
|
|||
{% if config == "web" %}
|
||||
|
||||
<h2>Configure a web site for the domain <strong>{{ new_record_name }}</strong></h2>
|
||||
|
||||
<form>
|
||||
<h3>Web servers</h3>
|
||||
<div class="form-input">
|
||||
<label for="record-0-addresses-address-0">IP Address #1</label>
|
||||
<input name="records[0][addresses][addresses][0][address]" id="record-0-addresses-address-0" type="text">
|
||||
</div>
|
||||
|
||||
<button type="submit">Save configuration</button>
|
||||
</form>
|
||||
|
||||
{% elif config == "mail" %}
|
||||
|
||||
<h2>Configure e-mails for the domain <strong>{{ new_record_name }}</strong></h2>
|
||||
|
||||
<form>
|
||||
<h3>Mail servers</h3>
|
||||
<fieldset>
|
||||
<legend>Mail server #1</legend>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-input">
|
||||
<label for="server">Server name</label>
|
||||
<input name="server" id="server" type="text">
|
||||
</div>
|
||||
|
||||
<div class="form-input">
|
||||
<label for="preference">Preference</label>
|
||||
<input name="preference" id="preference" type="text">
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
<h3>Security</h3>
|
||||
|
||||
<div class="form-input">
|
||||
<label for="spf">Sender policy (SPF)</label>
|
||||
<input name="spf" id="spf" type="text">
|
||||
</div>
|
||||
|
||||
<div class="form-input">
|
||||
<label for="dmarc">Error reporting policy (DMARC)</label>
|
||||
<input name="dmarc" id="dmarc" type="text">
|
||||
</div>
|
||||
|
||||
<fieldset>
|
||||
<legend>Cryptographic signature (DKIM) #1</legend>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-input">
|
||||
<label for="dkim-selector">Selector</label>
|
||||
<input name="dkim-selector" id="dkim-selector" type="text">
|
||||
</div>
|
||||
|
||||
<div class="form-input">
|
||||
<label for="dkim-key">Signing key</label>
|
||||
<textarea name="dkim-key" id="dkim-key"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</fieldset>
|
||||
|
||||
|
||||
<button type="submit">Save configuration</button>
|
||||
</form>
|
||||
{% endif %}
|
|
@ -12,14 +12,15 @@
|
|||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
||||
<section>
|
||||
<h2>{{ tr(msg="zone-content-records-header", lang=lang) }}</h2>
|
||||
{% for group in records.records %}
|
||||
{% for node in records.records %}
|
||||
<article class="domain">
|
||||
<header>
|
||||
<h3 class="folder-tab">{{ group.owner }}</h3>
|
||||
<h3 class="folder-tab">{{ node.name }}</h3>
|
||||
<span class="sep"></span>
|
||||
<a href="{{ url }}/new?subdomain={{ group.owner | trim_end_matches(pat=current_zone) | trim_end_matches(pat=".") }}" class="button">
|
||||
<a href="{{ url }}/new?subdomain={{ node.name | trim_end_matches(pat=current_zone) | trim_end_matches(pat=".") }}" class="button">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-plus-circle" viewBox="0 0 16 16" aria-hidden="true">
|
||||
<path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
|
||||
<path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4"/>
|
||||
|
@ -27,66 +28,58 @@
|
|||
{{ tr(msg="zone-content-new-record-button", lang=lang) }}
|
||||
</a>
|
||||
</header>
|
||||
{% set sections = node.records | group_by(attribute="record_section") %}
|
||||
<div class="records">
|
||||
{% if group.web %}
|
||||
{% if sections.web %}
|
||||
{% set records = sections.web | group_by(attribute="record_type") %}
|
||||
<h4>{{ tr(msg="zone-content-section-web-header", lang=lang) }}</h4>
|
||||
<ul>
|
||||
{% if group.web.addresses %}
|
||||
{% if records.addresses %}
|
||||
{{ rrset::rrset(
|
||||
rtype=group.web.addresses.rtype,
|
||||
ttl=group.web.addresses.ttl,
|
||||
data=group.web.addresses.data,
|
||||
record=records.addresses.0,
|
||||
zone=current_zone,
|
||||
lang=lang) }}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if group.mail %}
|
||||
{% if sections.mail %}
|
||||
{% set records = sections.mail | group_by(attribute="record_type") %}
|
||||
<h4>{{ tr(msg="zone-content-section-mail-header", lang=lang) }}</h4>
|
||||
<ul>
|
||||
{% if group.mail.servers %}
|
||||
{% if records.mailservers %}
|
||||
{{ rrset::rrset(
|
||||
rtype=group.mail.servers.rtype,
|
||||
ttl=group.mail.servers.ttl,
|
||||
data=group.mail.servers.data,
|
||||
record=records.mailservers.0,
|
||||
zone=current_zone,
|
||||
lang=lang) }}
|
||||
{% endif %}
|
||||
{% if group.mail.spf %}
|
||||
{% if records.spf %}
|
||||
{{ rrset::rrset(
|
||||
rtype=group.mail.spf.rtype,
|
||||
ttl=group.mail.spf.ttl,
|
||||
data=group.mail.spf.data,
|
||||
record=records.spf.0,
|
||||
zone=current_zone,
|
||||
lang=lang) }}
|
||||
{% endif %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if group.services %}
|
||||
{% if sections.services %}
|
||||
<h4>{{ tr(msg="zone-content-section-services-header", lang=lang) }}</h4>
|
||||
<ul>
|
||||
{% for service in group.services %}
|
||||
{% for service in sections.services %}
|
||||
{{ rrset::rrset(
|
||||
rtype=service[1].rtype,
|
||||
ttl=service[1].ttl,
|
||||
data=service[1].data,
|
||||
record=service,
|
||||
zone=current_zone,
|
||||
lang=lang,
|
||||
service_type=service[0]) }}
|
||||
lang=lang) }}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endif %}
|
||||
|
||||
{% if group.general_records %}
|
||||
{% if sections.miscellaneous %}
|
||||
<h4>{{ tr(msg="zone-content-section-general-header", lang=lang) }}</h4>
|
||||
<ul>
|
||||
{% for rrset in group.general_records %}
|
||||
{% for record in sections.miscellaneous %}
|
||||
{{ rrset::rrset(
|
||||
rtype=rrset.rtype,
|
||||
ttl=rrset.ttl,
|
||||
data=rrset.data,
|
||||
record=record,
|
||||
zone=current_zone,
|
||||
lang=lang) }}
|
||||
{% endfor %}
|
||||
|
|
Loading…
Reference in a new issue