Show registered domains on hosting resource pages

The resource detail now lists the domain records linked to the hosting
account (term, registration status), so customers see the domains
included in their package. Prices and procurement state are staff-only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Kundencenter 2026-10-01 11:54:46 +02:00
parent dad9528295
commit 1178e9edfe
2 changed files with 15 additions and 3 deletions

View file

@ -4,13 +4,15 @@ import Link from 'next/link';
import { useParams } from 'next/navigation';
import { api, errMsg } from '@/lib/api';
import { useSession } from '@/lib/session';
import { ACTION_LABEL, Alert, Field, JobStatus, ResState, fmt, CUSTOMER_ACTIONS } from '@/components/ui';
import { ACTION_LABEL, Alert, Field, JobStatus, ResState, fmt, eur, CUSTOMER_ACTIONS } from '@/components/ui';
import { SecretField } from '@/components/SecretField';
import { uuid } from '@/lib/uuid';
import { HostingPanel, UsageBars } from '@/components/HostingPanel';
interface Det { id: string; type: string; name: string; state: string; orgId: string | null; validFrom: string | null; validUntil: string | null; syncedAt: string; stale: boolean; staleReason: string | null; missing: boolean; instance?: string; connector?: string;
canReveal?: boolean; hasChildren?: boolean; canLogin?: boolean; limits: Record<string, unknown>; usage: Record<string, unknown>; details: Record<string, any>; allowedActions: string[]; customerActions?: string[]; jobs: { id: string; action: string; status: string; error: string | null; createdAt: string }[] }
canReveal?: boolean; hasChildren?: boolean; canLogin?: boolean; limits: Record<string, unknown>; usage: Record<string, unknown>; details: Record<string, any>; allowedActions: string[]; customerActions?: string[]; domains: DomainRec[]; jobs: { id: string; action: string; status: string; error: string | null; createdAt: string }[] }
interface DomainRec { id: string; domain: string; termMonths: number | null; registered: boolean; procurement?: string; sellGrossCents?: number | null }
const PROCUREMENT: Record<string, string> = { open: 'noch nicht bestellt', ordered: 'beim Registrar bestellt', external: 'extern verwaltet' };
const LIMIT_LABEL: Record<string, string> = { users: 'Benutzer', storageMb: 'Speicher (MB)' };
interface PanelCreds { canReset: boolean; set: boolean; setAt: string | null }
export default function Ressource() {
@ -74,6 +76,12 @@ export default function Ressource() {
<div><dt className="muted small">Gerät</dt><dd className="mono" style={{ margin: 0 }}>{d.details.activation.hardwareIdMasked}</dd></div></> : d.type === 'license' ? <div><dt className="muted small">Aktivierung</dt><dd style={{ margin: 0 }}>Noch nicht aktiviert</dd></div> : null}
</dl>
<p className="small muted" style={{ marginTop: 16 }}>Stand: {fmt(d.syncedAt)}{staff && d.instance ? ` · Verbindung: ${d.instance}` : ''}</p></div>
{d.domains.length > 0 && <div className="card"><h2>Domains</h2>
<div className="tablewrap"><table><thead><tr><th>Domain</th><th>Laufzeit</th><th>Status</th>{staff && <th>Preis (brutto)</th>}</tr></thead><tbody>
{d.domains.map((x) => <tr key={x.id}><td>{x.domain}</td><td>{x.termMonths ? `${x.termMonths} Monate` : '–'}</td>
<td>{staff ? PROCUREMENT[x.procurement ?? ''] ?? x.procurement : x.registered ? 'Registriert' : 'In Bearbeitung'}</td>
{staff && <td>{x.sellGrossCents != null ? eur(x.sellGrossCents) : '–'}</td>}</tr>)}</tbody></table></div>
{staff && <p className="small muted" style={{ marginTop: 8 }}><Link href="/admin/domains">Zur Domain-Aufstellung</Link></p>}</div>}
{d.type === 'hosting_account' && <UsageBars usage={d.usage} limits={d.limits} />}
{(d.hasChildren || d.canLogin) && <HostingPanel resourceId={d.id} canLogin={!!d.canLogin} />}
{d.canReveal && <div className="card"><h2>Lizenzschlüssel</h2><SecretField resourceId={d.id} /></div>}