Compare commits
4 Commits
e491485773
...
d0b2de72fe
Author | SHA1 | Date | |
---|---|---|---|
d0b2de72fe | |||
58ab870afe | |||
3c6c070bd1 | |||
3211f8e6f9 |
@ -98,7 +98,7 @@
|
|||||||
</ModalFooter>
|
</ModalFooter>
|
||||||
{/if}
|
{/if}
|
||||||
<ModalFooter>
|
<ModalFooter>
|
||||||
{#if origin && zoneId}
|
{#if update && origin && zoneId}
|
||||||
<Button
|
<Button
|
||||||
color="dark"
|
color="dark"
|
||||||
outline={!showRecords}
|
outline={!showRecords}
|
||||||
|
@ -38,6 +38,7 @@
|
|||||||
import type { Domain, DomainInList } from '$lib/model/domain';
|
import type { Domain, DomainInList } from '$lib/model/domain';
|
||||||
import type { ServiceCombined } from '$lib/model/service';
|
import type { ServiceCombined } from '$lib/model/service';
|
||||||
import { ZoneViewGrid } from '$lib/model/usersettings';
|
import { ZoneViewGrid } from '$lib/model/usersettings';
|
||||||
|
import { servicesSpecs } from '$lib/stores/services';
|
||||||
import { userSession } from '$lib/stores/usersession';
|
import { userSession } from '$lib/stores/usersession';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
|
||||||
@ -82,7 +83,43 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if isCNAME(services) || isPTR(services)}
|
{#if services.length === 0}
|
||||||
|
<div id={dn}>
|
||||||
|
{#if !reverseZone}
|
||||||
|
<h2
|
||||||
|
class="sticky-top"
|
||||||
|
style="background: white; z-index: 1"
|
||||||
|
>
|
||||||
|
<span style="white-space: nowrap">
|
||||||
|
<Icon
|
||||||
|
name="plus-square-dotted"
|
||||||
|
title="Intermediate domain with no service"
|
||||||
|
/>
|
||||||
|
<span
|
||||||
|
class="font-monospace"
|
||||||
|
title={fqdn(dn, origin.domain)}
|
||||||
|
>
|
||||||
|
{#if reverseZone}
|
||||||
|
{unreverseDomain(fqdn(dn, origin.domain))}
|
||||||
|
{:else}
|
||||||
|
{fqdn(dn, origin.domain)}
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
color="primary"
|
||||||
|
size="sm"
|
||||||
|
class="ml-2"
|
||||||
|
on:click={() => dispatch("new-service")}
|
||||||
|
>
|
||||||
|
<Icon name="plus" />
|
||||||
|
{$t('service.add')}
|
||||||
|
</Button>
|
||||||
|
</h2>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{:else if isCNAME(services) || isPTR(services)}
|
||||||
<div id={dn}>
|
<div id={dn}>
|
||||||
<h2
|
<h2
|
||||||
class="sticky-top"
|
class="sticky-top"
|
||||||
@ -188,12 +225,31 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</span>
|
</span>
|
||||||
</h2>
|
</h2>
|
||||||
|
{#if !showResources}
|
||||||
|
<Badge
|
||||||
|
id={"popoversvc-" + dn.replace('.', '__')}
|
||||||
|
style="cursor: pointer;"
|
||||||
|
>
|
||||||
|
{$t('domains.n-services', {count: services.length})}
|
||||||
|
</Badge>
|
||||||
|
<Popover
|
||||||
|
dismissible
|
||||||
|
placement="bottom"
|
||||||
|
target={"popoversvc-" + dn.replace('.', '__')}
|
||||||
|
>
|
||||||
|
{#each services as service}
|
||||||
|
<strong>{$servicesSpecs[service._svctype].name}:</strong>
|
||||||
|
<span class="text-muted">{service._comment}</span>
|
||||||
|
<br>
|
||||||
|
{/each}
|
||||||
|
</Popover>
|
||||||
|
{/if}
|
||||||
{#if aliases.length != 0}
|
{#if aliases.length != 0}
|
||||||
<Badge
|
<Badge
|
||||||
id={"popoverbadge-" + dn.replace('.', '__')}
|
id={"popoverbadge-" + dn.replace('.', '__')}
|
||||||
style="cursor: pointer;"
|
style="cursor: pointer;"
|
||||||
>
|
>
|
||||||
+ {$t('domains.n-aliases', {n: aliases.length})}
|
+ {$t('domains.n-aliases', {count: aliases.length})}
|
||||||
</Badge>
|
</Badge>
|
||||||
<Popover
|
<Popover
|
||||||
dismissible
|
dismissible
|
||||||
@ -209,7 +265,7 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</Popover>
|
</Popover>
|
||||||
{/if}
|
{/if}
|
||||||
{#if $userSession && $userSession.settings.zoneview !== ZoneViewGrid}
|
{#if !showResources || ($userSession && $userSession.settings.zoneview !== ZoneViewGrid)}
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -220,6 +276,7 @@
|
|||||||
{$t('domains.add-a-service')}
|
{$t('domains.add-a-service')}
|
||||||
</Button>
|
</Button>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if showResources}
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
color="primary"
|
color="primary"
|
||||||
@ -230,6 +287,7 @@
|
|||||||
<Icon name="link" />
|
<Icon name="link" />
|
||||||
{$t('domains.add-an-alias')}
|
{$t('domains.add-an-alias')}
|
||||||
</Button>
|
</Button>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{#if showResources}
|
{#if showResources}
|
||||||
<div
|
<div
|
||||||
|
@ -36,6 +36,7 @@
|
|||||||
|
|
||||||
export let origin: DomainInList | Domain;
|
export let origin: DomainInList | Domain;
|
||||||
export let sortedDomains: Array<string>;
|
export let sortedDomains: Array<string>;
|
||||||
|
export let sortedDomainsWithIntermediate: Array<string>;
|
||||||
export let zone: Zone;
|
export let zone: Zone;
|
||||||
|
|
||||||
let aliases: Record<string, Array<string>>;
|
let aliases: Record<string, Array<string>>;
|
||||||
@ -69,7 +70,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#each sortedDomains as dn}
|
{#each sortedDomainsWithIntermediate as dn}
|
||||||
<SubdomainItem
|
<SubdomainItem
|
||||||
aliases={aliases[dn]?aliases[dn]:[]}
|
aliases={aliases[dn]?aliases[dn]:[]}
|
||||||
{dn}
|
{dn}
|
||||||
|
41
ui/src/lib/components/domains/SubdomainListTiny.svelte
Normal file
41
ui/src/lib/components/domains/SubdomainListTiny.svelte
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<!--
|
||||||
|
This file is part of the happyDomain (R) project.
|
||||||
|
Copyright (c) 2022-2024 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 lang="ts">
|
||||||
|
import type { DomainInList } from '$lib/model/domain';
|
||||||
|
import { fqdn } from '$lib/dns';
|
||||||
|
|
||||||
|
export let domains: Array<string>;
|
||||||
|
export let origin: DomainInList;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each domains as dn}
|
||||||
|
<a
|
||||||
|
href={'#' + (dn?dn:'@')}
|
||||||
|
title={fqdn(dn, origin.domain)}
|
||||||
|
class="d-block text-truncate font-monospace text-muted text-decoration-none"
|
||||||
|
style={'max-width: none; padding-left: ' + (dn === '' ? 0 : (dn.split('.').length * 10)) + 'px'}
|
||||||
|
>
|
||||||
|
{fqdn(dn, origin.domain)}
|
||||||
|
</a>
|
||||||
|
{/each}
|
@ -108,7 +108,8 @@
|
|||||||
"give-explicit-name": "Give an explicit name in order to easily find this service.",
|
"give-explicit-name": "Give an explicit name in order to easily find this service.",
|
||||||
"history": "History",
|
"history": "History",
|
||||||
"list": "List importable domains",
|
"list": "List importable domains",
|
||||||
"n-aliases": "{{n:lt; 2:{{n}} alias; default:{{n}} aliases}}",
|
"n-aliases": "{{count:lt; 2:{{count}} alias; default:{{count}} aliases}}",
|
||||||
|
"n-services": "{{count:lt; 2:{{count}} service; default:{{count}} services}}",
|
||||||
"please-fill-fields": "Please fill the following fields:",
|
"please-fill-fields": "Please fill the following fields:",
|
||||||
"removal": "Confirm Domain Removal",
|
"removal": "Confirm Domain Removal",
|
||||||
"save-modifications": "Save those modifications",
|
"save-modifications": "Save those modifications",
|
||||||
|
@ -108,7 +108,8 @@
|
|||||||
"give-explicit-name": "Donnez un nom explicite afin de trouver facilement ce service.",
|
"give-explicit-name": "Donnez un nom explicite afin de trouver facilement ce service.",
|
||||||
"history": "Historique",
|
"history": "Historique",
|
||||||
"list": "Liste des domaines importables",
|
"list": "Liste des domaines importables",
|
||||||
"n-aliases": "{{n:lt; 2:{{n}} alias; default:{{n}} alias}}",
|
"n-aliases": "{{count:lt; 2:{{count}} alias; default:{{count}} alias}}",
|
||||||
|
"n-services": "{{count:lt; 2:{{count}} service; default:{{count}} services}}",
|
||||||
"please-fill-fields": "Veuillez remplir les champs suivants :",
|
"please-fill-fields": "Veuillez remplir les champs suivants :",
|
||||||
"removal": "Confirmer la suppression du domaine",
|
"removal": "Confirmer la suppression du domaine",
|
||||||
"save-modifications": "Enregistrer ces modifications",
|
"save-modifications": "Enregistrer ces modifications",
|
||||||
|
@ -101,7 +101,7 @@ export class ProviderForm {
|
|||||||
this.nextInProgress = true;
|
this.nextInProgress = true;
|
||||||
this.saveState();
|
this.saveState();
|
||||||
if (this.form && this.form.nextButtonLink) {
|
if (this.form && this.form.nextButtonLink) {
|
||||||
goto(this.form.nextButtonLink);
|
window.location.href = this.form.nextButtonLink;
|
||||||
} else {
|
} else {
|
||||||
this.form = await this.changeState(this.form && this.form.nextButtonState ? this.form.nextButtonState : 0);
|
this.form = await this.changeState(this.form && this.form.nextButtonState ? this.form.nextButtonState : 0);
|
||||||
}
|
}
|
||||||
@ -112,7 +112,7 @@ export class ProviderForm {
|
|||||||
this.previousInProgress = true;
|
this.previousInProgress = true;
|
||||||
this.saveState();
|
this.saveState();
|
||||||
if (this.form && this.form.previousButtonLink) {
|
if (this.form && this.form.previousButtonLink) {
|
||||||
goto(this.form.previousButtonLink);
|
window.location.href = this.form.previousButtonLink;
|
||||||
} else {
|
} else {
|
||||||
this.form = await this.changeState(this.form && this.form.previousButtonState ? this.form.previousButtonState : 0);
|
this.form = await this.changeState(this.form && this.form.previousButtonState ? this.form.previousButtonState : 0);
|
||||||
}
|
}
|
||||||
|
@ -31,6 +31,7 @@ import { refreshDomains } from '$lib/stores/domains';
|
|||||||
|
|
||||||
export const thisZone: Writable<null | Zone> = writable(null);
|
export const thisZone: Writable<null | Zone> = writable(null);
|
||||||
|
|
||||||
|
// sortedDomains returns all subdomains, sorted
|
||||||
export const sortedDomains = derived(
|
export const sortedDomains = derived(
|
||||||
thisZone,
|
thisZone,
|
||||||
($thisZone: null|Zone) => {
|
($thisZone: null|Zone) => {
|
||||||
@ -46,6 +47,36 @@ export const sortedDomains = derived(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// sortedDomainsWithIntermediate returns all subdomains, sorted, with intermediate subdomains
|
||||||
|
export const sortedDomainsWithIntermediate = derived(
|
||||||
|
sortedDomains,
|
||||||
|
($sortedDomains: null|Array<string>) => {
|
||||||
|
if (!$sortedDomains || $sortedDomains.length <= 1) {
|
||||||
|
return $sortedDomains;
|
||||||
|
}
|
||||||
|
const domains: Array<string> = [$sortedDomains[0]];
|
||||||
|
|
||||||
|
let previous = domains[0].split('.');
|
||||||
|
for (let i = 1; i < $sortedDomains.length; i++) {
|
||||||
|
const cur = $sortedDomains[i].split('.');
|
||||||
|
|
||||||
|
if (previous.length < cur.length && previous[0] !== cur[cur.length - previous.length]) {
|
||||||
|
domains.push(cur.slice(cur.length - previous.length).join('.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
while (previous.length + 1 < cur.length) {
|
||||||
|
previous = cur.slice(cur.length - previous.length - 1);
|
||||||
|
domains.push(previous.join('.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
domains.push(cur.join('.'));
|
||||||
|
previous = cur;
|
||||||
|
}
|
||||||
|
|
||||||
|
return domains;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export async function getZone(domain: DomainInList | Domain, zoneId: string) {
|
export async function getZone(domain: DomainInList | Domain, zoneId: string) {
|
||||||
thisZone.set(null);
|
thisZone.set(null);
|
||||||
|
|
||||||
|
@ -57,11 +57,12 @@
|
|||||||
import ModalViewZone, { controls as ctrlViewZone } from '$lib/components/ModalViewZone.svelte';
|
import ModalViewZone, { controls as ctrlViewZone } from '$lib/components/ModalViewZone.svelte';
|
||||||
import NewSubdomainPath, { controls as ctrlNewSubdomain } from '$lib/components/NewSubdomainPath.svelte';
|
import NewSubdomainPath, { controls as ctrlNewSubdomain } from '$lib/components/NewSubdomainPath.svelte';
|
||||||
import NewSubdomainModal from '$lib/components/domains/NewSubdomainModal.svelte';
|
import NewSubdomainModal from '$lib/components/domains/NewSubdomainModal.svelte';
|
||||||
import { fqdn } from '$lib/dns';
|
import SubdomainListTiny from '$lib/components/domains/SubdomainListTiny.svelte';
|
||||||
|
import { fqdn, isReverseZone } from '$lib/dns';
|
||||||
import type { Domain, DomainInList } from '$lib/model/domain';
|
import type { Domain, DomainInList } from '$lib/model/domain';
|
||||||
import type { ZoneMeta } from '$lib/model/zone';
|
import type { ZoneMeta } from '$lib/model/zone';
|
||||||
import { domains, domains_by_groups, domains_idx, refreshDomains } from '$lib/stores/domains';
|
import { domains, domains_by_groups, domains_idx, refreshDomains } from '$lib/stores/domains';
|
||||||
import { retrieveZone as StoreRetrieveZone, sortedDomains, thisZone } from '$lib/stores/thiszone';
|
import { retrieveZone as StoreRetrieveZone, sortedDomains, sortedDomainsWithIntermediate, thisZone } from '$lib/stores/thiszone';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
|
||||||
export let data: {domain: DomainInList;};
|
export let data: {domain: DomainInList;};
|
||||||
@ -175,7 +176,7 @@
|
|||||||
<Icon name="chevron-left" />
|
<Icon name="chevron-left" />
|
||||||
Retour à la zone
|
Retour à la zone
|
||||||
</Button>
|
</Button>
|
||||||
{:else if $page.data.streamed && $sortedDomains}
|
{:else if $page.data.streamed && $sortedDomainsWithIntermediate}
|
||||||
<div class="d-flex gap-2 pb-2 sticky-top bg-light" style="padding-top: 10px">
|
<div class="d-flex gap-2 pb-2 sticky-top bg-light" style="padding-top: 10px">
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
type="button"
|
||||||
@ -250,16 +251,17 @@
|
|||||||
</div>
|
</div>
|
||||||
{#await $page.data.streamed.zone then z}
|
{#await $page.data.streamed.zone then z}
|
||||||
<div style="min-height:0; overflow-y: auto;">
|
<div style="min-height:0; overflow-y: auto;">
|
||||||
{#each $sortedDomains as dn}
|
{#if isReverseZone(data.domain.domain)}
|
||||||
<a
|
<SubdomainListTiny
|
||||||
href={'#' + (dn?dn:'@')}
|
domains={$sortedDomains}
|
||||||
title={fqdn(dn, data.domain.domain)}
|
origin={data.domain}
|
||||||
class="d-block text-truncate font-monospace text-muted text-decoration-none"
|
/>
|
||||||
style={'max-width: none; padding-left: ' + (dn === '' ? 0 : (dn.split('.').length * 10)) + 'px'}
|
{:else}
|
||||||
>
|
<SubdomainListTiny
|
||||||
{fqdn(dn, data.domain.domain)}
|
domains={$sortedDomainsWithIntermediate}
|
||||||
</a>
|
origin={data.domain}
|
||||||
{/each}
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/await}
|
{/await}
|
||||||
{/if}
|
{/if}
|
||||||
@ -267,7 +269,7 @@
|
|||||||
<div class="flex-fill" />
|
<div class="flex-fill" />
|
||||||
|
|
||||||
{#if $page.data.isZonePage && data.domain.zone_history && $domains_idx[selectedDomain] && data.domain.id === $domains_idx[selectedDomain].id}
|
{#if $page.data.isZonePage && data.domain.zone_history && $domains_idx[selectedDomain] && data.domain.id === $domains_idx[selectedDomain].id}
|
||||||
{#if !($page.data.streamed && $sortedDomains)}
|
{#if !($page.data.streamed && $sortedDomainsWithIntermediate)}
|
||||||
<Button
|
<Button
|
||||||
color="danger"
|
color="danger"
|
||||||
class="mt-3"
|
class="mt-3"
|
||||||
|
@ -35,7 +35,7 @@
|
|||||||
import type { Zone } from '$lib/model/zone';
|
import type { Zone } from '$lib/model/zone';
|
||||||
import { domains_idx } from '$lib/stores/domains';
|
import { domains_idx } from '$lib/stores/domains';
|
||||||
import { servicesSpecs, refreshServicesSpecs } from '$lib/stores/services';
|
import { servicesSpecs, refreshServicesSpecs } from '$lib/stores/services';
|
||||||
import { retrieveZone, sortedDomains, thisZone } from '$lib/stores/thiszone';
|
import { retrieveZone, sortedDomains, sortedDomainsWithIntermediate, thisZone } from '$lib/stores/thiszone';
|
||||||
import { t } from '$lib/translations';
|
import { t } from '$lib/translations';
|
||||||
|
|
||||||
if (!$servicesSpecs) refreshServicesSpecs();
|
if (!$servicesSpecs) refreshServicesSpecs();
|
||||||
@ -65,6 +65,7 @@
|
|||||||
<SubdomainList
|
<SubdomainList
|
||||||
origin={data.domain}
|
origin={data.domain}
|
||||||
sortedDomains={$sortedDomains}
|
sortedDomains={$sortedDomains}
|
||||||
|
sortedDomainsWithIntermediate={$sortedDomainsWithIntermediate}
|
||||||
zone={$thisZone}
|
zone={$thisZone}
|
||||||
on:update-zone-services={(event) => thisZone.set(event.detail)}
|
on:update-zone-services={(event) => thisZone.set(event.detail)}
|
||||||
/>
|
/>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user