Compare commits
3 Commits
68d4071bb9
...
19c334a974
Author | SHA1 | Date | |
---|---|---|---|
19c334a974 | |||
0770c00737 | |||
300914eb8d |
@ -51,7 +51,7 @@ flate2 = { version = "1.0.26", optional = true }
|
|||||||
default = ["debug_server"]
|
default = ["debug_server"]
|
||||||
debug_server = [
|
debug_server = [
|
||||||
"dep:hyper",
|
"dep:hyper",
|
||||||
"minify-html",
|
"dep:minify-html",
|
||||||
"dep:minify-js",
|
"dep:minify-js",
|
||||||
"dep:css-minify",
|
"dep:css-minify",
|
||||||
"dep:flate2",
|
"dep:flate2",
|
||||||
|
@ -61,6 +61,8 @@ async fn authenticate(
|
|||||||
return Ok(AuthResult::OutOfPorts);
|
return Ok(AuthResult::OutOfPorts);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
handler_metadata.port = Some(port);
|
||||||
|
|
||||||
// make sure the client is authenticated before opening any ports
|
// make sure the client is authenticated before opening any ports
|
||||||
if !authenticated {
|
if !authenticated {
|
||||||
let _ip = dyn_ip_update(&config.dyn_ip_server, number, pin, port).await?;
|
let _ip = dyn_ip_update(&config.dyn_ip_server, number, pin, port).await?;
|
||||||
@ -98,8 +100,6 @@ async fn authenticate(
|
|||||||
.or_default()
|
.or_default()
|
||||||
.new_state(PortStatus::Idle);
|
.new_state(PortStatus::Idle);
|
||||||
|
|
||||||
handler_metadata.port = Some(port);
|
|
||||||
|
|
||||||
break Ok(AuthResult::Success { port });
|
break Ok(AuthResult::Success { port });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
10
src/main.rs
10
src/main.rs
@ -284,10 +284,18 @@ async fn connection_handler(
|
|||||||
let mut port_handler = port_handler.lock().await;
|
let mut port_handler = port_handler.lock().await;
|
||||||
|
|
||||||
if let Some(port_state) = port_handler.port_state.get_mut(&port) {
|
if let Some(port_state) = port_handler.port_state.get_mut(&port) {
|
||||||
|
// the client is known. Mark is as disconnected
|
||||||
port_state.new_state(PortStatus::Disconnected);
|
port_state.new_state(PortStatus::Disconnected);
|
||||||
port_handler.register_update();
|
} else {
|
||||||
|
// the client is not known. Free its port for realloction
|
||||||
|
assert!(
|
||||||
|
port_handler.free_ports.insert(port),
|
||||||
|
"tried to free up a port that was not allocted"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
port_handler.register_update();
|
||||||
|
|
||||||
if let Some(listener) = handler_metadata.listener.take() {
|
if let Some(listener) = handler_metadata.listener.take() {
|
||||||
port_handler.start_rejector(
|
port_handler.start_rejector(
|
||||||
port,
|
port,
|
||||||
|
12
src/ports.rs
12
src/ports.rs
@ -1,6 +1,6 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::{BTreeSet, HashMap, HashSet},
|
collections::{BTreeSet, HashMap, HashSet},
|
||||||
fmt::{Debug, Display},
|
fmt::Debug,
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{BufReader, BufWriter},
|
io::{BufReader, BufWriter},
|
||||||
ops::RangeInclusive,
|
ops::RangeInclusive,
|
||||||
@ -41,7 +41,7 @@ pub struct PortHandler {
|
|||||||
allowed_ports: AllowedList,
|
allowed_ports: AllowedList,
|
||||||
|
|
||||||
#[serde(skip)]
|
#[serde(skip)]
|
||||||
free_ports: HashSet<Port>,
|
pub free_ports: HashSet<Port>,
|
||||||
|
|
||||||
errored_ports: BTreeSet<(UnixTimestamp, Port)>,
|
errored_ports: BTreeSet<(UnixTimestamp, Port)>,
|
||||||
allocated_ports: HashMap<Number, Port>,
|
allocated_ports: HashMap<Number, Port>,
|
||||||
@ -103,14 +103,6 @@ pub async fn cache_daemon(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Hash, PartialEq, Eq)]
|
|
||||||
struct DisplayAsDebug<T: Display>(T);
|
|
||||||
impl<T: Display> Debug for DisplayAsDebug<T> {
|
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize)]
|
#[derive(Default, Serialize, Deserialize)]
|
||||||
pub struct PortState {
|
pub struct PortState {
|
||||||
#[serde(deserialize_with = "deserialize_last_change")]
|
#[serde(deserialize_with = "deserialize_last_change")]
|
||||||
|
437
web/main.js
437
web/main.js
@ -1,235 +1,252 @@
|
|||||||
window.onload = () => {
|
window.onload = () => {
|
||||||
const table_elem = document.getElementById("table");
|
const table_elem = document.getElementById("table");
|
||||||
const last_change = document.getElementById("last_change");
|
const last_change = document.getElementById("last_change");
|
||||||
const last_ping = document.getElementById("last_ping");
|
const last_ping = document.getElementById("last_ping");
|
||||||
const connected = document.getElementById("connected");
|
const connected = document.getElementById("connected");
|
||||||
const free_ports = document.getElementById("free_ports");
|
const free_ports = document.getElementById("free_ports");
|
||||||
|
|
||||||
const timeout_duration = 10*1000;
|
const timeout_duration = 10 * 1000;
|
||||||
const retry_timeout = 5*1000;
|
const retry_timeout = 5 * 1000;
|
||||||
|
|
||||||
let reconnect_timeout;
|
let reconnect_timeout;
|
||||||
let ping_timeout;
|
let ping_timeout;
|
||||||
let evtSource;
|
let evtSource;
|
||||||
let table = [];
|
let table = [];
|
||||||
|
|
||||||
let last_update = new Date();
|
let last_update = new Date();
|
||||||
|
|
||||||
let direction = -1;
|
let direction = -1;
|
||||||
let oldkey = "last_change";
|
let oldkey = "last_change";
|
||||||
|
|
||||||
table_elem.firstChild.firstChild.lastChild.className = "sort sort-down";
|
table_elem.firstChild.firstChild.lastChild.className = "sort sort-down";
|
||||||
|
|
||||||
|
let time_ago = (ms) => {
|
||||||
|
let value = ms / 1000;
|
||||||
|
// let prev = 0;
|
||||||
|
let unit = 0;
|
||||||
|
|
||||||
let time_ago = ms => {
|
let factors = [
|
||||||
|
[1, "Sekunde", "n"],
|
||||||
|
[60, "Minute", "n"],
|
||||||
|
[60, "Stunde", "n"],
|
||||||
|
[24, "Tag", "en"],
|
||||||
|
[7, "Woche", "n"],
|
||||||
|
[4.348214, "Monat", "en"],
|
||||||
|
[12, "Jahr", "en"],
|
||||||
|
];
|
||||||
|
|
||||||
let value = ms / 1000;
|
for (let i in factors) {
|
||||||
// let prev = 0;
|
let factor = factors[i][0];
|
||||||
let unit = 0;
|
let new_value = Math.floor(value / factor);
|
||||||
|
if (new_value == 0) break;
|
||||||
|
|
||||||
let factors = [
|
// prev = Math.floor(value % factor);
|
||||||
[1, 'Sekunde', 'n'],
|
value = new_value;
|
||||||
[60, 'Minute', 'n'],
|
unit = i;
|
||||||
[60, 'Stunde', 'n'],
|
}
|
||||||
[24, 'Tag', 'en'],
|
|
||||||
[7, 'Woche', 'n'],
|
|
||||||
[4.348214, 'Monat', 'en'],
|
|
||||||
[12, 'Jahr', 'en'],
|
|
||||||
];
|
|
||||||
|
|
||||||
for (let i in factors) {
|
let factor = factors[unit];
|
||||||
let factor = factors[i][0];
|
|
||||||
let new_value = Math.floor(value / factor);
|
|
||||||
if (new_value == 0) break;
|
|
||||||
|
|
||||||
// prev = Math.floor(value % factor);
|
return [value, factors[unit][1] + (value == 1 ? "" : factor[2])];
|
||||||
value = new_value;
|
};
|
||||||
unit = i;
|
|
||||||
|
sort = (element, key) => {
|
||||||
|
if (key == oldkey) {
|
||||||
|
direction *= -1;
|
||||||
|
} else {
|
||||||
|
oldkey = key;
|
||||||
|
direction = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let child of table_elem.firstChild.firstChild.children) {
|
||||||
|
child.className = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
element.className = `sort ${direction > 0 ? "sort-up" : "sort-down"}`;
|
||||||
|
|
||||||
|
do_sort(oldkey, direction);
|
||||||
|
};
|
||||||
|
|
||||||
|
let do_sort = (key, direction) => {
|
||||||
|
let is_number = !!~(["port", "number", "last_change"].indexOf(key));
|
||||||
|
|
||||||
|
table = table.sort((a, b) => (direction * (
|
||||||
|
is_number ? a[key] - b[key] : ("" + a[key]).localeCompare(b[key], "de-DE")
|
||||||
|
)));
|
||||||
|
|
||||||
|
populate_table();
|
||||||
|
};
|
||||||
|
|
||||||
|
let fmt = Intl.DateTimeFormat("de-DE", {
|
||||||
|
dateStyle: "medium",
|
||||||
|
timeStyle: "medium",
|
||||||
|
});
|
||||||
|
|
||||||
|
let format_date = (date) => fmt.format(date).replace(", ", " ");
|
||||||
|
let format_time = (date) => fmt.format(date).split(", ", 2)[1];
|
||||||
|
|
||||||
|
let populate_table = () => {
|
||||||
|
// clear everything except for the header row
|
||||||
|
while (table_elem.rows.length > 1) {
|
||||||
|
table_elem.deleteRow(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let row of table) {
|
||||||
|
let tr = table_elem.insertRow(-1);
|
||||||
|
|
||||||
|
let values = [
|
||||||
|
row.name === null ? "?" : row.name,
|
||||||
|
row.number,
|
||||||
|
row.port,
|
||||||
|
row.status,
|
||||||
|
time_ago(last_update - row.last_change),
|
||||||
|
];
|
||||||
|
|
||||||
|
let names = [
|
||||||
|
"name",
|
||||||
|
"number",
|
||||||
|
"port",
|
||||||
|
"status",
|
||||||
|
"last_change",
|
||||||
|
];
|
||||||
|
|
||||||
|
for (let i in values) {
|
||||||
|
let value = values[i];
|
||||||
|
let name = names[i];
|
||||||
|
|
||||||
|
let td = tr.insertCell(-1);
|
||||||
|
td.className = name;
|
||||||
|
|
||||||
|
if (name == "last_change") {
|
||||||
|
let [number, unit] = value;
|
||||||
|
|
||||||
|
let span = document.createElement("span");
|
||||||
|
// span.className = "value";
|
||||||
|
span.innerText = number;
|
||||||
|
td.appendChild(span);
|
||||||
|
|
||||||
|
span = document.createElement("span");
|
||||||
|
span.className = "unit";
|
||||||
|
span.innerText = unit;
|
||||||
|
td.appendChild(span);
|
||||||
|
} else {
|
||||||
|
td.innerText = value;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let factor = factors[unit];
|
let update_table = (data) => {
|
||||||
|
const allowed_ports = data.allowed_ports.map((x) => x.end - x.start + 1)
|
||||||
return [value, factors[unit][1] + (value == 1 ? "" : factor[2])];
|
.reduce((a, b) => a + b, 0);
|
||||||
|
free_ports.innerText = `Freie Ports: ${
|
||||||
|
allowed_ports - Object.keys(data.allocated_ports).length -
|
||||||
|
data.errored_ports.length
|
||||||
|
}`;
|
||||||
|
|
||||||
|
// last_change.innerHTML = `Letzte Änderung: ${format_date(new Date(+data.last_update * 1000))}`;
|
||||||
|
|
||||||
|
table = [];
|
||||||
|
|
||||||
|
for (let number in data.allocated_ports) {
|
||||||
|
let port = data.allocated_ports[number];
|
||||||
|
number = +number;
|
||||||
|
|
||||||
|
// allocated port has no state. This means that it is unknown and should not be displayed
|
||||||
|
if (data.port_state[port] == undefined) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
sort = (element, key) => {
|
let { status, last_change } = data.port_state[port];
|
||||||
if (key == oldkey) {
|
|
||||||
direction *= -1;
|
|
||||||
} else {
|
|
||||||
oldkey = key;
|
|
||||||
direction = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let child of table_elem.firstChild.firstChild.children) {
|
let rejector = data.rejectors[port] || null;
|
||||||
child.className = ""
|
|
||||||
};
|
|
||||||
|
|
||||||
element.className = `sort ${direction > 0 ? "sort-up" : "sort-down"}`;
|
if (rejector && rejector instanceof Array) {
|
||||||
|
rejector = rejector.map((x) => "0x" + x.toString(16).padStart(2, 0))
|
||||||
|
.join(" ");
|
||||||
|
}
|
||||||
|
|
||||||
do_sort(oldkey, direction);
|
last_change = new Date(last_change * 1000);
|
||||||
};
|
|
||||||
|
|
||||||
let do_sort = (key, direction) => {
|
let name = data.names[number] || null;
|
||||||
|
|
||||||
let is_number = !!~(["port", "number", "last_change"].indexOf(key));
|
switch (status) {
|
||||||
|
case "disconnected":
|
||||||
|
status = rejector ? `getrennt: ${rejector}` : "getrennt";
|
||||||
|
break;
|
||||||
|
case "idle":
|
||||||
|
status = "bereit";
|
||||||
|
break;
|
||||||
|
case "in_call":
|
||||||
|
status = "anruf";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
table = table.sort((a, b) => (direction * (
|
table.push({ port, number, status, last_change, rejector, name });
|
||||||
is_number ? a[key] - b[key] : ('' +a[key]).localeCompare(b[key], "de-DE")
|
|
||||||
)));
|
|
||||||
|
|
||||||
populate_table();
|
|
||||||
};
|
|
||||||
|
|
||||||
let fmt = Intl.DateTimeFormat('de-DE', { dateStyle: 'medium', timeStyle: 'medium' });
|
|
||||||
|
|
||||||
let format_date = date => fmt.format(date).replace(', ', ' ');
|
|
||||||
let format_time = date => fmt.format(date).split(', ', 2)[1];
|
|
||||||
|
|
||||||
let populate_table = () => {
|
|
||||||
// clear everything except for the header row
|
|
||||||
while(table_elem.rows.length > 1) {
|
|
||||||
table_elem.deleteRow(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let row of table) {
|
|
||||||
let tr = table_elem.insertRow(-1);
|
|
||||||
|
|
||||||
let values = [
|
|
||||||
row.name === null ? "?" : row.name,
|
|
||||||
row.number,
|
|
||||||
row.port,
|
|
||||||
row.status,
|
|
||||||
time_ago(last_update - row.last_change)
|
|
||||||
];
|
|
||||||
|
|
||||||
let names = [
|
|
||||||
"name",
|
|
||||||
"number",
|
|
||||||
"port",
|
|
||||||
"status",
|
|
||||||
"last_change"
|
|
||||||
];
|
|
||||||
|
|
||||||
for(let i in values) {
|
|
||||||
let value = values[i];
|
|
||||||
let name = names[i];
|
|
||||||
|
|
||||||
let td = tr.insertCell(-1);
|
|
||||||
td.className = name;
|
|
||||||
|
|
||||||
if (name == "last_change") {
|
|
||||||
let [number, unit] = value;
|
|
||||||
|
|
||||||
let span = document.createElement("span");
|
|
||||||
// span.className = "value";
|
|
||||||
span.innerText = number;
|
|
||||||
td.appendChild(span);
|
|
||||||
|
|
||||||
span = document.createElement("span");
|
|
||||||
span.className = "unit";
|
|
||||||
span.innerText = unit;
|
|
||||||
td.appendChild(span);
|
|
||||||
} else {
|
|
||||||
td.innerText = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let update_table = data => {
|
|
||||||
const allowed_ports = data.allowed_ports.map(x => x.end - x.start + 1).reduce((a,b) => a + b, 0);
|
|
||||||
free_ports.innerText = `Freie Ports: ${allowed_ports - Object.keys(data.allocated_ports).length - data.errored_ports.length}`;
|
|
||||||
|
|
||||||
// last_change.innerHTML = `Letzte Änderung: ${format_date(new Date(+data.last_update * 1000))}`;
|
|
||||||
|
|
||||||
table = [];
|
|
||||||
|
|
||||||
for(let number in data.allocated_ports) {
|
|
||||||
let port = data.allocated_ports[number];
|
|
||||||
number = +number;
|
|
||||||
let {status, last_change} = data.port_state[port];
|
|
||||||
|
|
||||||
let rejector = data.rejectors[port] || null;
|
|
||||||
|
|
||||||
if (rejector && rejector instanceof Array) {
|
|
||||||
rejector = rejector.map(x => "0x"+x.toString(16).padStart(2, 0)).join(" ")
|
|
||||||
}
|
|
||||||
|
|
||||||
last_change = new Date(last_change * 1000);
|
|
||||||
|
|
||||||
let name = data.names[number] || null;
|
|
||||||
|
|
||||||
switch(status) {
|
|
||||||
case "disconnected":
|
|
||||||
status = rejector ? `getrennt: ${rejector}` : "getrennt";
|
|
||||||
break;
|
|
||||||
case "idle":
|
|
||||||
status = "bereit";
|
|
||||||
break;
|
|
||||||
case "in_call":
|
|
||||||
status = "anruf";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.push({port, number, status, last_change, rejector, name})
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let [timestamp, port] of data.errored_ports) {
|
|
||||||
table.push({port, number: null, status: "Fehler", last_change: new Date(timestamp * 1000), rejector: null, name: ""})
|
|
||||||
}
|
|
||||||
|
|
||||||
do_sort(oldkey, direction);
|
|
||||||
|
|
||||||
populate_table();
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
let format_event = (event, method) => (method || format_date)(new Date(+event.data * 1000));
|
|
||||||
|
|
||||||
let display_disconnected;
|
|
||||||
|
|
||||||
let connect_event_source = () => {
|
|
||||||
|
|
||||||
clearTimeout(reconnect_timeout);
|
|
||||||
clearTimeout(ping_timeout);
|
|
||||||
ping_timeout = setTimeout(connect_event_source, timeout_duration);
|
|
||||||
|
|
||||||
evtSource && evtSource.close && evtSource.close();
|
|
||||||
|
|
||||||
evtSource = new EventSource("/events");
|
|
||||||
evtSource.addEventListener("change", event => {
|
|
||||||
last_ping.innerText = `Stand: ${format_event(event, format_time)}`;
|
|
||||||
|
|
||||||
last_update = new Date(+event.data * 1000);
|
|
||||||
|
|
||||||
fetch("/data")
|
|
||||||
.then(res => res.json())
|
|
||||||
.then(update_table);
|
|
||||||
});
|
|
||||||
|
|
||||||
evtSource.addEventListener("ping", event => {
|
|
||||||
clearTimeout(ping_timeout);
|
|
||||||
ping_timeout = setTimeout(connect_event_source, timeout_duration);
|
|
||||||
|
|
||||||
last_update = new Date(+event.data * 1000);
|
|
||||||
|
|
||||||
last_ping.innerText = `Stand: ${format_event(event, format_time)}`;
|
|
||||||
connected.className = "visible";
|
|
||||||
|
|
||||||
populate_table();
|
|
||||||
|
|
||||||
clearTimeout(display_disconnected);
|
|
||||||
display_disconnected = setTimeout(() => connected.className = "hidden", 5000);
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
evtSource.onerror = () => {
|
|
||||||
clearTimeout(reconnect_timeout);
|
|
||||||
reconnect_timeout = setTimeout(connect_event_source, retry_timeout);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
connect_event_source();
|
for (let [timestamp, port] of data.errored_ports) {
|
||||||
|
table.push({
|
||||||
|
port,
|
||||||
|
number: null,
|
||||||
|
status: "Fehler",
|
||||||
|
last_change: new Date(timestamp * 1000),
|
||||||
|
rejector: null,
|
||||||
|
name: "",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
};
|
do_sort(oldkey, direction);
|
||||||
|
|
||||||
|
populate_table();
|
||||||
|
};
|
||||||
|
|
||||||
|
let format_event = (event, method) =>
|
||||||
|
(method || format_date)(new Date(+event.data * 1000));
|
||||||
|
|
||||||
|
let display_disconnected;
|
||||||
|
|
||||||
|
let connect_event_source = () => {
|
||||||
|
clearTimeout(reconnect_timeout);
|
||||||
|
clearTimeout(ping_timeout);
|
||||||
|
ping_timeout = setTimeout(connect_event_source, timeout_duration);
|
||||||
|
|
||||||
|
evtSource && evtSource.close && evtSource.close();
|
||||||
|
|
||||||
|
evtSource = new EventSource("/events");
|
||||||
|
evtSource.addEventListener("change", (event) => {
|
||||||
|
last_ping.innerText = `Stand: ${format_event(event, format_time)}`;
|
||||||
|
|
||||||
|
last_update = new Date(+event.data * 1000);
|
||||||
|
|
||||||
|
fetch("/data")
|
||||||
|
.then((res) => res.json())
|
||||||
|
.then(update_table);
|
||||||
|
});
|
||||||
|
|
||||||
|
evtSource.addEventListener("ping", (event) => {
|
||||||
|
clearTimeout(ping_timeout);
|
||||||
|
ping_timeout = setTimeout(connect_event_source, timeout_duration);
|
||||||
|
|
||||||
|
last_update = new Date(+event.data * 1000);
|
||||||
|
|
||||||
|
last_ping.innerText = `Stand: ${format_event(event, format_time)}`;
|
||||||
|
connected.className = "visible";
|
||||||
|
|
||||||
|
populate_table();
|
||||||
|
|
||||||
|
clearTimeout(display_disconnected);
|
||||||
|
display_disconnected = setTimeout(
|
||||||
|
() => connected.className = "hidden",
|
||||||
|
5000,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
evtSource.onerror = () => {
|
||||||
|
clearTimeout(reconnect_timeout);
|
||||||
|
reconnect_timeout = setTimeout(connect_event_source, retry_timeout);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
connect_event_source();
|
||||||
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user