@@ -11,8 +11,8 @@ Canonical host registry. Hosts are identified by full DNS name in `fqdn`. |
||
| 11 | 11 |
| `fqdn` | `TEXT` | no | none | Canonical full host name. Primary key. | |
| 12 | 12 |
| `legacy_id` | `TEXT` | no | none | Short ID used by the existing UI/API. Unique. | |
| 13 | 13 |
| `status` | `TEXT` | no | `'active'` | Host lifecycle state, currently `active`, `planned`, or `retired`. | |
| 14 |
-| `hosts_ip` | `TEXT` | no | `''` | IP used for `/etc/hosts` on jumper. | |
|
| 15 |
-| `dns_ip` | `TEXT` | no | `''` | IP published to clients through local DNS. | |
|
| 14 |
+| `hosts_ip` | `TEXT` | no | `''` | Legacy compatibility column. The current app model keeps one canonical routable IP and mirrors it here. | |
|
| 15 |
+| `dns_ip` | `TEXT` | no | `''` | Canonical routable IP for the host. The current UI/API expose this as a single `ip` field. | |
|
| 16 | 16 |
| `monitoring` | `TEXT` | no | `'pending'` | Monitoring state. | |
| 17 | 17 |
| `notes` | `TEXT` | no | `''` | Operator notes. | |
| 18 | 18 |
| `created_at` | `TEXT` | no | none | ISO UTC creation timestamp. | |
@@ -30,6 +30,7 @@ Stores virtual hosts separately from physical/logical hosts so a vhost can be mo |
||
| 30 | 30 |
## Rules |
| 31 | 31 |
|
| 32 | 32 |
- Moving a vhost means updating `host_fqdn`. |
| 33 |
+- The `certificate_id` binding stays on the vhost row when the vhost moves to another host. |
|
| 33 | 34 |
- Retiring a vhost means setting `status = 'retired'`; the row remains. |
| 34 | 35 |
|
| 35 | 36 |
## Definition |
@@ -2,6 +2,22 @@ |
||
| 2 | 2 |
|
| 3 | 3 |
Decizii despre autentificare, OTP, sesiuni, formulare si compatibilitate cu password managere/autofill. |
| 4 | 4 |
|
| 5 |
+## 2026-06-09 - Verificare sesiune inainte de fluxuri cu save |
|
| 6 |
+ |
|
| 7 |
+Regula de development: orice operatie UI care poate ajunge la `save`/`submit` trebuie sa verifice starea de autentificare inainte ca operatorul sa inceapa editarea si din nou imediat inainte de salvare. Aceeasi regula se aplica la schimbarea taburilor/sectiunilor, pentru ca navigarea poate declansa incarcari sau actiuni protejate. |
|
| 8 |
+ |
|
| 9 |
+Comportamentul asteptat: |
|
| 10 |
+ |
|
| 11 |
+- daca sesiunea este valida, fluxul continua normal |
|
| 12 |
+- daca sesiunea lipseste sau a expirat, aplicatia revine coerent la login |
|
| 13 |
+- datele deja introduse intr-un formular nu trebuie resetate doar pentru ca autentificarea a expirat inainte de save |
|
| 14 |
+ |
|
| 15 |
+Scop: |
|
| 16 |
+ |
|
| 17 |
+- evitarea pierderii modificarilor locale din formulare |
|
| 18 |
+- separarea clara intre erori de validare si sesiune expirata |
|
| 19 |
+- evitarea operatiunilor partiale pe endpoint-uri protejate |
|
| 20 |
+ |
|
| 5 | 21 |
## 2026-06-06 - OTP Login Keeps a Password-Manager-Friendly Form Shape |
| 6 | 22 |
|
| 7 | 23 |
Observatie: unele password managere si autofill-uri mobile nu initiau corect pe login-ul Madagascar Local Authority, desi completarea mergea pe o pagina similara din PBX management. |
@@ -153,6 +153,16 @@ Primul WO curent este pentru retragerea numelor locale `pmx.*`/`pbs.*` create is |
||
| 153 | 153 |
|
| 154 | 154 |
`madagascar.xdev.ro` este domeniul implicit al rețelei interne. Hosturile sunt identificate în baza de date prin FQDN complet, iar UI-ul păstrează temporar `id` ca identificator compatibil. |
| 155 | 155 |
|
| 156 |
+Modelul curent de adresare păstrează un singur IP canonic per host. UI-ul și API-ul expun acest câmp ca `ip`, iar store-ul runtime îl mapează în coloanele istorice SQLite doar pentru compatibilitate internă. |
|
| 157 |
+ |
|
| 158 |
+Modelul curent de naming separă explicit: |
|
| 159 |
+ |
|
| 160 |
+- `fqdn` — numele canonic al hostului |
|
| 161 |
+- `aliases` — nume suplimentare atașate hostului, care pot fi adăugate sau șterse |
|
| 162 |
+- `vhosts` — nume virtuale servite de host, care pot fi adăugate, șterse sau mutate pe alt host |
|
| 163 |
+ |
|
| 164 |
+Mutarea unui vhost înseamnă schimbarea `host_fqdn` în tabelul `vhosts`; legătura `certificate_id` de pe rândul vhostului rămâne atașată vhostului pe durata mutării. |
|
| 165 |
+ |
|
| 156 | 166 |
Pentru orice nume `*.madagascar.xdev.ro`, aplicația derivă automat aliasul scurt prin eliminarea sufixului `.madagascar.xdev.ro`. |
| 157 | 167 |
|
| 158 | 168 |
Exemple: |
@@ -28,7 +28,7 @@ Implementarea versionată este: |
||
| 28 | 28 |
|--------|-----| |
| 29 | 29 |
| `var/host-manager.sqlite` | sursa de adevăr runtime pentru registry și Work Orders | |
| 30 | 30 |
| `config/hosts.yaml` | seed/snapshot export pentru hosturi și FQDN-uri canonice | |
| 31 |
-| `config/local-hosts.tsv` | manifest DNS generat, cu aliasuri scurte derivate | |
|
| 31 |
+| `config/local-hosts.tsv` | manifest DNS generat, cu un singur IP canonic pe host și aliasuri scurte derivate | |
|
| 32 | 32 |
| `scripts/sync_local_hosts.sh` | generează și sincronizează `/etc/hosts`, `cloaking-rules.txt` și `/ip dns static` | |
| 33 | 33 |
|
| 34 | 34 |
`madagascar.xdev.ro` este domeniul implicit. Pentru orice nume `*.madagascar.xdev.ro`, aliasul scurt este derivat automat. De exemplu, `autonas01.madagascar.xdev.ro` publică și `autonas01`, iar `pmx.baobab.madagascar.xdev.ro` publică și `pmx.baobab`. Aliasurile derivate nu se declară separat în registry. |
@@ -121,12 +121,16 @@ dig @192.168.2.2 nohost.madagascar.xdev.ro +short |
||
| 121 | 121 |
|
| 122 | 122 |
Comenzile negative de mai sus trebuie să întoarcă output gol, iar cu `+comments` statusul trebuie să fie `NXDOMAIN`. |
| 123 | 123 |
|
| 124 |
+## Politica de adresare |
|
| 125 |
+ |
|
| 126 |
+Registry-ul de cluster publică un singur IP rutabil pentru fiecare host. Micromanagement-ul local, inclusiv orice intrări `127.0.0.1 localhost localhost.localdomain` sau override-uri locale pe host, nu se administrează din Madagascar Local Authority. |
|
| 127 |
+ |
|
| 124 | 128 |
## Hosturi speciale — excepții |
| 125 | 129 |
|
| 126 | 130 |
| Hostname | /etc/hosts pe jumper | cloaking-rules | Motiv | |
| 127 | 131 |
|----------|------------------------|----------------|-------| |
| 128 | 132 |
| `mazeri.madagascar.xdev.ro` | 192.168.2.102 | 192.168.2.102 | Mașina Debian la .102, separată de jumper chiar dacă Exim folosește acest hostname | |
| 129 |
-| `jumper.madagascar.xdev.ro` | 127.0.0.1 | 192.168.2.100 | Jumper se referă la sine prin loopback (necesar pentru Exim local delivery); clienții LAN primesc IP-ul real | |
|
| 133 |
+| `jumper.madagascar.xdev.ro` | 192.168.2.100 | 192.168.2.100 | Registry-ul publică doar adresa rutabilă; orice comportament local special rămâne responsabilitatea hostului | |
|
| 130 | 134 |
|
| 131 | 135 |
## Hosturi existente |
| 132 | 136 |
|
@@ -134,7 +138,7 @@ Comenzile negative de mai sus trebuie să întoarcă output gol, iar cu `+commen |
||
| 134 | 138 |
|----------|----| |
| 135 | 139 |
| `baobab.madagascar.xdev.ro` | 192.168.10.91 | |
| 136 | 140 |
| `mazeri.madagascar.xdev.ro` | 192.168.2.102 | |
| 137 |
-| `jumper.madagascar.xdev.ro` | 192.168.2.100 (127.0.0.1 local) | |
|
| 141 |
+| `jumper.madagascar.xdev.ro` | 192.168.2.100 | |
|
| 138 | 142 |
| `hosts.madagascar.xdev.ro` | 192.168.2.100 (vhost nginx pe jumper) | |
| 139 | 143 |
| `zabbix.madagascar.xdev.ro` | 192.168.2.107 | |
| 140 | 144 |
| `toltec.madagascar.xdev.ro` | 192.168.2.103 | |
@@ -30,6 +30,7 @@ Operational rules: |
||
| 30 | 30 |
- Perl from the distribution and core/distribution modules are allowed. |
| 31 | 31 |
- CPAN modules are allowed only after requesting an audit and RPM packaging for the local audited repository. |
| 32 | 32 |
- Secrets live outside git, mainly under `/etc/xdev/host-manager.env` on jumper. |
| 33 |
+- UI flows that can lead to a save must verify authentication before the user starts editing, before tab/section changes, and again before submit/save. If authentication is missing or expired, return to login without discarding in-progress form data. |
|
| 33 | 34 |
|
| 34 | 35 |
Before code changes: |
| 35 | 36 |
|
@@ -10,8 +10,7 @@ policy: |
||
| 10 | 10 |
hosts: |
| 11 | 11 |
- id: "baobab" |
| 12 | 12 |
status: "active" |
| 13 |
- hosts_ip: "192.168.10.91" |
|
| 14 |
- dns_ip: "192.168.10.91" |
|
| 13 |
+ ip: "192.168.10.91" |
|
| 15 | 14 |
names: |
| 16 | 15 |
- "baobab.madagascar.xdev.ro" |
| 17 | 16 |
- "pmx.baobab.madagascar.xdev.ro" |
@@ -25,8 +24,7 @@ hosts: |
||
| 25 | 24 |
notes: "Service DNS uses thunderbridge." |
| 26 | 25 |
- id: "ebony" |
| 27 | 26 |
status: "active" |
| 28 |
- hosts_ip: "192.168.10.92" |
|
| 29 |
- dns_ip: "192.168.10.92" |
|
| 27 |
+ ip: "192.168.10.92" |
|
| 30 | 28 |
names: |
| 31 | 29 |
- "ebony.madagascar.xdev.ro" |
| 32 | 30 |
- "pmx.ebony.madagascar.xdev.ro" |
@@ -40,8 +38,7 @@ hosts: |
||
| 40 | 38 |
notes: "Service DNS uses thunderbridge." |
| 41 | 39 |
- id: "tapia" |
| 42 | 40 |
status: "active" |
| 43 |
- hosts_ip: "192.168.10.93" |
|
| 44 |
- dns_ip: "192.168.10.93" |
|
| 41 |
+ ip: "192.168.10.93" |
|
| 45 | 42 |
names: |
| 46 | 43 |
- "tapia.madagascar.xdev.ro" |
| 47 | 44 |
- "pmx.tapia.madagascar.xdev.ro" |
@@ -55,8 +52,7 @@ hosts: |
||
| 55 | 52 |
notes: "Service DNS uses thunderbridge." |
| 56 | 53 |
- id: "autonas01" |
| 57 | 54 |
status: "active" |
| 58 |
- hosts_ip: "192.168.10.21" |
|
| 59 |
- dns_ip: "192.168.10.21" |
|
| 55 |
+ ip: "192.168.10.21" |
|
| 60 | 56 |
names: |
| 61 | 57 |
- "autonas01.madagascar.xdev.ro" |
| 62 | 58 |
roles: |
@@ -67,8 +63,7 @@ hosts: |
||
| 67 | 63 |
notes: "" |
| 68 | 64 |
- id: "autonas02" |
| 69 | 65 |
status: "active" |
| 70 |
- hosts_ip: "192.168.10.22" |
|
| 71 |
- dns_ip: "192.168.10.22" |
|
| 66 |
+ ip: "192.168.10.22" |
|
| 72 | 67 |
names: |
| 73 | 68 |
- "autonas02.madagascar.xdev.ro" |
| 74 | 69 |
roles: |
@@ -79,8 +74,7 @@ hosts: |
||
| 79 | 74 |
notes: "" |
| 80 | 75 |
- id: "anjothibe" |
| 81 | 76 |
status: "active" |
| 82 |
- hosts_ip: "192.168.2.95" |
|
| 83 |
- dns_ip: "192.168.2.95" |
|
| 77 |
+ ip: "192.168.2.95" |
|
| 84 | 78 |
names: |
| 85 | 79 |
- "anjothibe.madagascar.xdev.ro" |
| 86 | 80 |
- "pbs.anjothibe.madagascar.xdev.ro" |
@@ -94,8 +88,7 @@ hosts: |
||
| 94 | 88 |
notes: "" |
| 95 | 89 |
- id: "andrafiabe" |
| 96 | 90 |
status: "active" |
| 97 |
- hosts_ip: "192.168.2.96" |
|
| 98 |
- dns_ip: "192.168.2.96" |
|
| 91 |
+ ip: "192.168.2.96" |
|
| 99 | 92 |
names: |
| 100 | 93 |
- "andrafiabe.madagascar.xdev.ro" |
| 101 | 94 |
- "pbs.andrafiabe.madagascar.xdev.ro" |
@@ -109,8 +102,7 @@ hosts: |
||
| 109 | 102 |
notes: "" |
| 110 | 103 |
- id: "mazeri" |
| 111 | 104 |
status: "active" |
| 112 |
- hosts_ip: "192.168.2.102" |
|
| 113 |
- dns_ip: "192.168.2.102" |
|
| 105 |
+ ip: "192.168.2.102" |
|
| 114 | 106 |
names: |
| 115 | 107 |
- "mazeri.madagascar.xdev.ro" |
| 116 | 108 |
roles: |
@@ -122,8 +114,7 @@ hosts: |
||
| 122 | 114 |
notes: "" |
| 123 | 115 |
- id: "toltec" |
| 124 | 116 |
status: "active" |
| 125 |
- hosts_ip: "192.168.2.103" |
|
| 126 |
- dns_ip: "192.168.2.103" |
|
| 117 |
+ ip: "192.168.2.103" |
|
| 127 | 118 |
names: |
| 128 | 119 |
- "toltec.madagascar.xdev.ro" |
| 129 | 120 |
roles: |
@@ -135,8 +126,7 @@ hosts: |
||
| 135 | 126 |
notes: "" |
| 136 | 127 |
- id: "zabbix" |
| 137 | 128 |
status: "active" |
| 138 |
- hosts_ip: "192.168.2.107" |
|
| 139 |
- dns_ip: "192.168.2.107" |
|
| 129 |
+ ip: "192.168.2.107" |
|
| 140 | 130 |
names: |
| 141 | 131 |
- "zabbix.madagascar.xdev.ro" |
| 142 | 132 |
roles: |
@@ -148,8 +138,7 @@ hosts: |
||
| 148 | 138 |
notes: "" |
| 149 | 139 |
- id: "jumper" |
| 150 | 140 |
status: "active" |
| 151 |
- hosts_ip: "127.0.0.1" |
|
| 152 |
- dns_ip: "192.168.2.100" |
|
| 141 |
+ ip: "192.168.2.100" |
|
| 153 | 142 |
names: |
| 154 | 143 |
- "jumper.madagascar.xdev.ro" |
| 155 | 144 |
- "hosts.madagascar.xdev.ro" |
@@ -160,4 +149,4 @@ hosts: |
||
| 160 | 149 |
- "local-hosts.tsv" |
| 161 | 150 |
- "hosts-local.yaml" |
| 162 | 151 |
monitoring: "enabled" |
| 163 |
- notes: "Loopback only for local delivery on jumper; LAN DNS gets 192.168.2.100." |
|
| 152 |
+ notes: "Cluster registry publishes only the routable address." |
|
@@ -2,20 +2,20 @@ |
||
| 2 | 2 |
# Generated by scripts/host_manager.pl from config/hosts.yaml. |
| 3 | 3 |
# |
| 4 | 4 |
# Format: |
| 5 |
-# hosts_ip<TAB>dns_ip<TAB>name [aliases...] |
|
| 5 |
+# ip<TAB>name [aliases...] |
|
| 6 | 6 |
# |
| 7 | 7 |
# Priority rule: |
| 8 | 8 |
# - DHCP lease/reservation on 192.168.2.1 is canonical for LAN IP allocation. |
| 9 | 9 |
# - madagascar.json is canonical for cluster roles and service interfaces. |
| 10 | 10 |
# - This file publishes approved local DNS records derived from those sources. |
| 11 |
-192.168.2.96 192.168.2.96 andrafiabe.madagascar.xdev.ro pbs.andrafiabe.madagascar.xdev.ro andrafiabe pbs.andrafiabe |
|
| 12 |
-192.168.2.95 192.168.2.95 anjothibe.madagascar.xdev.ro pbs.anjothibe.madagascar.xdev.ro anjothibe pbs.anjothibe |
|
| 13 |
-192.168.10.21 192.168.10.21 autonas01.madagascar.xdev.ro autonas01 |
|
| 14 |
-192.168.10.22 192.168.10.22 autonas02.madagascar.xdev.ro autonas02 |
|
| 15 |
-192.168.10.91 192.168.10.91 baobab.madagascar.xdev.ro pmx.baobab.madagascar.xdev.ro baobab pmx.baobab |
|
| 16 |
-192.168.10.92 192.168.10.92 ebony.madagascar.xdev.ro pmx.ebony.madagascar.xdev.ro ebony pmx.ebony |
|
| 17 |
-127.0.0.1 192.168.2.100 jumper.madagascar.xdev.ro hosts.madagascar.xdev.ro jumper hosts |
|
| 18 |
-192.168.2.102 192.168.2.102 mazeri.madagascar.xdev.ro mazeri |
|
| 19 |
-192.168.10.93 192.168.10.93 tapia.madagascar.xdev.ro pmx.tapia.madagascar.xdev.ro tapia pmx.tapia |
|
| 20 |
-192.168.2.103 192.168.2.103 toltec.madagascar.xdev.ro toltec |
|
| 21 |
-192.168.2.107 192.168.2.107 zabbix.madagascar.xdev.ro zabbix |
|
| 11 |
+192.168.2.96 andrafiabe.madagascar.xdev.ro pbs.andrafiabe.madagascar.xdev.ro andrafiabe pbs.andrafiabe |
|
| 12 |
+192.168.2.95 anjothibe.madagascar.xdev.ro pbs.anjothibe.madagascar.xdev.ro anjothibe pbs.anjothibe |
|
| 13 |
+192.168.10.21 autonas01.madagascar.xdev.ro autonas01 |
|
| 14 |
+192.168.10.22 autonas02.madagascar.xdev.ro autonas02 |
|
| 15 |
+192.168.10.91 baobab.madagascar.xdev.ro pmx.baobab.madagascar.xdev.ro baobab pmx.baobab |
|
| 16 |
+192.168.10.92 ebony.madagascar.xdev.ro pmx.ebony.madagascar.xdev.ro ebony pmx.ebony |
|
| 17 |
+192.168.2.100 jumper.madagascar.xdev.ro hosts.madagascar.xdev.ro jumper hosts |
|
| 18 |
+192.168.2.102 mazeri.madagascar.xdev.ro mazeri |
|
| 19 |
+192.168.10.93 tapia.madagascar.xdev.ro pmx.tapia.madagascar.xdev.ro tapia pmx.tapia |
|
| 20 |
+192.168.2.103 toltec.madagascar.xdev.ro toltec |
|
| 21 |
+192.168.2.107 zabbix.madagascar.xdev.ro zabbix |
|
@@ -224,7 +224,7 @@ sub handle_client {
|
||
| 224 | 224 |
|
| 225 | 225 |
sub app_page_path {
|
| 226 | 226 |
my ($path) = @_; |
| 227 |
- return $path =~ m{\A/(?:|overview|hosts|dns|work-orders|ca|debug)\z};
|
|
| 227 |
+ return $path =~ m{\A/(?:|overview|hosts|vhosts|dns|work-orders|ca|debug)\z};
|
|
| 228 | 228 |
} |
| 229 | 229 |
|
| 230 | 230 |
sub load_registry {
|
@@ -363,9 +363,11 @@ sub apply_work_order {
|
||
| 363 | 363 |
my $removed = 0; |
| 364 | 364 |
for my $host (@{ $registry->{hosts} || [] }) {
|
| 365 | 365 |
next unless ($host->{id} || '') eq $host_id;
|
| 366 |
- my @kept = grep { $_ ne $name } @{ $host->{names} || [] };
|
|
| 367 |
- $removed = @kept != @{ $host->{names} || [] };
|
|
| 368 |
- $host->{names} = \@kept;
|
|
| 366 |
+ my @kept_aliases = grep { $_ ne $name } declared_alias_names($host);
|
|
| 367 |
+ my @kept_vhosts = grep { $_ ne $name } declared_vhost_names($host);
|
|
| 368 |
+ $removed = (@kept_aliases != @{ $host->{aliases} || [] }) || (@kept_vhosts != @{ $host->{vhosts} || [] });
|
|
| 369 |
+ $host->{aliases} = \@kept_aliases;
|
|
| 370 |
+ $host->{vhosts} = \@kept_vhosts;
|
|
| 369 | 371 |
last; |
| 370 | 372 |
} |
| 371 | 373 |
push @results, {
|
@@ -385,6 +387,7 @@ sub registry_payload {
|
||
| 385 | 387 |
my ($registry) = @_; |
| 386 | 388 |
my $problems = analyze_hosts($registry->{hosts});
|
| 387 | 389 |
my @hosts = map { host_payload($_) } @{ $registry->{hosts} };
|
| 390 |
+ my $vhost_count = sum(map { scalar declared_vhost_names($_) } @{ $registry->{hosts} });
|
|
| 388 | 391 |
return {
|
| 389 | 392 |
version => $registry->{version},
|
| 390 | 393 |
updated_at => $registry->{updated_at},
|
@@ -393,6 +396,7 @@ sub registry_payload {
|
||
| 393 | 396 |
problems => $problems, |
| 394 | 397 |
counts => {
|
| 395 | 398 |
hosts => scalar @{ $registry->{hosts} },
|
| 399 |
+ vhosts => $vhost_count, |
|
| 396 | 400 |
problems => scalar @$problems, |
| 397 | 401 |
}, |
| 398 | 402 |
}; |
@@ -403,36 +407,47 @@ sub upsert_host {
|
||
| 403 | 407 |
my $id = clean_id($payload->{id} || '');
|
| 404 | 408 |
return send_json($client, 400, { error => 'invalid_id' }) unless $id;
|
| 405 | 409 |
|
| 406 |
- my $hosts_ip = clean_scalar($payload->{hosts_ip} || '');
|
|
| 407 |
- my $dns_ip = clean_scalar($payload->{dns_ip} || '');
|
|
| 408 |
- return send_json($client, 400, { error => 'missing_ip' }) unless $hosts_ip && $dns_ip;
|
|
| 410 |
+ my $ip = canonical_ip($payload); |
|
| 411 |
+ return send_json($client, 400, { error => 'missing_ip' }) unless $ip;
|
|
| 409 | 412 |
|
| 410 |
- my @names = remove_derived_names(clean_list($payload->{names}));
|
|
| 411 |
- return send_json($client, 400, { error => 'missing_names' }) unless @names;
|
|
| 413 |
+ my $fqdn = canonical_host_fqdn($payload); |
|
| 414 |
+ return send_json($client, 400, { error => 'missing_fqdn' }) unless $fqdn;
|
|
| 415 |
+ my @aliases = clean_alias_names($payload); |
|
| 416 |
+ my @vhosts = clean_vhost_names($payload); |
|
| 412 | 417 |
|
| 413 | 418 |
my $registry = load_registry(); |
| 414 | 419 |
my %host = ( |
| 415 | 420 |
id => $id, |
| 421 |
+ fqdn => $fqdn, |
|
| 416 | 422 |
status => clean_scalar($payload->{status} || 'active'),
|
| 417 |
- hosts_ip => $hosts_ip, |
|
| 418 |
- dns_ip => $dns_ip, |
|
| 419 |
- names => \@names, |
|
| 423 |
+ ip => $ip, |
|
| 424 |
+ aliases => \@aliases, |
|
| 425 |
+ vhosts => \@vhosts, |
|
| 420 | 426 |
roles => [ clean_list($payload->{roles}) ],
|
| 421 | 427 |
sources => [ clean_list($payload->{sources}) ],
|
| 422 | 428 |
monitoring => clean_scalar($payload->{monitoring} || 'pending'),
|
| 423 | 429 |
notes => clean_scalar($payload->{notes} || ''),
|
| 424 | 430 |
); |
| 425 | 431 |
|
| 426 |
- my $replaced = 0; |
|
| 427 |
- for my $i (0 .. $#{ $registry->{hosts} }) {
|
|
| 428 |
- if ($registry->{hosts}->[$i]{id} eq $id) {
|
|
| 429 |
- $registry->{hosts}->[$i] = \%host;
|
|
| 430 |
- $replaced = 1; |
|
| 431 |
- last; |
|
| 432 |
+ my $response = eval {
|
|
| 433 |
+ my $replaced = 0; |
|
| 434 |
+ for my $i (0 .. $#{ $registry->{hosts} }) {
|
|
| 435 |
+ if ($registry->{hosts}->[$i]{id} eq $id) {
|
|
| 436 |
+ $registry->{hosts}->[$i] = \%host;
|
|
| 437 |
+ $replaced = 1; |
|
| 438 |
+ last; |
|
| 439 |
+ } |
|
| 432 | 440 |
} |
| 441 |
+ push @{ $registry->{hosts} }, \%host unless $replaced;
|
|
| 442 |
+ save_registry($registry); |
|
| 443 |
+ 1; |
|
| 444 |
+ }; |
|
| 445 |
+ if (!$response) {
|
|
| 446 |
+ my $err = $@ || 'upsert_failed'; |
|
| 447 |
+ return send_json($client, 409, { error => 'alias_conflict', detail => clean_scalar($err) })
|
|
| 448 |
+ if $err =~ /alias_conflict:/; |
|
| 449 |
+ die $err; |
|
| 433 | 450 |
} |
| 434 |
- push @{ $registry->{hosts} }, \%host unless $replaced;
|
|
| 435 |
- save_registry($registry); |
|
| 436 | 451 |
return send_json($client, 200, { ok => json_bool(1), host => \%host });
|
| 437 | 452 |
} |
| 438 | 453 |
|
@@ -455,23 +470,23 @@ sub analyze_hosts {
|
||
| 455 | 470 |
my (%names, %ids); |
| 456 | 471 |
for my $host (@$hosts) {
|
| 457 | 472 |
push @problems, problem($host, 'duplicate-id', "Duplicate id $host->{id}") if $ids{ $host->{id} }++;
|
| 458 |
- my @fqdn = grep { /\.madagascar\.xdev\.ro$/ } @{ $host->{names} || [] };
|
|
| 459 |
- push @problems, problem($host, 'missing-fqdn', 'No madagascar.xdev.ro FQDN') unless @fqdn || ($host->{status} || '') ne 'active';
|
|
| 473 |
+ my $fqdn = canonical_host_fqdn($host); |
|
| 474 |
+ push @problems, problem($host, 'missing-fqdn', 'No madagascar.xdev.ro FQDN') unless ($fqdn =~ /\.madagascar\.xdev\.ro$/) || ($host->{status} || '') ne 'active';
|
|
| 475 |
+ my @declared = declared_dns_names($host); |
|
| 460 | 476 |
push @problems, problem($host, 'deprecated-vad-is', 'Deprecated vad.is.xdev.ro name present') |
| 461 |
- if grep { /\.vad\.is\.xdev\.ro$/ } @{ $host->{names} || [] };
|
|
| 477 |
+ if grep { /\.vad\.is\.xdev\.ro$/ } @declared;
|
|
| 462 | 478 |
push @problems, problem($host, 'legacy-prefix', 'Legacy prefix should be normalized out') |
| 463 |
- if grep { /^(is|vad|b)-/ } @{ $host->{names} || [] };
|
|
| 464 |
- for my $name (@{ $host->{names} || [] }) {
|
|
| 479 |
+ if grep { /^(is|vad|b)-/ } @declared;
|
|
| 480 |
+ for my $name (@declared) {
|
|
| 465 | 481 |
push @problems, problem($host, 'duplicate-name', "Duplicate name $name") if $names{$name}++;
|
| 466 | 482 |
} |
| 467 |
- my %declared = map { $_ => 1 } @{ $host->{names} || [] };
|
|
| 468 |
- for my $derived (derived_names($host)) {
|
|
| 483 |
+ my %declared = map { $_ => 1 } @declared;
|
|
| 484 |
+ for my $derived (derived_alias_names($host), derived_vhost_alias_names($host)) {
|
|
| 469 | 485 |
push @problems, problem($host, 'redundant-derived-name', "Name $derived is derived from madagascar.xdev.ro") |
| 470 | 486 |
if $declared{$derived};
|
| 471 | 487 |
} |
| 472 |
- if (($host->{hosts_ip} || '') ne ($host->{dns_ip} || '') && ($host->{hosts_ip} || '') ne '127.0.0.1') {
|
|
| 473 |
- push @problems, problem($host, 'split-ip', 'hosts_ip differs from dns_ip; check that this is intentional'); |
|
| 474 |
- } |
|
| 488 |
+ push @problems, problem($host, 'missing-ip', 'Host is missing a canonical routable IP') |
|
| 489 |
+ unless canonical_ip($host) || ($host->{status} || '') ne 'active';
|
|
| 475 | 490 |
} |
| 476 | 491 |
return \@problems; |
| 477 | 492 |
} |
@@ -479,27 +494,119 @@ sub analyze_hosts {
|
||
| 479 | 494 |
sub host_payload {
|
| 480 | 495 |
my ($host) = @_; |
| 481 | 496 |
my %copy = %$host; |
| 497 |
+ $copy{fqdn} = canonical_host_fqdn($host);
|
|
| 498 |
+ $copy{ip} = canonical_ip($host);
|
|
| 482 | 499 |
$copy{names} = [ effective_names($host) ];
|
| 483 |
- $copy{declared_names} = [ @{ $host->{names} || [] } ];
|
|
| 484 |
- $copy{derived_names} = [ derived_names($host) ];
|
|
| 500 |
+ $copy{declared_names} = [ declared_dns_names($host) ];
|
|
| 501 |
+ $copy{aliases} = [ declared_alias_names($host) ];
|
|
| 502 |
+ $copy{derived_aliases} = [ derived_alias_names($host) ];
|
|
| 503 |
+ $copy{vhosts} = [ declared_vhost_names($host) ];
|
|
| 504 |
+ $copy{derived_vhost_aliases} = [ derived_vhost_alias_names($host) ];
|
|
| 485 | 505 |
return \%copy; |
| 486 | 506 |
} |
| 487 | 507 |
|
| 488 | 508 |
sub effective_names {
|
| 489 | 509 |
my ($host) = @_; |
| 490 |
- my @names = @{ $host->{names} || [] };
|
|
| 491 |
- push @names, derived_names($host); |
|
| 510 |
+ my @names = declared_dns_names($host); |
|
| 511 |
+ push @names, derived_alias_names($host), derived_vhost_alias_names($host); |
|
| 492 | 512 |
return unique_preserve(@names); |
| 493 | 513 |
} |
| 494 | 514 |
|
| 495 |
-sub derived_names {
|
|
| 515 |
+sub declared_dns_names {
|
|
| 516 |
+ my ($host) = @_; |
|
| 517 |
+ my @names; |
|
| 518 |
+ my $fqdn = canonical_host_fqdn($host); |
|
| 519 |
+ push @names, $fqdn if length $fqdn; |
|
| 520 |
+ push @names, declared_alias_names($host); |
|
| 521 |
+ push @names, declared_vhost_names($host); |
|
| 522 |
+ return unique_preserve(@names); |
|
| 523 |
+} |
|
| 524 |
+ |
|
| 525 |
+sub declared_alias_names {
|
|
| 526 |
+ my ($host) = @_; |
|
| 527 |
+ return unique_preserve(map { normalize_dns_name($_) } @{ $host->{aliases} || [] });
|
|
| 528 |
+} |
|
| 529 |
+ |
|
| 530 |
+sub declared_vhost_names {
|
|
| 531 |
+ my ($host) = @_; |
|
| 532 |
+ return unique_preserve(map { normalize_dns_name($_) } @{ $host->{vhosts} || [] });
|
|
| 533 |
+} |
|
| 534 |
+ |
|
| 535 |
+sub declared_dns_names_legacy {
|
|
| 536 |
+ my ($host) = @_; |
|
| 537 |
+ return map { normalize_dns_name($_) } @{ $host->{names} || [] };
|
|
| 538 |
+} |
|
| 539 |
+ |
|
| 540 |
+sub split_legacy_names {
|
|
| 541 |
+ my ($id, $names) = @_; |
|
| 542 |
+ my $fallback = clean_id($id || ''); |
|
| 543 |
+ my (%result) = ( |
|
| 544 |
+ fqdn => '', |
|
| 545 |
+ aliases => [], |
|
| 546 |
+ vhosts => [], |
|
| 547 |
+ ); |
|
| 548 |
+ for my $name (map { normalize_dns_name($_) } @$names) {
|
|
| 549 |
+ next unless length $name; |
|
| 550 |
+ if (!$result{fqdn} && $name =~ /\.madagascar\.xdev\.ro\z/ && !name_is_vhost($name)) {
|
|
| 551 |
+ $result{fqdn} = $name;
|
|
| 552 |
+ next; |
|
| 553 |
+ } |
|
| 554 |
+ if (!$result{fqdn} && $name =~ /\./ && !name_is_vhost($name)) {
|
|
| 555 |
+ $result{fqdn} = $name;
|
|
| 556 |
+ next; |
|
| 557 |
+ } |
|
| 558 |
+ if (name_is_vhost($name)) {
|
|
| 559 |
+ push @{ $result{vhosts} }, $name;
|
|
| 560 |
+ } else {
|
|
| 561 |
+ push @{ $result{aliases} }, $name;
|
|
| 562 |
+ } |
|
| 563 |
+ } |
|
| 564 |
+ $result{fqdn} ||= $fallback ? "$fallback.madagascar.xdev.ro" : '';
|
|
| 565 |
+ $result{aliases} = [ unique_preserve(grep { $_ ne $result{fqdn} } @{ $result{aliases} }) ];
|
|
| 566 |
+ $result{vhosts} = [ unique_preserve(@{ $result{vhosts} }) ];
|
|
| 567 |
+ return \%result; |
|
| 568 |
+} |
|
| 569 |
+ |
|
| 570 |
+sub derived_alias_names {
|
|
| 496 | 571 |
my ($host) = @_; |
| 497 | 572 |
my @derived; |
| 498 |
- for my $name (@{ $host->{names} || [] }) {
|
|
| 499 |
- next unless $name =~ /^(.+)\.madagascar\.xdev\.ro$/; |
|
| 500 |
- push @derived, $1 if length $1; |
|
| 573 |
+ my $fqdn = canonical_host_fqdn($host); |
|
| 574 |
+ push @derived, short_alias_for_fqdn($fqdn) if length $fqdn; |
|
| 575 |
+ for my $name (declared_alias_names($host)) {
|
|
| 576 |
+ push @derived, short_alias_for_fqdn($name); |
|
| 577 |
+ } |
|
| 578 |
+ return unique_preserve(grep { length $_ } @derived);
|
|
| 579 |
+} |
|
| 580 |
+ |
|
| 581 |
+sub derived_vhost_alias_names {
|
|
| 582 |
+ my ($host) = @_; |
|
| 583 |
+ my @derived; |
|
| 584 |
+ for my $name (declared_vhost_names($host)) {
|
|
| 585 |
+ push @derived, short_alias_for_fqdn($name); |
|
| 501 | 586 |
} |
| 502 |
- return unique_preserve(@derived); |
|
| 587 |
+ return unique_preserve(grep { length $_ } @derived);
|
|
| 588 |
+} |
|
| 589 |
+ |
|
| 590 |
+sub clean_alias_names {
|
|
| 591 |
+ my ($payload) = @_; |
|
| 592 |
+ return clean_name_bucket($payload->{aliases})
|
|
| 593 |
+ if defined $payload->{aliases};
|
|
| 594 |
+ my @legacy = remove_derived_names(clean_list($payload->{names}));
|
|
| 595 |
+ return grep { !name_is_vhost($_) && $_ ne canonical_host_fqdn({ %$payload, names => \@legacy }) } @legacy;
|
|
| 596 |
+} |
|
| 597 |
+ |
|
| 598 |
+sub clean_vhost_names {
|
|
| 599 |
+ my ($payload) = @_; |
|
| 600 |
+ return clean_name_bucket($payload->{vhosts})
|
|
| 601 |
+ if defined $payload->{vhosts};
|
|
| 602 |
+ my @legacy = remove_derived_names(clean_list($payload->{names}));
|
|
| 603 |
+ return grep { name_is_vhost($_) } @legacy;
|
|
| 604 |
+} |
|
| 605 |
+ |
|
| 606 |
+sub clean_name_bucket {
|
|
| 607 |
+ my ($value) = @_; |
|
| 608 |
+ my @names = clean_list($value); |
|
| 609 |
+ return unique_preserve(map { normalize_dns_name($_) } remove_derived_names(@names));
|
|
| 503 | 610 |
} |
| 504 | 611 |
|
| 505 | 612 |
sub remove_derived_names {
|
@@ -518,6 +625,16 @@ sub unique_preserve {
|
||
| 518 | 625 |
return grep { !$seen{$_}++ } @values;
|
| 519 | 626 |
} |
| 520 | 627 |
|
| 628 |
+sub canonical_ip {
|
|
| 629 |
+ my ($host) = @_; |
|
| 630 |
+ return '' unless $host && ref($host) eq 'HASH'; |
|
| 631 |
+ for my $key (qw(ip dns_ip hosts_ip)) {
|
|
| 632 |
+ my $value = clean_scalar($host->{$key} || '');
|
|
| 633 |
+ return $value if length $value; |
|
| 634 |
+ } |
|
| 635 |
+ return ''; |
|
| 636 |
+} |
|
| 637 |
+ |
|
| 521 | 638 |
sub problem {
|
| 522 | 639 |
my ($host, $code, $message) = @_; |
| 523 | 640 |
return { host_id => $host->{id}, code => $code, message => $message };
|
@@ -529,7 +646,7 @@ sub render_local_hosts_tsv {
|
||
| 529 | 646 |
$out .= "# Generated by scripts/host_manager.pl from the runtime SQLite registry.\n"; |
| 530 | 647 |
$out .= "#\n"; |
| 531 | 648 |
$out .= "# Format:\n"; |
| 532 |
- $out .= "# hosts_ip<TAB>dns_ip<TAB>name [aliases...]\n"; |
|
| 649 |
+ $out .= "# ip<TAB>name [aliases...]\n"; |
|
| 533 | 650 |
$out .= "#\n"; |
| 534 | 651 |
$out .= "# Priority rule:\n"; |
| 535 | 652 |
$out .= "# - DHCP lease/reservation on 192.168.2.1 is canonical for LAN IP allocation.\n"; |
@@ -537,9 +654,11 @@ sub render_local_hosts_tsv {
|
||
| 537 | 654 |
$out .= "# - This file publishes approved local DNS records derived from those sources.\n"; |
| 538 | 655 |
for my $host (sort { $a->{id} cmp $b->{id} } @{ $registry->{hosts} }) {
|
| 539 | 656 |
next unless ($host->{status} || 'active') eq 'active';
|
| 657 |
+ my $ip = canonical_ip($host); |
|
| 658 |
+ next unless $ip; |
|
| 540 | 659 |
my @names = effective_names($host); |
| 541 | 660 |
next unless @names; |
| 542 |
- $out .= join("\t", $host->{hosts_ip}, $host->{dns_ip}, join(' ', @names)) . "\n";
|
|
| 661 |
+ $out .= join("\t", $ip, join(' ', @names)) . "\n";
|
|
| 543 | 662 |
} |
| 544 | 663 |
return $out; |
| 545 | 664 |
} |
@@ -554,10 +673,14 @@ sub render_monitoring {
|
||
| 554 | 673 |
push @hosts, {
|
| 555 | 674 |
id => $host->{id},
|
| 556 | 675 |
primary_name => $names[0], |
| 557 |
- address => $host->{dns_ip},
|
|
| 676 |
+ address => canonical_ip($host), |
|
| 558 | 677 |
aliases => \@names, |
| 559 |
- declared_names => [ @{ $host->{names} || [] } ],
|
|
| 560 |
- derived_names => [ derived_names($host) ], |
|
| 678 |
+ fqdn => canonical_host_fqdn($host), |
|
| 679 |
+ declared_names => [ declared_dns_names($host) ], |
|
| 680 |
+ aliases_declared => [ declared_alias_names($host) ], |
|
| 681 |
+ aliases_derived => [ derived_alias_names($host) ], |
|
| 682 |
+ vhosts_declared => [ declared_vhost_names($host) ], |
|
| 683 |
+ vhost_aliases_derived => [ derived_vhost_alias_names($host) ], |
|
| 561 | 684 |
roles => [ @{ $host->{roles} || [] } ],
|
| 562 | 685 |
monitoring => $host->{monitoring} || 'pending',
|
| 563 | 686 |
notes => $host->{notes} || '',
|
@@ -830,10 +953,11 @@ sub parse_hosts_yaml {
|
||
| 830 | 953 |
} elsif (($section || '') eq 'hosts' && $line =~ /^ - id:\s*(.+)$/) {
|
| 831 | 954 |
$current = {
|
| 832 | 955 |
id => yaml_unquote($1), |
| 956 |
+ fqdn => '', |
|
| 833 | 957 |
status => 'active', |
| 834 |
- hosts_ip => '', |
|
| 835 |
- dns_ip => '', |
|
| 836 |
- names => [], |
|
| 958 |
+ ip => '', |
|
| 959 |
+ aliases => [], |
|
| 960 |
+ vhosts => [], |
|
| 837 | 961 |
roles => [], |
| 838 | 962 |
sources => [], |
| 839 | 963 |
monitoring => 'pending', |
@@ -847,10 +971,33 @@ sub parse_hosts_yaml {
|
||
| 847 | 971 |
} elsif ($current && defined $list_key && $line =~ /^ -\s*(.+)$/) {
|
| 848 | 972 |
push @{ $current->{$list_key} }, yaml_unquote($1);
|
| 849 | 973 |
} elsif ($current && $line =~ /^ ([A-Za-z0-9_]+):\s*(.*)$/) {
|
| 850 |
- $current->{$1} = yaml_unquote($2);
|
|
| 974 |
+ my $key = $1; |
|
| 975 |
+ my $value = yaml_unquote($2); |
|
| 976 |
+ if ($key eq 'ip') {
|
|
| 977 |
+ $current->{ip} = $value;
|
|
| 978 |
+ } elsif ($key eq 'dns_ip' || $key eq 'hosts_ip') {
|
|
| 979 |
+ $current->{ip} ||= $value;
|
|
| 980 |
+ } elsif ($key eq 'fqdn') {
|
|
| 981 |
+ $current->{fqdn} = normalize_dns_name($value);
|
|
| 982 |
+ } elsif ($key eq 'names') {
|
|
| 983 |
+ # ignored here; legacy list is handled after parsing |
|
| 984 |
+ } else {
|
|
| 985 |
+ $current->{$key} = $value;
|
|
| 986 |
+ } |
|
| 851 | 987 |
$list_key = undef; |
| 852 | 988 |
} |
| 853 | 989 |
} |
| 990 |
+ for my $host (@{ $registry{hosts} }) {
|
|
| 991 |
+ my @legacy_names = @{ $host->{names} || [] };
|
|
| 992 |
+ if (@legacy_names) {
|
|
| 993 |
+ my $legacy = split_legacy_names($host->{id}, \@legacy_names);
|
|
| 994 |
+ $host->{fqdn} ||= $legacy->{fqdn};
|
|
| 995 |
+ $host->{aliases} = $legacy->{aliases} unless @{ $host->{aliases} || [] };
|
|
| 996 |
+ $host->{vhosts} = $legacy->{vhosts} unless @{ $host->{vhosts} || [] };
|
|
| 997 |
+ } |
|
| 998 |
+ delete $host->{names};
|
|
| 999 |
+ $host->{fqdn} ||= canonical_host_fqdn($host);
|
|
| 1000 |
+ } |
|
| 854 | 1001 |
return \%registry; |
| 855 | 1002 |
} |
| 856 | 1003 |
|
@@ -865,10 +1012,10 @@ sub render_hosts_yaml {
|
||
| 865 | 1012 |
$out .= "hosts:\n"; |
| 866 | 1013 |
for my $host (sort { $a->{id} cmp $b->{id} } @{ $registry->{hosts} || [] }) {
|
| 867 | 1014 |
$out .= " - id: " . yq($host->{id}) . "\n";
|
| 868 |
- for my $key (qw(status hosts_ip dns_ip)) {
|
|
| 869 |
- $out .= " $key: " . yq($host->{$key} || '') . "\n";
|
|
| 870 |
- } |
|
| 871 |
- for my $key (qw(names roles sources)) {
|
|
| 1015 |
+ $out .= " fqdn: " . yq(canonical_host_fqdn($host)) . "\n"; |
|
| 1016 |
+ $out .= " status: " . yq($host->{status} || '') . "\n";
|
|
| 1017 |
+ $out .= " ip: " . yq(canonical_ip($host)) . "\n"; |
|
| 1018 |
+ for my $key (qw(aliases vhosts roles sources)) {
|
|
| 872 | 1019 |
$out .= " $key:\n"; |
| 873 | 1020 |
for my $value (@{ $host->{$key} || [] }) {
|
| 874 | 1021 |
$out .= " - " . yq($value) . "\n"; |
@@ -1698,10 +1845,11 @@ sub load_registry_from_db {
|
||
| 1698 | 1845 |
my $fqdn = $row->{fqdn};
|
| 1699 | 1846 |
push @{ $registry->{hosts} }, {
|
| 1700 | 1847 |
id => $row->{legacy_id},
|
| 1848 |
+ fqdn => $fqdn, |
|
| 1701 | 1849 |
status => $row->{status},
|
| 1702 |
- hosts_ip => $row->{hosts_ip},
|
|
| 1703 |
- dns_ip => $row->{dns_ip},
|
|
| 1704 |
- names => [ active_names_for_host($dbh, $fqdn) ], |
|
| 1850 |
+ ip => canonical_ip($row), |
|
| 1851 |
+ aliases => [ active_aliases_for_host($dbh, $fqdn) ], |
|
| 1852 |
+ vhosts => [ active_vhosts_for_host($dbh, $fqdn) ], |
|
| 1705 | 1853 |
roles => [ active_values_for_host($dbh, 'host_roles', 'role', $fqdn) ], |
| 1706 | 1854 |
sources => [ active_values_for_host($dbh, 'host_sources', 'source', $fqdn) ], |
| 1707 | 1855 |
monitoring => $row->{monitoring},
|
@@ -1745,8 +1893,7 @@ sub upsert_host_to_db {
|
||
| 1745 | 1893 |
return '' unless $fqdn; |
| 1746 | 1894 |
my $legacy_id = clean_id($host->{id} || legacy_id_from_fqdn($fqdn));
|
| 1747 | 1895 |
my $status = clean_scalar($host->{status} || 'active');
|
| 1748 |
- my $hosts_ip = clean_scalar($host->{hosts_ip} || '');
|
|
| 1749 |
- my $dns_ip = clean_scalar($host->{dns_ip} || '');
|
|
| 1896 |
+ my $ip = canonical_ip($host); |
|
| 1750 | 1897 |
my $monitoring = clean_scalar($host->{monitoring} || 'pending');
|
| 1751 | 1898 |
my $notes = clean_scalar($host->{notes} || '');
|
| 1752 | 1899 |
|
@@ -1757,12 +1904,12 @@ sub upsert_host_to_db {
|
||
| 1757 | 1904 |
. 'hosts_ip = excluded.hosts_ip, dns_ip = excluded.dns_ip, monitoring = excluded.monitoring, ' |
| 1758 | 1905 |
. 'notes = excluded.notes, updated_at = excluded.updated_at', |
| 1759 | 1906 |
undef, |
| 1760 |
- $fqdn, $legacy_id, $status, $hosts_ip, $dns_ip, $monitoring, $notes, $now, $now, |
|
| 1907 |
+ $fqdn, $legacy_id, $status, $ip, $ip, $monitoring, $notes, $now, $now, |
|
| 1761 | 1908 |
); |
| 1762 | 1909 |
|
| 1763 | 1910 |
sync_host_values($dbh, 'host_roles', 'role', $fqdn, [ clean_list($host->{roles}) ]);
|
| 1764 | 1911 |
sync_host_values($dbh, 'host_sources', 'source', $fqdn, [ clean_list($host->{sources}) ]);
|
| 1765 |
- sync_host_names($dbh, $fqdn, [ clean_list($host->{names}) ]);
|
|
| 1912 |
+ sync_host_aliases_and_vhosts($dbh, $fqdn, [ declared_alias_names($host) ], [ declared_vhost_names($host) ]); |
|
| 1766 | 1913 |
return $fqdn; |
| 1767 | 1914 |
} |
| 1768 | 1915 |
|
@@ -1787,32 +1934,33 @@ sub sync_host_values {
|
||
| 1787 | 1934 |
} |
| 1788 | 1935 |
} |
| 1789 | 1936 |
|
| 1790 |
-sub sync_host_names {
|
|
| 1791 |
- my ($dbh, $fqdn, $names) = @_; |
|
| 1937 |
+sub sync_host_aliases_and_vhosts {
|
|
| 1938 |
+ my ($dbh, $fqdn, $aliases_in, $vhosts_in) = @_; |
|
| 1792 | 1939 |
my $now = iso_now(); |
| 1793 | 1940 |
my (%aliases, %vhosts); |
| 1794 | 1941 |
if (my $short = short_alias_for_fqdn($fqdn)) {
|
| 1795 | 1942 |
$aliases{$short} = 1;
|
| 1796 | 1943 |
upsert_alias_to_db($dbh, $fqdn, $short, 'derived', $now); |
| 1797 | 1944 |
} |
| 1798 |
- for my $name (@$names) {
|
|
| 1945 |
+ for my $name (@$aliases_in) {
|
|
| 1799 | 1946 |
$name = normalize_dns_name($name); |
| 1800 | 1947 |
next unless length $name; |
| 1801 | 1948 |
next if $name eq $fqdn; |
| 1802 |
- if (name_is_vhost($name)) {
|
|
| 1803 |
- $vhosts{$name} = 1;
|
|
| 1804 |
- upsert_vhost_to_db($dbh, $fqdn, $name, $now); |
|
| 1805 |
- if (my $short = short_alias_for_fqdn($name)) {
|
|
| 1806 |
- $aliases{$short} = 1;
|
|
| 1807 |
- upsert_alias_to_db($dbh, $fqdn, $short, 'derived-vhost', $now); |
|
| 1808 |
- } |
|
| 1809 |
- } else {
|
|
| 1810 |
- $aliases{$name} = 1;
|
|
| 1811 |
- upsert_alias_to_db($dbh, $fqdn, $name, 'declared', $now); |
|
| 1812 |
- if (my $short = short_alias_for_fqdn($name)) {
|
|
| 1813 |
- $aliases{$short} = 1;
|
|
| 1814 |
- upsert_alias_to_db($dbh, $fqdn, $short, 'derived', $now); |
|
| 1815 |
- } |
|
| 1949 |
+ $aliases{$name} = 1;
|
|
| 1950 |
+ upsert_alias_to_db($dbh, $fqdn, $name, 'declared', $now); |
|
| 1951 |
+ if (my $short = short_alias_for_fqdn($name)) {
|
|
| 1952 |
+ $aliases{$short} = 1;
|
|
| 1953 |
+ upsert_alias_to_db($dbh, $fqdn, $short, 'derived', $now); |
|
| 1954 |
+ } |
|
| 1955 |
+ } |
|
| 1956 |
+ for my $name (@$vhosts_in) {
|
|
| 1957 |
+ $name = normalize_dns_name($name); |
|
| 1958 |
+ next unless length $name; |
|
| 1959 |
+ $vhosts{$name} = 1;
|
|
| 1960 |
+ upsert_vhost_to_db($dbh, $fqdn, $name, $now); |
|
| 1961 |
+ if (my $short = short_alias_for_fqdn($name)) {
|
|
| 1962 |
+ $aliases{$short} = 1;
|
|
| 1963 |
+ upsert_alias_to_db($dbh, $fqdn, $short, 'derived-vhost', $now); |
|
| 1816 | 1964 |
} |
| 1817 | 1965 |
} |
| 1818 | 1966 |
|
@@ -1822,6 +1970,22 @@ sub sync_host_names {
|
||
| 1822 | 1970 |
|
| 1823 | 1971 |
sub upsert_alias_to_db {
|
| 1824 | 1972 |
my ($dbh, $fqdn, $alias, $kind, $now) = @_; |
| 1973 |
+ my ($existing_fqdn) = $dbh->selectrow_array( |
|
| 1974 |
+ "SELECT host_fqdn FROM host_aliases WHERE alias_name = ? AND status = 'active'", |
|
| 1975 |
+ undef, |
|
| 1976 |
+ $alias, |
|
| 1977 |
+ ); |
|
| 1978 |
+ if ($existing_fqdn && $existing_fqdn ne $fqdn) {
|
|
| 1979 |
+ if ($kind eq 'derived-vhost') {
|
|
| 1980 |
+ $dbh->do( |
|
| 1981 |
+ "UPDATE host_aliases SET status = 'retired', is_dns_published = 0, retired_at = ? WHERE alias_name = ? AND host_fqdn = ? AND status = 'active'", |
|
| 1982 |
+ undef, |
|
| 1983 |
+ $now, $alias, $existing_fqdn, |
|
| 1984 |
+ ); |
|
| 1985 |
+ } else {
|
|
| 1986 |
+ die "alias_conflict: $alias is already active on $existing_fqdn\n"; |
|
| 1987 |
+ } |
|
| 1988 |
+ } |
|
| 1825 | 1989 |
$dbh->do( |
| 1826 | 1990 |
'INSERT INTO host_aliases (alias_name, host_fqdn, alias_kind, status, is_dns_published, created_at, retired_at, notes) ' |
| 1827 | 1991 |
. "VALUES (?, ?, ?, 'active', 1, ?, '', '') " |
@@ -1874,14 +2038,20 @@ sub retire_host_in_db {
|
||
| 1874 | 2038 |
$dbh->do("UPDATE host_sources SET status = 'retired', retired_at = ? WHERE host_fqdn = ? AND status = 'active'", undef, $now, $fqdn);
|
| 1875 | 2039 |
} |
| 1876 | 2040 |
|
| 1877 |
-sub active_names_for_host {
|
|
| 2041 |
+sub active_aliases_for_host {
|
|
| 1878 | 2042 |
my ($dbh, $fqdn) = @_; |
| 1879 |
- my @names = ($fqdn); |
|
| 2043 |
+ my @names; |
|
| 1880 | 2044 |
my $aliases = $dbh->prepare("SELECT alias_name FROM host_aliases WHERE host_fqdn = ? AND status = 'active' AND is_dns_published = 1 AND alias_kind NOT LIKE 'derived%' ORDER BY alias_name");
|
| 1881 | 2045 |
$aliases->execute($fqdn); |
| 1882 | 2046 |
while (my ($name) = $aliases->fetchrow_array) {
|
| 1883 | 2047 |
push @names, $name; |
| 1884 | 2048 |
} |
| 2049 |
+ return unique_preserve(@names); |
|
| 2050 |
+} |
|
| 2051 |
+ |
|
| 2052 |
+sub active_vhosts_for_host {
|
|
| 2053 |
+ my ($dbh, $fqdn) = @_; |
|
| 2054 |
+ my @names; |
|
| 1885 | 2055 |
my $vhosts = $dbh->prepare("SELECT vhost_fqdn FROM vhosts WHERE host_fqdn = ? AND status = 'active' ORDER BY vhost_fqdn");
|
| 1886 | 2056 |
$vhosts->execute($fqdn); |
| 1887 | 2057 |
while (my ($name) = $vhosts->fetchrow_array) {
|
@@ -2099,16 +2269,15 @@ sub fqdn_for_legacy_id {
|
||
| 2099 | 2269 |
|
| 2100 | 2270 |
sub canonical_host_fqdn {
|
| 2101 | 2271 |
my ($host) = @_; |
| 2102 |
- my @names = map { normalize_dns_name($_) } @{ $host->{names} || [] };
|
|
| 2272 |
+ my $fqdn = normalize_dns_name($host->{fqdn} || '');
|
|
| 2273 |
+ return $fqdn if length $fqdn; |
|
| 2274 |
+ my @names = declared_dns_names_legacy($host); |
|
| 2103 | 2275 |
for my $name (@names) {
|
| 2104 | 2276 |
return $name if $name =~ /\.madagascar\.xdev\.ro\z/ && !name_is_vhost($name); |
| 2105 | 2277 |
} |
| 2106 | 2278 |
for my $name (@names) {
|
| 2107 | 2279 |
return $name if $name =~ /\./ && !name_is_vhost($name); |
| 2108 | 2280 |
} |
| 2109 |
- for my $name (@names) {
|
|
| 2110 |
- return $name if $name =~ /\./; |
|
| 2111 |
- } |
|
| 2112 | 2281 |
my $id = clean_id($host->{id} || '');
|
| 2113 | 2282 |
return $id ? "$id.madagascar.xdev.ro" : ''; |
| 2114 | 2283 |
} |
@@ -2462,6 +2631,8 @@ sub app_html {
|
||
| 2462 | 2631 |
.pill.warn { color: var(--warn); border-color: #f1d184; background: #fff7df; }
|
| 2463 | 2632 |
.pill.bad { color: var(--bad); border-color: #f0b8b3; background: #fff0ee; }
|
| 2464 | 2633 |
.pill.derived { border-style: dashed; }
|
| 2634 |
+ .pill.canonical { font-weight: 700; }
|
|
| 2635 |
+ .pill.vhost { background: #eef7ff; border-color: #b6d6f7; color: #0e4f96; }
|
|
| 2465 | 2636 |
.grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; padding: 14px; }
|
| 2466 | 2637 |
.span2 { grid-column: 1 / -1; }
|
| 2467 | 2638 |
label { display: grid; gap: 5px; color: var(--muted); font-size: 12px; font-weight: 650; }
|
@@ -2637,6 +2808,7 @@ sub app_html {
|
||
| 2637 | 2808 |
<nav aria-label="Sections"> |
| 2638 | 2809 |
<a href="/overview" data-page-link="overview">Overview</a> |
| 2639 | 2810 |
<a href="/hosts" data-page-link="hosts">Hosts</a> |
| 2811 |
+ <a href="/vhosts" data-page-link="vhosts">Vhosts</a> |
|
| 2640 | 2812 |
<a href="/dns" data-page-link="dns">DNS</a> |
| 2641 | 2813 |
<a href="/work-orders" data-page-link="work-orders">Work Orders</a> |
| 2642 | 2814 |
<a href="/ca" data-page-link="ca">Local CA</a> |
@@ -2674,8 +2846,7 @@ sub app_html {
|
||
| 2674 | 2846 |
<thead> |
| 2675 | 2847 |
<tr> |
| 2676 | 2848 |
<th style="width: 120px">ID</th> |
| 2677 |
- <th style="width: 130px">hosts_ip</th> |
|
| 2678 |
- <th style="width: 130px">dns_ip</th> |
|
| 2849 |
+ <th style="width: 140px">IP</th> |
|
| 2679 | 2850 |
<th>Names</th> |
| 2680 | 2851 |
<th style="width: 150px">Roles</th> |
| 2681 | 2852 |
<th style="width: 110px">Monitoring</th> |
@@ -2688,6 +2859,33 @@ sub app_html {
|
||
| 2688 | 2859 |
</section> |
| 2689 | 2860 |
</section> |
| 2690 | 2861 |
|
| 2862 |
+ <section class="page" id="page-vhosts" data-page="vhosts" hidden> |
|
| 2863 |
+ <section class="panel"> |
|
| 2864 |
+ <div class="panel-head"> |
|
| 2865 |
+ <h2>Vhosts</h2> |
|
| 2866 |
+ <div class="host-tools"> |
|
| 2867 |
+ <input id="vhost-filter" placeholder="filter"> |
|
| 2868 |
+ <div class="stats" id="vhost-stats"></div> |
|
| 2869 |
+ </div> |
|
| 2870 |
+ </div> |
|
| 2871 |
+ <div class="table-wrap"> |
|
| 2872 |
+ <table> |
|
| 2873 |
+ <thead> |
|
| 2874 |
+ <tr> |
|
| 2875 |
+ <th>Vhost</th> |
|
| 2876 |
+ <th style="width: 190px">Host</th> |
|
| 2877 |
+ <th style="width: 140px">IP</th> |
|
| 2878 |
+ <th style="width: 180px">Derived aliases</th> |
|
| 2879 |
+ <th style="width: 120px">Monitoring</th> |
|
| 2880 |
+ <th style="width: 90px">Status</th> |
|
| 2881 |
+ </tr> |
|
| 2882 |
+ </thead> |
|
| 2883 |
+ <tbody id="vhosts"></tbody> |
|
| 2884 |
+ </table> |
|
| 2885 |
+ </div> |
|
| 2886 |
+ </section> |
|
| 2887 |
+ </section> |
|
| 2888 |
+ |
|
| 2691 | 2889 |
<section class="page" id="page-dns" data-page="dns" hidden> |
| 2692 | 2890 |
<section class="toolbar"> |
| 2693 | 2891 |
<a class="linkbtn" href="/download/hosts.yaml">hosts.yaml</a> |
@@ -2796,10 +2994,11 @@ sub app_html {
|
||
| 2796 | 2994 |
</div> |
| 2797 | 2995 |
<form id="host-form" class="grid"> |
| 2798 | 2996 |
<label>ID<input name="id" required></label> |
| 2997 |
+ <label>FQDN<input name="fqdn" required></label> |
|
| 2799 | 2998 |
<label>Status<select name="status"><option>active</option><option>planned</option><option>retired</option></select></label> |
| 2800 |
- <label>hosts_ip<input name="hosts_ip" required></label> |
|
| 2801 |
- <label>dns_ip<input name="dns_ip" required></label> |
|
| 2802 |
- <label class="span2">Names<textarea name="names" required></textarea></label> |
|
| 2999 |
+ <label>IP<input name="ip" required></label> |
|
| 3000 |
+ <label class="span2">Aliases<textarea name="aliases"></textarea></label> |
|
| 3001 |
+ <label class="span2">Vhosts<textarea name="vhosts"></textarea></label> |
|
| 2803 | 3002 |
<label>Roles<input name="roles"></label> |
| 2804 | 3003 |
<label>Sources<input name="sources"></label> |
| 2805 | 3004 |
<label>Monitoring<select name="monitoring"><option>pending</option><option>enabled</option><option>disabled</option></select></label> |
@@ -2829,6 +3028,7 @@ sub app_html {
|
||
| 2829 | 3028 |
'/': 'overview', |
| 2830 | 3029 |
'/overview': 'overview', |
| 2831 | 3030 |
'/hosts': 'hosts', |
| 3031 |
+ '/vhosts': 'vhosts', |
|
| 2832 | 3032 |
'/dns': 'dns', |
| 2833 | 3033 |
'/work-orders': 'work-orders', |
| 2834 | 3034 |
'/ca': 'ca', |
@@ -2851,6 +3051,20 @@ sub app_html {
|
||
| 2851 | 3051 |
showLogin(message || 'Sesiunea a expirat. Autentifica-te din nou.'); |
| 2852 | 3052 |
} |
| 2853 | 3053 |
|
| 3054 |
+ async function ensureAuthenticated(message) {
|
|
| 3055 |
+ if (!state.authenticated) {
|
|
| 3056 |
+ handleAuthLost(message || 'Autentifica-te pentru a continua.'); |
|
| 3057 |
+ return false; |
|
| 3058 |
+ } |
|
| 3059 |
+ const session = await api('/api/session');
|
|
| 3060 |
+ state.authenticated = session.authenticated; |
|
| 3061 |
+ if (!state.authenticated) {
|
|
| 3062 |
+ handleAuthLost(message || 'Sesiunea a expirat. Autentifica-te din nou.'); |
|
| 3063 |
+ return false; |
|
| 3064 |
+ } |
|
| 3065 |
+ return true; |
|
| 3066 |
+ } |
|
| 3067 |
+ |
|
| 2854 | 3068 |
async function api(path, options = {}) {
|
| 2855 | 3069 |
const res = await fetch(path, options); |
| 2856 | 3070 |
let body = {};
|
@@ -2932,6 +3146,7 @@ sub app_html {
|
||
| 2932 | 3146 |
|
| 2933 | 3147 |
$('stats').innerHTML = [
|
| 2934 | 3148 |
['hosts', data.counts.hosts], |
| 3149 |
+ ['vhosts', data.counts.vhosts || vhostRows().length], |
|
| 2935 | 3150 |
['problems', data.counts.problems], |
| 2936 | 3151 |
].map(([k, v]) => `<span class="stat">${k}: ${escapeHtml(String(v))}</span>`).join('');
|
| 2937 | 3152 |
|
@@ -2940,6 +3155,7 @@ sub app_html {
|
||
| 2940 | 3155 |
: '<div class="muted" style="padding: 8px 0">No registry problems detected.</div>'; |
| 2941 | 3156 |
|
| 2942 | 3157 |
renderHosts(); |
| 3158 |
+ renderVhosts(); |
|
| 2943 | 3159 |
} |
| 2944 | 3160 |
|
| 2945 | 3161 |
async function renderCa() {
|
@@ -3251,38 +3467,88 @@ sub app_html {
|
||
| 3251 | 3467 |
const cls = problems.length ? 'warn' : 'ok'; |
| 3252 | 3468 |
return `<tr data-id="${escapeHtml(h.id)}">
|
| 3253 | 3469 |
<td><button type="button" data-edit="${escapeHtml(h.id)}">${escapeHtml(h.id)}</button></td>
|
| 3254 |
- <td>${escapeHtml(h.hosts_ip || '')}</td>
|
|
| 3255 |
- <td>${escapeHtml(h.dns_ip || '')}</td>
|
|
| 3470 |
+ <td>${escapeHtml(h.ip || '')}</td>
|
|
| 3256 | 3471 |
<td>${renderNamePills(h)}</td>
|
| 3257 | 3472 |
<td>${(h.roles || []).map(n => `<span class="pill">${escapeHtml(n)}</span>`).join('')}</td>
|
| 3258 | 3473 |
<td><span class="pill ${cls}">${escapeHtml(h.monitoring || '')}</span></td>
|
| 3259 | 3474 |
<td>${escapeHtml(h.status || '')}</td>
|
| 3260 | 3475 |
</tr>`; |
| 3261 | 3476 |
}).join('');
|
| 3262 |
- document.querySelectorAll('[data-edit]').forEach(button => button.addEventListener('click', () => editHost(button.dataset.edit)));
|
|
| 3477 |
+ document.querySelectorAll('[data-edit]').forEach(button => button.addEventListener('click', () => {
|
|
| 3478 |
+ editHost(button.dataset.edit).catch(e => {
|
|
| 3479 |
+ if (!isAuthLost(e)) msg(e.message); |
|
| 3480 |
+ }); |
|
| 3481 |
+ })); |
|
| 3263 | 3482 |
} |
| 3264 | 3483 |
|
| 3265 | 3484 |
function renderNamePills(host) {
|
| 3266 |
- const declared = host.declared_names || host.names || []; |
|
| 3267 |
- const derived = host.derived_names || []; |
|
| 3268 |
- const declaredHtml = declared.map(name => `<span class="pill">${escapeHtml(name)}</span>`).join('');
|
|
| 3269 |
- const derivedHtml = derived.map(name => `<span class="pill derived" title="derived from madagascar.xdev.ro">${escapeHtml(name)}</span>`).join('');
|
|
| 3270 |
- return declaredHtml + derivedHtml; |
|
| 3485 |
+ const canonical = host.fqdn ? `<span class="pill canonical">${escapeHtml(host.fqdn)}</span>` : '';
|
|
| 3486 |
+ const aliases = (host.aliases || []).map(name => `<span class="pill">${escapeHtml(name)}</span>`).join('');
|
|
| 3487 |
+ const derivedAliases = (host.derived_aliases || []).map(name => `<span class="pill derived" title="derived alias">${escapeHtml(name)}</span>`).join('');
|
|
| 3488 |
+ const vhosts = (host.vhosts || []).map(name => `<span class="pill vhost">${escapeHtml(name)}</span>`).join('');
|
|
| 3489 |
+ const derivedVhostAliases = (host.derived_vhost_aliases || []).map(name => `<span class="pill derived vhost" title="derived vhost alias">${escapeHtml(name)}</span>`).join('');
|
|
| 3490 |
+ return canonical + aliases + derivedAliases + vhosts + derivedVhostAliases; |
|
| 3491 |
+ } |
|
| 3492 |
+ |
|
| 3493 |
+ function vhostRows() {
|
|
| 3494 |
+ return state.hosts.flatMap(host => (host.vhosts || []).map(vhost => ({
|
|
| 3495 |
+ vhost, |
|
| 3496 |
+ host_id: host.id || '', |
|
| 3497 |
+ host_fqdn: host.fqdn || '', |
|
| 3498 |
+ ip: host.ip || '', |
|
| 3499 |
+ derived_aliases: shortAliasForFqdn(vhost) ? [shortAliasForFqdn(vhost)] : [], |
|
| 3500 |
+ monitoring: host.monitoring || '', |
|
| 3501 |
+ status: host.status || '', |
|
| 3502 |
+ }))); |
|
| 3503 |
+ } |
|
| 3504 |
+ |
|
| 3505 |
+ function renderVhosts() {
|
|
| 3506 |
+ const input = $('vhost-filter');
|
|
| 3507 |
+ const filter = input ? input.value.toLowerCase() : ''; |
|
| 3508 |
+ const rows = vhostRows() |
|
| 3509 |
+ .sort((a, b) => String(a.vhost || '').localeCompare(String(b.vhost || ''))) |
|
| 3510 |
+ .filter(row => JSON.stringify(row).toLowerCase().includes(filter)); |
|
| 3511 |
+ $('vhost-stats').innerHTML = [
|
|
| 3512 |
+ ['shown', rows.length], |
|
| 3513 |
+ ['total', vhostRows().length], |
|
| 3514 |
+ ].map(([k, v]) => `<span class="stat">${escapeHtml(k)}: ${escapeHtml(String(v))}</span>`).join('');
|
|
| 3515 |
+ $('vhosts').innerHTML = rows.length ? rows.map(row => `<tr>
|
|
| 3516 |
+ <td><span class="pill vhost">${escapeHtml(row.vhost)}</span></td>
|
|
| 3517 |
+ <td><button type="button" data-edit-vhost-host="${escapeHtml(row.host_id)}">${escapeHtml(row.host_id)}</button><div class="muted mono">${escapeHtml(row.host_fqdn)}</div></td>
|
|
| 3518 |
+ <td>${escapeHtml(row.ip)}</td>
|
|
| 3519 |
+ <td>${row.derived_aliases.map(name => `<span class="pill derived vhost">${escapeHtml(name)}</span>`).join('')}</td>
|
|
| 3520 |
+ <td><span class="pill">${escapeHtml(row.monitoring)}</span></td>
|
|
| 3521 |
+ <td>${escapeHtml(row.status)}</td>
|
|
| 3522 |
+ </tr>`).join('') : '<tr><td colspan="6" class="muted">No vhosts.</td></tr>';
|
|
| 3523 |
+ document.querySelectorAll('[data-edit-vhost-host]').forEach(button => button.addEventListener('click', () => {
|
|
| 3524 |
+ editHost(button.dataset.editVhostHost).catch(e => {
|
|
| 3525 |
+ if (!isAuthLost(e)) msg(e.message); |
|
| 3526 |
+ }); |
|
| 3527 |
+ })); |
|
| 3528 |
+ } |
|
| 3529 |
+ |
|
| 3530 |
+ function shortAliasForFqdn(name) {
|
|
| 3531 |
+ const suffix = '.madagascar.xdev.ro'; |
|
| 3532 |
+ name = String(name || '').toLowerCase(); |
|
| 3533 |
+ return name.endsWith(suffix) ? name.slice(0, -suffix.length) : ''; |
|
| 3271 | 3534 |
} |
| 3272 | 3535 |
|
| 3273 |
- function editHost(id) {
|
|
| 3536 |
+ async function editHost(id) {
|
|
| 3537 |
+ if (!await ensureAuthenticated('Autentifica-te inainte de editare.')) return;
|
|
| 3274 | 3538 |
const host = state.hosts.find(h => h.id === id); |
| 3275 | 3539 |
if (!host) return; |
| 3276 | 3540 |
const form = $('host-form');
|
| 3277 | 3541 |
clearHostFormMessage(); |
| 3278 |
- for (const key of ['id', 'status', 'hosts_ip', 'dns_ip', 'monitoring', 'notes']) hostField(key).value = host[key] || ''; |
|
| 3279 |
- hostField('names').value = (host.declared_names || host.names || []).join('\n');
|
|
| 3542 |
+ for (const key of ['id', 'fqdn', 'status', 'ip', 'monitoring', 'notes']) hostField(key).value = host[key] || ''; |
|
| 3543 |
+ hostField('aliases').value = (host.aliases || []).join('\n');
|
|
| 3544 |
+ hostField('vhosts').value = (host.vhosts || []).join('\n');
|
|
| 3280 | 3545 |
hostField('roles').value = (host.roles || []).join(' ');
|
| 3281 | 3546 |
hostField('sources').value = (host.sources || []).join(' ');
|
| 3282 | 3547 |
openHostModal('Edit host');
|
| 3283 | 3548 |
} |
| 3284 | 3549 |
|
| 3285 |
- function newHost() {
|
|
| 3550 |
+ async function newHost() {
|
|
| 3551 |
+ if (!await ensureAuthenticated('Autentifica-te inainte de adaugarea unui host.')) return;
|
|
| 3286 | 3552 |
const form = $('host-form');
|
| 3287 | 3553 |
form.reset(); |
| 3288 | 3554 |
clearHostFormMessage(); |
@@ -3461,13 +3727,18 @@ sub app_html {
|
||
| 3461 | 3727 |
else otpDigits[0].focus(); |
| 3462 | 3728 |
|
| 3463 | 3729 |
document.querySelectorAll('[data-page-link]').forEach(link => {
|
| 3464 |
- link.addEventListener('click', (event) => {
|
|
| 3730 |
+ link.addEventListener('click', async (event) => {
|
|
| 3465 | 3731 |
event.preventDefault(); |
| 3732 |
+ if (!await ensureAuthenticated('Autentifica-te pentru a schimba sectiunea.')) return;
|
|
| 3466 | 3733 |
showPage(link.dataset.pageLink, true); |
| 3467 | 3734 |
}); |
| 3468 | 3735 |
}); |
| 3469 | 3736 |
|
| 3470 |
- window.addEventListener('popstate', () => showPage(currentPage()));
|
|
| 3737 |
+ window.addEventListener('popstate', () => {
|
|
| 3738 |
+ ensureAuthenticated('Autentifica-te pentru a schimba sectiunea.')
|
|
| 3739 |
+ .then(authenticated => { if (authenticated) showPage(currentPage()); })
|
|
| 3740 |
+ .catch(e => { if (!isAuthLost(e)) msg(e.message); });
|
|
| 3741 |
+ }); |
|
| 3471 | 3742 |
|
| 3472 | 3743 |
async function copyText(text) {
|
| 3473 | 3744 |
if (navigator.clipboard && window.isSecureContext) {
|
@@ -3518,7 +3789,12 @@ sub app_html {
|
||
| 3518 | 3789 |
if (!isAuthLost(e)) msg(e.message); |
| 3519 | 3790 |
})); |
| 3520 | 3791 |
$('filter').addEventListener('input', renderHosts);
|
| 3521 |
- $('new-host').addEventListener('click', newHost);
|
|
| 3792 |
+ $('vhost-filter').addEventListener('input', renderVhosts);
|
|
| 3793 |
+ $('new-host').addEventListener('click', () => {
|
|
| 3794 |
+ newHost().catch(e => {
|
|
| 3795 |
+ if (!isAuthLost(e)) msg(e.message); |
|
| 3796 |
+ }); |
|
| 3797 |
+ }); |
|
| 3522 | 3798 |
$('debug-db-refresh').addEventListener('click', () => renderDebugDatabase().catch(e => {
|
| 3523 | 3799 |
if (!isAuthLost(e)) msg(e.message); |
| 3524 | 3800 |
})); |
@@ -3532,6 +3808,7 @@ sub app_html {
|
||
| 3532 | 3808 |
|
| 3533 | 3809 |
$('host-form').addEventListener('submit', async (event) => {
|
| 3534 | 3810 |
event.preventDefault(); |
| 3811 |
+ if (!await ensureAuthenticated('Autentifica-te inainte de salvare. Modificarile raman in formular.')) return;
|
|
| 3535 | 3812 |
setHostFormBusy(true); |
| 3536 | 3813 |
setHostFormMessage('Saving...');
|
| 3537 | 3814 |
try {
|
@@ -96,20 +96,28 @@ while IFS= read -r line || [[ -n "$line" ]]; do |
||
| 96 | 96 |
[[ -z "${line//[[:space:]]/}" ]] && continue
|
| 97 | 97 |
[[ "$line" =~ ^[[:space:]]*# ]] && continue |
| 98 | 98 |
|
| 99 |
- read -r hosts_ip dns_ip names <<< "$line" |
|
| 100 |
- [[ -n "${hosts_ip:-}" && -n "${dns_ip:-}" && -n "${names:-}" ]] || die "Invalid row: $line"
|
|
| 99 |
+ line="${line//$'\r'/}"
|
|
| 100 |
+ IFS=$'\t' read -r col1 col2 col3 _ <<< "$line" |
|
| 101 |
+ if [[ -n "${col3:-}" ]]; then
|
|
| 102 |
+ ip="$col2" |
|
| 103 |
+ names="$col3" |
|
| 104 |
+ else |
|
| 105 |
+ ip="$col1" |
|
| 106 |
+ names="$col2" |
|
| 107 |
+ fi |
|
| 108 |
+ [[ -n "${ip:-}" && -n "${names:-}" ]] || die "Invalid row: $line"
|
|
| 101 | 109 |
|
| 102 |
- printf '%s %s\n' "$hosts_ip" "$names" >> "$HOSTS_ROWS" |
|
| 110 |
+ printf '%s %s\n' "$ip" "$names" >> "$HOSTS_ROWS" |
|
| 103 | 111 |
|
| 104 | 112 |
for name in $names; do |
| 105 | 113 |
printf '%s\n' "$name" >> "$NAMES_FILE" |
| 106 |
- printf '%-40s %s\n' "$name" "$dns_ip" >> "$CLOAK_ROWS" |
|
| 114 |
+ printf '%-40s %s\n' "$name" "$ip" >> "$CLOAK_ROWS" |
|
| 107 | 115 |
if [[ "$name" == *.xdev.ro ]]; then |
| 108 |
- printf '%s %s\n' "$name" "$dns_ip" >> "$VERIFY_ROWS" |
|
| 116 |
+ printf '%s %s\n' "$name" "$ip" >> "$VERIFY_ROWS" |
|
| 109 | 117 |
fi |
| 110 | 118 |
|
| 111 | 119 |
ros_name="$(quote_ros "$name")" |
| 112 |
- ros_ip="$(quote_ros "$dns_ip")" |
|
| 120 |
+ ros_ip="$(quote_ros "$ip")" |
|
| 113 | 121 |
{
|
| 114 | 122 |
printf '/ip dns static remove [find name="%s"]\n' "$ros_name" |
| 115 | 123 |
printf '/ip dns static add name="%s" type=A address=%s comment="xdev-local managed"\n' "$ros_name" "$ros_ip" |