Include intermediate subdomains without service in list

Closes: https://github.com/happyDomain/happydomain/issues/26
This commit is contained in:
nemunaire 2024-02-08 12:52:05 +01:00
parent e491485773
commit 3211f8e6f9
6 changed files with 129 additions and 17 deletions

View File

@ -82,7 +82,43 @@
}
</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}>
<h2
class="sticky-top"

View File

@ -36,6 +36,7 @@
export let origin: DomainInList | Domain;
export let sortedDomains: Array<string>;
export let sortedDomainsWithIntermediate: Array<string>;
export let zone: Zone;
let aliases: Record<string, Array<string>>;
@ -69,7 +70,7 @@
}
</script>
{#each sortedDomains as dn}
{#each sortedDomainsWithIntermediate as dn}
<SubdomainItem
aliases={aliases[dn]?aliases[dn]:[]}
{dn}

View 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}

View File

@ -31,6 +31,7 @@ import { refreshDomains } from '$lib/stores/domains';
export const thisZone: Writable<null | Zone> = writable(null);
// sortedDomains returns all subdomains, sorted
export const sortedDomains = derived(
thisZone,
($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) {
thisZone.set(null);

View File

@ -57,11 +57,12 @@
import ModalViewZone, { controls as ctrlViewZone } from '$lib/components/ModalViewZone.svelte';
import NewSubdomainPath, { controls as ctrlNewSubdomain } from '$lib/components/NewSubdomainPath.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 { ZoneMeta } from '$lib/model/zone';
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';
export let data: {domain: DomainInList;};
@ -175,7 +176,7 @@
<Icon name="chevron-left" />
Retour à la zone
</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">
<Button
type="button"
@ -250,16 +251,17 @@
</div>
{#await $page.data.streamed.zone then z}
<div style="min-height:0; overflow-y: auto;">
{#each $sortedDomains as dn}
<a
href={'#' + (dn?dn:'@')}
title={fqdn(dn, data.domain.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, data.domain.domain)}
</a>
{/each}
{#if isReverseZone(data.domain.domain)}
<SubdomainListTiny
domains={$sortedDomains}
origin={data.domain}
/>
{:else}
<SubdomainListTiny
domains={$sortedDomainsWithIntermediate}
origin={data.domain}
/>
{/if}
</div>
{/await}
{/if}
@ -267,7 +269,7 @@
<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.streamed && $sortedDomains)}
{#if !($page.data.streamed && $sortedDomainsWithIntermediate)}
<Button
color="danger"
class="mt-3"

View File

@ -35,7 +35,7 @@
import type { Zone } from '$lib/model/zone';
import { domains_idx } from '$lib/stores/domains';
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';
if (!$servicesSpecs) refreshServicesSpecs();
@ -65,6 +65,7 @@
<SubdomainList
origin={data.domain}
sortedDomains={$sortedDomains}
sortedDomainsWithIntermediate={$sortedDomainsWithIntermediate}
zone={$thisZone}
on:update-zone-services={(event) => thisZone.set(event.detail)}
/>