feat: DNS_SERVER env var — pre-fills discovery UI, optional for scan
- DNS_SERVER env var configures the default DNS server for PTR lookups - GET /api/discovery/config exposes it to the frontend - DiscoveryModal fetches it on mount and pre-fills the field (editable) - dns_server is now optional in ScanRequest (default empty string) - PTR lookup is skipped when dns_server is empty — scan still proceeds - Validator only runs when dns_server is non-empty - .env.example, docker-compose.yml, READMEs (fr/en/es) updated Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -57,3 +57,13 @@ BIND_ADDRESS=0.0.0.0
|
||||
#
|
||||
DOCKER_UID=1000
|
||||
DOCKER_GID=1000
|
||||
|
||||
# ── DNS server for auto-discovery ────────────────────────────────────────────
|
||||
# IP address of the DNS server used for PTR (reverse DNS) lookups during
|
||||
# host discovery. Pre-fills the field in the discovery UI (user can override).
|
||||
# Leave empty to disable reverse DNS lookups by default.
|
||||
#
|
||||
# Examples:
|
||||
# DNS_SERVER=192.168.1.1 (your router / local DNS)
|
||||
# DNS_SERVER= (disabled — no PTR lookups)
|
||||
DNS_SERVER=
|
||||
|
||||
@@ -100,6 +100,7 @@ Toda la configuración se realiza mediante variables de entorno. Ver `.env.examp
|
||||
| `INITIAL_ADMIN_PASSWORD` | _(vacío)_ | Contraseña de bootstrap del admin. Si no se define, se usa `admin/admin` con cambio forzado. |
|
||||
| `ALLOWED_ORIGINS` | `*` | Orígenes CORS permitidos (separados por comas). Definir en tu dominio en producción. |
|
||||
| `BIND_ADDRESS` | `0.0.0.0` | Dirección IP de escucha. Definir en la interfaz frente al reverse proxy. |
|
||||
| `DNS_SERVER` | _(vacío)_ | Servidor DNS para lookups PTR durante el descubrimiento automático. Pre-rellena el campo en la UI (modificable por escaneo). Dejar vacío para desactivar el DNS inverso. |
|
||||
| `DOCKER_UID` / `DOCKER_GID` | `1000` | UID/GID para el proceso backend. Debe coincidir con el usuario propietario de `./db_data/`. |
|
||||
|
||||
```bash
|
||||
|
||||
@@ -100,6 +100,7 @@ Toute la configuration se fait via des variables d'environnement. Voir `.env.exa
|
||||
| `INITIAL_ADMIN_PASSWORD` | _(vide)_ | Mot de passe admin de bootstrap. Si non défini, `admin/admin` est utilisé avec changement forcé. |
|
||||
| `ALLOWED_ORIGINS` | `*` | Origines CORS autorisées (séparées par des virgules). À définir sur votre domaine en production. |
|
||||
| `BIND_ADDRESS` | `0.0.0.0` | Adresse IP d'écoute. À définir sur l'interface face au reverse proxy. |
|
||||
| `DNS_SERVER` | _(vide)_ | Serveur DNS pour les lookups PTR lors de la découverte automatique. Pré-remplit le champ dans l'UI (modifiable par scan). Laisser vide pour désactiver le reverse DNS. |
|
||||
| `DOCKER_UID` / `DOCKER_GID` | `1000` | UID/GID pour le processus backend. Doit correspondre à l'utilisateur propriétaire de `./db_data/`. |
|
||||
|
||||
```bash
|
||||
|
||||
@@ -100,6 +100,7 @@ All configuration is via environment variables. See `.env.example` for the full
|
||||
| `INITIAL_ADMIN_PASSWORD` | _(empty)_ | Bootstrap admin password. If unset, `admin/admin` is used with forced change. |
|
||||
| `ALLOWED_ORIGINS` | `*` | CORS allowed origins (comma-separated). Set to your domain in production. |
|
||||
| `BIND_ADDRESS` | `0.0.0.0` | IP address to listen on. Set to the interface facing the reverse proxy. |
|
||||
| `DNS_SERVER` | _(empty)_ | DNS server for PTR lookups during auto-discovery. Pre-fills the field in the UI (overridable per scan). Leave empty to disable reverse DNS. |
|
||||
| `DOCKER_UID` / `DOCKER_GID` | `1000` | UID/GID for the backend process. Must match the host user owning `./db_data/`. |
|
||||
|
||||
```bash
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import errno
|
||||
import ipaddress
|
||||
import os
|
||||
import socket
|
||||
import subprocess
|
||||
import time
|
||||
@@ -16,6 +17,8 @@ router = APIRouter()
|
||||
MAX_HOSTS_PER_TARGET = 1024 # refuse les /21 et plus larges
|
||||
MAX_HOSTS_TOTAL = 4096 # cap global sur l'ensemble des targets
|
||||
|
||||
_ENV_DNS = os.environ.get("DNS_SERVER", "").strip()
|
||||
|
||||
|
||||
class ScanTarget(BaseModel):
|
||||
vlan_id: int
|
||||
@@ -23,13 +26,14 @@ class ScanTarget(BaseModel):
|
||||
|
||||
|
||||
class ScanRequest(BaseModel):
|
||||
dns_server: str
|
||||
dns_server: str = ""
|
||||
targets: list[ScanTarget]
|
||||
tcp_check: bool = False
|
||||
|
||||
@field_validator("dns_server")
|
||||
@classmethod
|
||||
def _dns_server(cls, v: str) -> str:
|
||||
if v:
|
||||
try:
|
||||
ipaddress.ip_address(v)
|
||||
except ValueError:
|
||||
@@ -37,6 +41,11 @@ class ScanRequest(BaseModel):
|
||||
return v
|
||||
|
||||
|
||||
@router.get("/config")
|
||||
def get_config():
|
||||
return {"dns_server": _ENV_DNS}
|
||||
|
||||
|
||||
class DiscoveredHost(BaseModel):
|
||||
ip: str
|
||||
hostname: Optional[str] = None
|
||||
@@ -109,7 +118,7 @@ def _scan_one(ip: str, dns_server: str, vlan_id: int, cidr: str, tcp_check: bool
|
||||
return None
|
||||
if tcp_check and not _tcp_check(ip):
|
||||
return None
|
||||
hostname = _ptr_lookup(ip, dns_server)
|
||||
hostname = _ptr_lookup(ip, dns_server) if dns_server else None
|
||||
return DiscoveredHost(ip=ip, hostname=hostname, vlan_id=vlan_id, cidr=cidr)
|
||||
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@ services:
|
||||
- SECRET_KEY=${SECRET_KEY}
|
||||
- INITIAL_ADMIN_PASSWORD=${INITIAL_ADMIN_PASSWORD}
|
||||
- ALLOWED_ORIGINS=${ALLOWED_ORIGINS:-*}
|
||||
- DNS_SERVER=${DNS_SERVER:-}
|
||||
# ── Docker secret alternative for SECRET_KEY ─────────────────────────────
|
||||
# Instead of passing SECRET_KEY via environment, you can mount a secret file.
|
||||
# The backend reads data/secret_key.txt when SECRET_KEY env var is unset.
|
||||
|
||||
@@ -46,6 +46,7 @@ export const devicesApi = {
|
||||
}
|
||||
|
||||
export const discoveryApi = {
|
||||
config: () => http.get('/discovery/config'),
|
||||
scan: (data) => http.post('/discovery/scan', data),
|
||||
ping: (ips) => http.post('/discovery/ping', { ips }),
|
||||
}
|
||||
|
||||
@@ -173,7 +173,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed } from 'vue'
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { devicesApi, discoveryApi } from '../api.js'
|
||||
import { t } from '../i18n.js'
|
||||
|
||||
@@ -194,6 +194,13 @@ const configError = ref('')
|
||||
const importError = ref('')
|
||||
const importing = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
const resp = await discoveryApi.config()
|
||||
dnsServer.value = resp.data.dns_server || ''
|
||||
} catch { /* ignore — field stays empty */ }
|
||||
})
|
||||
|
||||
const scanableVlans = computed(() => props.vlans.filter(v => v.cidr))
|
||||
|
||||
const existingIps = computed(() => {
|
||||
@@ -246,10 +253,6 @@ function toggleAll(e) {
|
||||
|
||||
async function startScan() {
|
||||
configError.value = ''
|
||||
if (!dnsServer.value.trim()) {
|
||||
configError.value = t('dnsRequired')
|
||||
return
|
||||
}
|
||||
if (selectedVlanIds.value.length === 0) {
|
||||
configError.value = t('selectVlan')
|
||||
return
|
||||
|
||||
@@ -118,7 +118,7 @@ const LANGS = {
|
||||
// DiscoveryModal
|
||||
autoDiscovery: 'Découverte automatique',
|
||||
dnsServer: 'Serveur DNS',
|
||||
dnsHint: 'Le reverse DNS sera interrogé sur ce serveur pour résoudre les noms.',
|
||||
dnsHint: 'Optionnel. Si renseigné, le reverse DNS (PTR) sera interrogé pour résoudre les noms d'hôtes. Configurez la valeur par défaut via DNS_SERVER dans .env.',
|
||||
vlansToScan: 'VLANs à scanner',
|
||||
vlansHint: 'Seuls les VLANs avec un sous-réseau CIDR configuré peuvent être scannés.',
|
||||
noCidrWarning: "Aucun VLAN n'a de CIDR configuré. Renseignez-les dans l'onglet VLANs.",
|
||||
@@ -277,7 +277,7 @@ const LANGS = {
|
||||
subnetPlaceholder: 'e.g. 192.168.10.0/24',
|
||||
autoDiscovery: 'Auto discovery',
|
||||
dnsServer: 'DNS server',
|
||||
dnsHint: 'Reverse DNS will be queried on this server to resolve hostnames.',
|
||||
dnsHint: 'Optional. If set, reverse DNS (PTR) will be queried to resolve hostnames. Set the default via DNS_SERVER in .env.',
|
||||
vlansToScan: 'VLANs to scan',
|
||||
vlansHint: 'Only VLANs with a configured CIDR subnet can be scanned.',
|
||||
noCidrWarning: 'No VLAN has a CIDR configured. Set them in the Networks tab.',
|
||||
@@ -435,7 +435,7 @@ const LANGS = {
|
||||
subnetPlaceholder: 'ej: 192.168.10.0/24',
|
||||
autoDiscovery: 'Descubrimiento automático',
|
||||
dnsServer: 'Servidor DNS',
|
||||
dnsHint: 'El DNS inverso será consultado en este servidor para resolver nombres.',
|
||||
dnsHint: 'Opcional. Si se indica, se consultará el DNS inverso (PTR) para resolver nombres de host. Configure el valor por defecto con DNS_SERVER en .env.',
|
||||
vlansToScan: 'VLANs a escanear',
|
||||
vlansHint: 'Solo los VLANs con subred CIDR configurada pueden escanearse.',
|
||||
noCidrWarning: 'Ninguna VLAN tiene CIDR configurado. Configúrelo en la pestaña Redes.',
|
||||
|
||||
Reference in New Issue
Block a user