web: Add WHOIS modal accessible from zone sidebar

Introduces ModalDomainWhois component that fetches and displays domain
registration data (status, dates, registrar, nameservers) in a modal,
accessible via a new "View registration data" item in the zone gear menu.
This commit is contained in:
nemunaire 2026-03-17 15:48:38 +07:00
commit e1b13b9e5c
6 changed files with 111 additions and 1 deletions

View file

@ -86,6 +86,7 @@
"upload": "Import a zone file", "upload": "Import a zone file",
"view": "View my zone", "view": "View my zone",
"view-checks": "View checks", "view-checks": "View checks",
"whois": "View registration data",
"others": "More actions on {{domain}}" "others": "More actions on {{domain}}"
}, },
"alert": { "alert": {

View file

@ -76,7 +76,8 @@
"rollback": "Revenir à cette version", "rollback": "Revenir à cette version",
"share": "Partager la zone…", "share": "Partager la zone…",
"upload": "Importer un fichier de zone", "upload": "Importer un fichier de zone",
"view": "Voir ma zone" "view": "Voir ma zone",
"whois": "Voir les infos d'enregistrement"
}, },
"alert": { "alert": {
"remove": "Cette action retirera définitivement le domaine {{domain}} de votre liste. L'historique et les zones abstraites seront supprimés. Cela ne supprimera ni n'affectera votre domaine auprès de votre fournisseur, ni ne modifiera ce qui est actuellement diffusé. Cela ne concerne seulement que ce que vous voyez dans happyDomain. Êtes-vous certain de vouloir continuer?", "remove": "Cette action retirera définitivement le domaine {{domain}} de votre liste. L'historique et les zones abstraites seront supprimés. Cela ne supprimera ni n'affectera votre domaine auprès de votre fournisseur, ni ne modifiera ce qui est actuellement diffusé. Cela ne concerne seulement que ce que vous voyez dans happyDomain. Êtes-vous certain de vouloir continuer?",

View file

@ -49,6 +49,7 @@ interface Params {
providers?: string; providers?: string;
services?: string; services?: string;
label?: string; label?: string;
days?: number;
// add more parameters that are used here // add more parameters that are used here
} }

View file

@ -49,6 +49,7 @@
import ButtonZonePublish from "./ButtonZonePublish.svelte"; import ButtonZonePublish from "./ButtonZonePublish.svelte";
import ModalDiffZone from "./ModalDiffZone.svelte"; import ModalDiffZone from "./ModalDiffZone.svelte";
import ModalDomainDelete, { controls as ctrlDomainDelete } from "./ModalDomainDelete.svelte"; import ModalDomainDelete, { controls as ctrlDomainDelete } from "./ModalDomainDelete.svelte";
import ModalDomainWhois from "./ModalDomainWhois.svelte";
import ModalUploadZone from "./ModalUploadZone.svelte"; import ModalUploadZone from "./ModalUploadZone.svelte";
import NewSubdomainPath from "./NewSubdomainPath.svelte"; import NewSubdomainPath from "./NewSubdomainPath.svelte";
import ServiceDetailsOffcanvas from "./ServiceDetailsOffcanvas.svelte"; import ServiceDetailsOffcanvas from "./ServiceDetailsOffcanvas.svelte";
@ -268,6 +269,8 @@
<ModalDomainDelete on:detachDomain={detachDomain} /> <ModalDomainDelete on:detachDomain={detachDomain} />
<ModalDomainWhois domain={data.domain.domain} />
<ModalDiffZone domain={data.domain} {selectedHistory} /> <ModalDiffZone domain={data.domain} {selectedHistory} />
<ServiceDetailsOffcanvas domain={data.domain} {selectedHistory} /> <ServiceDetailsOffcanvas domain={data.domain} {selectedHistory} />

View file

@ -0,0 +1,100 @@
<!--
This file is part of the happyDomain (R) project.
Copyright (c) 2022-2026 happyDomain
Authors: Pierre-Olivier Mercier, et al.
This program is offered under a commercial and under the AGPL license.
For commercial licensing, contact us at <contact@happydomain.org>.
For AGPL licensing:
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
-->
<script module lang="ts">
import type { ModalController } from "$lib/model/modal_controller";
export const controls: ModalController = {
Open() { },
};
</script>
<script lang="ts">
import { Modal, ModalBody, ModalHeader, Spinner } from "@sveltestrap/sveltestrap";
import { getDomainInfo } from "$lib/api/domaininfo";
import type { DomainInfo } from "$lib/model/domaininfo";
import { t } from "$lib/translations";
import DomainInfoDisplay from "$lib/components/DomainInfoDisplay.svelte";
interface Props {
domain: string;
isOpen?: boolean;
}
let { domain, isOpen = $bindable(false) }: Props = $props();
let info: DomainInfo | null = $state(null);
let error_response: string | null = $state(null);
let request_pending = $state(false);
function Open(): void {
isOpen = true;
info = null;
error_response = null;
request_pending = true;
getDomainInfo(domain).then(
(result) => {
info = result;
request_pending = false;
},
(error: unknown) => {
const msg = error instanceof Error ? error.message : String(error);
error_response = msg;
request_pending = false;
},
);
}
function toggle(): void {
isOpen = !isOpen;
}
controls.Open = Open;
</script>
<Modal {isOpen} size="lg" {toggle}>
<ModalHeader {toggle}>
{$t("domaininfo.page-title")}<span class="font-monospace">{domain}</span>
</ModalHeader>
<ModalBody>
{#if request_pending}
<div class="text-center text-muted py-4">
<Spinner />
<p class="mt-3">{$t("common.spinning")}</p>
</div>
{:else if error_response !== null}
<div class="card border-danger">
<div class="card-body">
<div class="d-flex align-items-center">
<i class="bi bi-x-circle text-danger fs-3 me-3"></i>
<p class="card-text mb-0">{error_response}</p>
</div>
</div>
</div>
{:else if info !== null}
<DomainInfoDisplay {info} {domain} />
{/if}
</ModalBody>
</Modal>

View file

@ -45,6 +45,7 @@
import { t } from "$lib/translations"; import { t } from "$lib/translations";
import { navigate } from "$lib/stores/config"; import { navigate } from "$lib/stores/config";
import { controls as ctrlDomainDelete } from "./ModalDomainDelete.svelte"; import { controls as ctrlDomainDelete } from "./ModalDomainDelete.svelte";
import { controls as ctrlDomainWhois } from "./ModalDomainWhois.svelte";
import { controls as ctrlUploadZone } from "./ModalUploadZone.svelte"; import { controls as ctrlUploadZone } from "./ModalUploadZone.svelte";
import { controls as ctrlNewSubdomain } from "./NewSubdomainPath.svelte"; import { controls as ctrlNewSubdomain } from "./NewSubdomainPath.svelte";
import SubdomainListTiny from "./SubdomainListTiny.svelte"; import SubdomainListTiny from "./SubdomainListTiny.svelte";
@ -120,6 +121,9 @@
<DropdownItem href={`/domains/${domainLink(selectedDomain)}/checks`}> <DropdownItem href={`/domains/${domainLink(selectedDomain)}/checks`}>
{$t("domains.actions.view-checks")} {$t("domains.actions.view-checks")}
</DropdownItem> </DropdownItem>
<DropdownItem on:click={() => ctrlDomainWhois.Open()}>
{$t("domains.actions.whois")}
</DropdownItem>
<DropdownItem divider /> <DropdownItem divider />
<DropdownItem on:click={viewZone} disabled={!$sortedDomains}> <DropdownItem on:click={viewZone} disabled={!$sortedDomains}>
{$t("domains.actions.view")} {$t("domains.actions.view")}