Files
inbuxa-admin/src/lib/layout.ts
T
jcoffey-dev 5641560a91 Guided wizards, opt-in every time, and automatic DNS as the first
A job that has a wizard now asks "Guide me / I'll do it myself" each time
it starts; nothing is remembered. The shared wizard shell gives every guide
a stepper, a side panel on what each step does and how to undo it, and the
way forward or back.

Automatic DNS, from a domain's DNS section:
- finds where the domain's DNS is hosted from its SOA and NS records, and
  offers that host when the server can drive it;
- for the major hosts, steps to create the narrowest credential, and the
  field named as the steps name it;
- records grouped by what they do, TLSA off unless the zone is signed;
- saves the provider and switches the domain over, removing the provider
  again if the switch fails;
- watches the publishing task and public DNS, ticking each record green,
  and boils a host's refusal down to its distinct messages;
- for hosts it can't drive, or domains not in DNS yet, every record laid
  out for copying, with the same live checks.
2026-09-19 01:41:00 -07:00

202 lines
6.0 KiB
TypeScript

/*
* SPDX-FileCopyrightText: 2020 Stalwart Labs LLC <[email protected]>
* SPDX-FileCopyrightText: 2026 Coffey Labs
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-SEL
*/
import type { Layout, LayoutItem, LayoutSubItem, Schema } from '@/types/schema';
import { resolveObject } from '@/lib/schemaResolver';
function findFirstSubLink(items: LayoutSubItem[]): string | null {
for (const item of items) {
if (item.type === 'link') return item.viewName;
if (item.type === 'container') {
const found = findFirstSubLink(item.items);
if (found) return found;
}
}
return null;
}
export function findFirstLinkInLayout(items: LayoutItem[] | Layout): string | null {
const list = Array.isArray(items) ? items : items.items;
for (const item of list) {
if ('link' in item) return item.link.viewName;
if ('container' in item) {
const found = findFirstSubLink(item.container.items);
if (found) return found;
}
}
return null;
}
export type CanGet = (permissionPrefix: string) => boolean;
export type HasPermission = (permission: string) => boolean;
interface SpecialLinkInfo {
visible: boolean;
enterprise: boolean;
}
function checkSpecialLink(
viewName: string,
edition: string,
hasPerm?: HasPermission,
canGet?: CanGet,
): SpecialLinkInfo | null {
if (viewName.startsWith('Dashboard/') || viewName === 'CustomComponent/Dashboard') {
if (edition === 'oss') return { visible: false, enterprise: true };
const hasLiveMetrics = hasPerm ? hasPerm('liveMetrics') : true;
const hasTraceGet = canGet ? canGet('sysTrace') : true;
return { visible: hasLiveMetrics && hasTraceGet, enterprise: true };
}
if (viewName === 'CustomComponent/LiveDelivery') {
const allowed = hasPerm ? hasPerm('liveDeliveryTest') : true;
return { visible: allowed, enterprise: false };
}
if (viewName === 'CustomComponent/LiveTracing') {
if (edition === 'oss') return { visible: false, enterprise: true };
const allowed = hasPerm ? hasPerm('liveTracing') : true;
return { visible: allowed, enterprise: true };
}
if (viewName.startsWith('CustomComponent/')) {
return { visible: true, enterprise: false };
}
// INBUXA: guided jobs are open to whoever may manage what they change.
if (viewName.startsWith('Wizard/dns/')) {
return { visible: canGet ? canGet('sysDomain') : true, enterprise: false };
}
return null;
}
export function isLinkVisible(
schema: Schema,
viewName: string,
edition: string,
canGet: CanGet,
hasPerm?: HasPermission,
): boolean {
const special = checkSpecialLink(viewName, edition, hasPerm, canGet);
if (special !== null) return special.visible;
const obj = schema.objects[viewName];
if (!obj) return false;
const resolved = resolveObject(schema, viewName);
if (!resolved) return false;
if (!canGet(resolved.permissionPrefix)) return false;
if (resolved.enterprise && edition === 'oss') return false;
return true;
}
export function isLinkEnterprise(schema: Schema, viewName: string, edition: string): boolean {
const special = checkSpecialLink(viewName, edition);
if (special !== null) return special.enterprise;
const obj = schema.objects[viewName];
if (!obj) return false;
if (obj.type === 'view') {
const parent = schema.objects[obj.objectName];
return parent?.type !== 'view' && parent?.enterprise === true;
}
return obj.enterprise === true;
}
function findFirstVisibleSubLink(
schema: Schema,
items: LayoutSubItem[],
edition: string,
canGet: CanGet,
hasPerm?: HasPermission,
): string | null {
for (const item of items) {
if (item.type === 'link') {
if (isLinkVisible(schema, item.viewName, edition, canGet, hasPerm)) return item.viewName;
} else if (item.type === 'container') {
const found = findFirstVisibleSubLink(schema, item.items, edition, canGet, hasPerm);
if (found) return found;
}
}
return null;
}
export function findFirstVisibleLinkInLayout(
schema: Schema,
layout: Layout,
edition: string,
canGet: CanGet,
hasPerm?: HasPermission,
): string | null {
for (const item of layout.items) {
if ('link' in item) {
if (isLinkVisible(schema, item.link.viewName, edition, canGet, hasPerm)) return item.link.viewName;
} else if ('container' in item) {
const found = findFirstVisibleSubLink(schema, item.container.items, edition, canGet, hasPerm);
if (found) return found;
}
}
return null;
}
export function isLinkAccessible(
schema: Schema,
viewName: string,
edition: string,
canGet: CanGet,
hasPerm?: HasPermission,
): boolean {
if (!isLinkVisible(schema, viewName, edition, canGet, hasPerm)) return false;
if (edition === 'community' && isLinkEnterprise(schema, viewName, edition)) return false;
return true;
}
function findFirstAccessibleSubLink(
schema: Schema,
items: LayoutSubItem[],
edition: string,
canGet: CanGet,
hasPerm?: HasPermission,
): string | null {
for (const item of items) {
if (item.type === 'link') {
if (isLinkAccessible(schema, item.viewName, edition, canGet, hasPerm)) return item.viewName;
} else if (item.type === 'container') {
const found = findFirstAccessibleSubLink(schema, item.items, edition, canGet, hasPerm);
if (found) return found;
}
}
return null;
}
export function findFirstAccessibleLinkInLayout(
schema: Schema,
layout: Layout,
edition: string,
canGet: CanGet,
hasPerm?: HasPermission,
): string | null {
for (const item of layout.items) {
if ('link' in item) {
if (isLinkAccessible(schema, item.link.viewName, edition, canGet, hasPerm)) return item.link.viewName;
} else if ('container' in item) {
const found = findFirstAccessibleSubLink(schema, item.container.items, edition, canGet, hasPerm);
if (found) return found;
}
}
return null;
}
export function visibleLayouts(schema: Schema, edition: string, canGet: CanGet, hasPerm?: HasPermission): Layout[] {
return schema.layouts.filter(
(layout) => findFirstVisibleLinkInLayout(schema, layout, edition, canGet, hasPerm) !== null,
);
}