mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-08-23 07:53:36 +00:00
Add Discovery provenance markers
This commit is contained in:
parent
ba896a5366
commit
f83d25d155
13 changed files with 142 additions and 26 deletions
|
|
@ -1400,6 +1400,11 @@ AI runtime.
|
|||
"Observed by Discovery" context and pass suggested URLs into the shared
|
||||
web-interface field. Persisted/manual web-interface metadata remains the
|
||||
only row-link source until the operator explicitly adopts a suggested URL.
|
||||
Discovery-sourced values rendered outside the Discovery tab must carry the
|
||||
shared compact provenance marker from
|
||||
`frontend-modern/src/components/shared/DiscoveryProvenanceMarker.tsx`, so
|
||||
operators can distinguish opt-in Discovery context from API-owned resource
|
||||
facts without reading a drawer-specific explanation.
|
||||
|
||||
## Current State
|
||||
|
||||
|
|
@ -3158,6 +3163,10 @@ surfacing "Unknown" rows or zero-confidence noise. Manual/persisted
|
|||
web-interface URLs still win: Discovery suggestions may be copied, opened, or
|
||||
adopted through the shared `WebInterfaceUrlField`, but they must not silently
|
||||
replace metadata or make row-name links active until the operator saves them.
|
||||
The visible provenance marker for those values is the shared
|
||||
`DiscoveryProvenanceMarker`; local surfaces may choose the labelled or
|
||||
icon-only variant, but must not invent alternate Discovery badges or hide the
|
||||
source on compact cards.
|
||||
That same settings-shell boundary now also owns the shared settings
|
||||
presentation helpers that those panels consume. `frontend-modern/src/utils/systemSettingsPresentation.ts`
|
||||
is the canonical owner for shared system-settings presets, summaries, and
|
||||
|
|
|
|||
|
|
@ -1581,3 +1581,7 @@ dependency, row-time endpoint probe, or auto-save mutation. Suggested
|
|||
URLs remain copy/open/adopt affordances only; persisting them as a
|
||||
workload link stays a user action through the existing Web Interface URL
|
||||
field.
|
||||
The compact provenance marker for those values is presentation-only. It
|
||||
may identify already-loaded Discovery fields, but must not introduce
|
||||
additional discovery fetches, provider lookups, endpoint probes, or
|
||||
per-row layout measurement on the Workloads hot path.
|
||||
|
|
|
|||
|
|
@ -1696,6 +1696,10 @@ continue to use the operator-saved web-interface URL. Version, config-path,
|
|||
port, and endpoint facts surfaced outside the Discovery sub-tab must be
|
||||
labelled as Discovery-observed so API-owned resource facts and command-derived
|
||||
facts remain distinguishable.
|
||||
That label must be visible through the shared Discovery provenance marker on
|
||||
compact cards, suggested URL panels, and other out-of-tab Discovery values
|
||||
rather than buried in helper text or inferred from the tab where the operator
|
||||
found the value.
|
||||
Command-availability guidance inside that analysis surface must consume the
|
||||
shared `frontend-modern/src/utils/discoveryPresentation.ts` settings targets
|
||||
and scan-error copy instead of hard-coding legacy settings labels such as
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import {
|
|||
getDiscoverySuggestedURLHeadingClass,
|
||||
getDiscoverySuggestedURLTextClass,
|
||||
} from '@/utils/discoveryPresentation';
|
||||
import { DiscoveryProvenanceMarker } from '@/components/shared/DiscoveryProvenanceMarker';
|
||||
import {
|
||||
DISCOVERY_ANALYSIS_EXPLANATION,
|
||||
DISCOVERY_ANALYSIS_REASONING_LABEL,
|
||||
|
|
@ -661,11 +662,15 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
|||
<div class="rounded border border-border bg-surface p-3 shadow-sm">
|
||||
<div class="flex items-start justify-between">
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-base-content">
|
||||
{d().service_name || 'Unknown Service'}
|
||||
</h3>
|
||||
<div class="flex flex-wrap items-center gap-1.5">
|
||||
<h3 class="text-sm font-semibold text-base-content">
|
||||
{d().service_name || 'Unknown Service'}
|
||||
</h3>
|
||||
<DiscoveryProvenanceMarker />
|
||||
</div>
|
||||
<Show when={d().service_version}>
|
||||
<div class="mt-1 flex items-center gap-2">
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
<span class="text-xs text-muted">Version {d().service_version}</span>
|
||||
<CopyValueButton
|
||||
value={d().service_version}
|
||||
|
|
@ -690,9 +695,7 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
|||
</p>
|
||||
</Show>
|
||||
<div class="mt-3 flex flex-wrap gap-2 text-[10px] text-muted">
|
||||
<span class="rounded border border-border bg-surface-alt px-2 py-0.5">
|
||||
{getDiscoveryObservedSourceLabel()}
|
||||
</span>
|
||||
<DiscoveryProvenanceMarker label={getDiscoveryObservedSourceLabel()} />
|
||||
<Show when={d().updated_at}>
|
||||
<span class="rounded border border-border bg-surface-alt px-2 py-0.5">
|
||||
Last observed {formatDiscoveryAge(d().updated_at)}
|
||||
|
|
@ -706,7 +709,10 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
|||
|
||||
<Show when={d().suggested_url || d().suggested_url_diagnostic}>
|
||||
<div class={getDiscoverySuggestedURLCardClass()}>
|
||||
<div class={getDiscoverySuggestedURLHeadingClass()}>Web Interface Suggestion</div>
|
||||
<div class={`${getDiscoverySuggestedURLHeadingClass()} flex items-center gap-1.5`}>
|
||||
<span>Web Interface Suggestion</span>
|
||||
<DiscoveryProvenanceMarker />
|
||||
</div>
|
||||
<Show
|
||||
when={d().suggested_url}
|
||||
fallback={
|
||||
|
|
@ -756,8 +762,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
|||
{/* CLI Access */}
|
||||
<Show when={d().cli_access}>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-base-content mb-2">
|
||||
CLI Access
|
||||
<div class="mb-2 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>CLI Access</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
</div>
|
||||
<CopyableCodeRow
|
||||
value={d().cli_access}
|
||||
|
|
@ -779,8 +786,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
|||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<Show when={d().config_paths?.length > 0}>
|
||||
<div class="mb-3">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-base-content mb-1">
|
||||
Config Paths
|
||||
<div class="mb-1 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>Config Paths</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<For each={d().config_paths}>
|
||||
|
|
@ -798,8 +806,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
|||
</Show>
|
||||
<Show when={d().data_paths?.length > 0}>
|
||||
<div class="mb-3">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-base-content mb-1">
|
||||
Data Paths
|
||||
<div class="mb-1 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>Data Paths</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<For each={d().data_paths}>
|
||||
|
|
@ -817,8 +826,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
|||
</Show>
|
||||
<Show when={d().log_paths?.length > 0}>
|
||||
<div>
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-base-content mb-1">
|
||||
Log Paths
|
||||
<div class="mb-1 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>Log Paths</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<For each={d().log_paths}>
|
||||
|
|
@ -840,8 +850,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
|||
{/* Ports */}
|
||||
<Show when={d().ports?.length > 0}>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-base-content mb-2">
|
||||
Listening Ports
|
||||
<div class="mb-2 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>Listening Ports</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
</div>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<For each={d().ports}>
|
||||
|
|
@ -877,8 +888,9 @@ export const DiscoveryTab: Component<DiscoveryTabProps> = (props) => {
|
|||
{/* Key Facts */}
|
||||
<Show when={d().facts?.length > 0}>
|
||||
<div class="rounded border border-border p-3 shadow-sm">
|
||||
<div class="text-[11px] font-medium uppercase tracking-wide text-base-content mb-2">
|
||||
Discovered Facts
|
||||
<div class="mb-2 flex items-center gap-1.5 text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
<span>Discovered Facts</span>
|
||||
<DiscoveryProvenanceMarker showLabel={false} />
|
||||
</div>
|
||||
<div class="space-y-1.5">
|
||||
<For each={d().facts.slice(0, 8)}>
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ vi.mock('@/utils/clipboard', () => ({
|
|||
import * as discoveryApi from '@/api/discovery';
|
||||
import { DiscoveryTab } from '@/components/Discovery/DiscoveryTab';
|
||||
import { copyToClipboard } from '@/utils/clipboard';
|
||||
import { getDiscoveryProvenanceTitle } from '@/utils/discoveryPresentation';
|
||||
|
||||
describe('DiscoveryTab', () => {
|
||||
afterEach(() => {
|
||||
|
|
@ -176,6 +177,7 @@ describe('DiscoveryTab', () => {
|
|||
expect(await screen.findByText('Analysis: Cloud (Anthropic)')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Observed by Discovery')).toBeInTheDocument();
|
||||
expect(await screen.findByText('Available to Pulse Assistant')).toBeInTheDocument();
|
||||
expect(screen.getAllByLabelText(getDiscoveryProvenanceTitle()).length).toBeGreaterThan(1);
|
||||
expect(await screen.findByText('Analysis Reasoning')).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: 'Open suggested URL' })).toHaveAttribute(
|
||||
'href',
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import type { WorkloadGuest } from '@/types/workloads';
|
|||
import type { Memory, Disk, GuestNetworkInterface } from '@/types/api';
|
||||
import { resetCreateNonSuspendingQueryCacheForTest } from '@/hooks/createNonSuspendingQuery';
|
||||
import { getCanonicalWorkloadId } from '@/utils/workloads';
|
||||
import { getDiscoveryProvenanceTitle } from '@/utils/discoveryPresentation';
|
||||
|
||||
// ── Mocks ──────────────────────────────────────────────────────────────
|
||||
|
||||
|
|
@ -226,6 +227,7 @@ describe('GuestDrawer', () => {
|
|||
expect(screen.getByText('web_server')).toBeInTheDocument();
|
||||
expect(screen.getByText('0.9.0')).toBeInTheDocument();
|
||||
expect(screen.getByText('95%')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(getDiscoveryProvenanceTitle())).toBeInTheDocument();
|
||||
expect(screen.getAllByText('http://192.0.2.10:3000').length).toBeGreaterThan(0);
|
||||
expect(screen.getByText('docker exec -it homepage /bin/sh')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('url-suggested')).toHaveTextContent('http://192.0.2.10:3000');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { For, Show } from 'solid-js';
|
||||
|
||||
import { formatDiscoveryAge } from '@/api/discovery';
|
||||
import { DiscoveryProvenanceMarker } from '@/components/shared/DiscoveryProvenanceMarker';
|
||||
import { WebInterfaceUrlField } from '@/components/shared/WebInterfaceUrlField';
|
||||
import type { DiscoveryIdentifiedSummary } from '@/utils/discoveryPresentation';
|
||||
import { formatBytes, formatUptime } from '@/utils/format';
|
||||
|
|
@ -45,10 +46,13 @@ export function GuestDrawerOverview(props: GuestDrawerOverviewProps) {
|
|||
{(summary) => (
|
||||
<div class="rounded border border-border bg-surface p-3 shadow-sm">
|
||||
<div class="flex items-center justify-between gap-2 mb-2">
|
||||
<h3 class="text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
Identified Service
|
||||
</h3>
|
||||
<span class="text-[10px] font-medium text-muted">
|
||||
<div class="flex min-w-0 items-center gap-1.5">
|
||||
<h3 class="truncate text-[11px] font-medium uppercase tracking-wide text-base-content">
|
||||
Identified Service
|
||||
</h3>
|
||||
<DiscoveryProvenanceMarker />
|
||||
</div>
|
||||
<span class="shrink-0 text-[10px] font-medium text-muted">
|
||||
{summary().confidencePercent}
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
import { Show, type Component } from 'solid-js';
|
||||
import ScanSearchIcon from 'lucide-solid/icons/scan-search';
|
||||
|
||||
import {
|
||||
getDiscoveryProvenanceBadgeClass,
|
||||
getDiscoveryProvenanceIconClass,
|
||||
getDiscoveryProvenanceLabel,
|
||||
getDiscoveryProvenanceTitle,
|
||||
} from '@/utils/discoveryPresentation';
|
||||
|
||||
interface DiscoveryProvenanceMarkerProps {
|
||||
showLabel?: boolean;
|
||||
label?: string;
|
||||
title?: string;
|
||||
class?: string;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
export const DiscoveryProvenanceMarker: Component<DiscoveryProvenanceMarkerProps> = (props) => {
|
||||
const showLabel = () => props.showLabel !== false;
|
||||
const title = () => props.title || getDiscoveryProvenanceTitle();
|
||||
const label = () => props.label || getDiscoveryProvenanceLabel();
|
||||
const className = () =>
|
||||
props.class ||
|
||||
(showLabel() ? getDiscoveryProvenanceBadgeClass() : getDiscoveryProvenanceIconClass());
|
||||
|
||||
return (
|
||||
<span class={className()} title={title()} aria-label={title()} data-testid={props.testId}>
|
||||
<ScanSearchIcon class="h-3 w-3" aria-hidden="true" />
|
||||
<Show when={showLabel()}>
|
||||
<span>{label()}</span>
|
||||
</Show>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default DiscoveryProvenanceMarker;
|
||||
|
|
@ -2,6 +2,7 @@ import { Component, Show } from 'solid-js';
|
|||
import CheckIcon from 'lucide-solid/icons/check';
|
||||
import CopyIcon from 'lucide-solid/icons/copy';
|
||||
import ExternalLinkIcon from 'lucide-solid/icons/external-link';
|
||||
import { DiscoveryProvenanceMarker } from './DiscoveryProvenanceMarker';
|
||||
import { useWebInterfaceUrlFieldState } from './useWebInterfaceUrlFieldState';
|
||||
import type { WebInterfaceUrlFieldProps } from './webInterfaceUrlFieldModel';
|
||||
|
||||
|
|
@ -105,15 +106,19 @@ export const WebInterfaceUrlField: Component<WebInterfaceUrlFieldProps> = (props
|
|||
|
||||
<Show when={state.showSuggestedDiagnostic()}>
|
||||
<div class="mt-2 rounded border border-amber-200 bg-amber-50 p-2 text-[11px] text-amber-800 dark:border-amber-800 dark:bg-amber-900 dark:text-amber-200">
|
||||
<p class="font-medium">{state.suggestedUrlFallback().title}</p>
|
||||
<div class="flex items-center gap-1.5 font-medium">
|
||||
<span>{state.suggestedUrlFallback().title}</span>
|
||||
<DiscoveryProvenanceMarker />
|
||||
</div>
|
||||
<p class="mt-0.5">{state.suggestedUrlFallback().description}</p>
|
||||
</div>
|
||||
</Show>
|
||||
|
||||
<Show when={state.showSuggestedUrl()}>
|
||||
<div class="mt-2 p-2 rounded bg-blue-50 border border-blue-200 dark:bg-blue-900 dark:border-blue-800">
|
||||
<div class="text-[10px] font-medium text-blue-700 dark:text-blue-300 mb-1">
|
||||
{state.normalizedCurrentUrl() ? 'Discovered URL' : 'Suggested URL'}
|
||||
<div class="mb-1 flex items-center gap-1.5 text-[10px] font-medium text-blue-700 dark:text-blue-300">
|
||||
<span>{state.normalizedCurrentUrl() ? 'Discovered URL' : 'Suggested URL'}</span>
|
||||
<DiscoveryProvenanceMarker />
|
||||
</div>
|
||||
<Show when={props.suggestedUrlReasonText}>
|
||||
<p
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ vi.mock('@/utils/clipboard', () => ({
|
|||
import { AgentMetadataAPI } from '@/api/agentMetadata';
|
||||
import { WebInterfaceUrlField } from '@/components/shared/WebInterfaceUrlField';
|
||||
import { copyToClipboard } from '@/utils/clipboard';
|
||||
import { getDiscoveryProvenanceTitle } from '@/utils/discoveryPresentation';
|
||||
|
||||
describe('WebInterfaceUrlField', () => {
|
||||
afterEach(() => {
|
||||
|
|
@ -119,6 +120,7 @@ describe('WebInterfaceUrlField', () => {
|
|||
|
||||
expect(await screen.findByText('No suggested URL available')).toBeInTheDocument();
|
||||
expect(screen.getByText('No management interface could be inferred.')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(getDiscoveryProvenanceTitle())).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('offers discovered URL copy, open, and adopt actions without saving automatically', async () => {
|
||||
|
|
@ -133,6 +135,7 @@ describe('WebInterfaceUrlField', () => {
|
|||
));
|
||||
|
||||
expect(await screen.findByText('Suggested URL')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText(getDiscoveryProvenanceTitle())).toBeInTheDocument();
|
||||
expect(screen.getByText('http://192.0.2.10:8123')).toBeInTheDocument();
|
||||
expect(screen.getByRole('link', { name: 'Open suggested URL' })).toHaveAttribute(
|
||||
'href',
|
||||
|
|
|
|||
|
|
@ -6,6 +6,10 @@ import {
|
|||
getDiscoveryInitialEmptyState,
|
||||
getDiscoveryLoadingState,
|
||||
getDiscoveryNoConnectedAgentMessage,
|
||||
getDiscoveryProvenanceBadgeClass,
|
||||
getDiscoveryProvenanceIconClass,
|
||||
getDiscoveryProvenanceLabel,
|
||||
getDiscoveryProvenanceTitle,
|
||||
getNetworkDiscoveryModePresentation,
|
||||
getDiscoveryNotesEmptyState,
|
||||
getDiscoveryObservedSourceLabel,
|
||||
|
|
@ -60,6 +64,10 @@ describe('discoveryPresentation', () => {
|
|||
expect(getDiscoveryCategoryBadgeClass()).toContain('text-blue-700');
|
||||
expect(getDiscoverySuggestedURLCardClass()).toContain('bg-blue-50');
|
||||
expect(getDiscoverySuggestedURLActionClass()).toContain('text-blue-700');
|
||||
expect(getDiscoveryProvenanceLabel()).toBe('Discovery');
|
||||
expect(getDiscoveryProvenanceTitle()).toContain('opt-in Discovery');
|
||||
expect(getDiscoveryProvenanceBadgeClass()).toContain('border-cyan-200');
|
||||
expect(getDiscoveryProvenanceIconClass()).toContain('h-4');
|
||||
});
|
||||
|
||||
it('returns canonical discovery empty-state copy', () => {
|
||||
|
|
|
|||
|
|
@ -329,6 +329,7 @@ import useUnifiedResourcesSource from '@/hooks/useUnifiedResources.ts?raw';
|
|||
import useWorkloadsSource from '@/hooks/useWorkloads.ts?raw';
|
||||
import findingsPanelSource from '@/components/AI/FindingsPanel.tsx?raw';
|
||||
import discoveryTabSource from '@/components/Discovery/DiscoveryTab.tsx?raw';
|
||||
import discoveryProvenanceMarkerSource from '@/components/shared/DiscoveryProvenanceMarker.tsx?raw';
|
||||
import discoveryPresentationSource from '@/utils/discoveryPresentation.ts?raw';
|
||||
import mailGatewaySource from '@/components/PMG/MailGateway.tsx?raw';
|
||||
import pmgInstancePanelSource from '@/components/PMG/PMGInstancePanel.tsx?raw';
|
||||
|
|
@ -3170,6 +3171,12 @@ describe('frontend resource type boundaries', () => {
|
|||
expect(discoveryPresentationSource).toContain(
|
||||
'export function getDiscoveryObservedSourceLabel',
|
||||
);
|
||||
expect(discoveryPresentationSource).toContain('export function getDiscoveryProvenanceTitle');
|
||||
expect(discoveryProvenanceMarkerSource).toContain('getDiscoveryProvenanceTitle');
|
||||
expect(discoveryProvenanceMarkerSource).toContain('getDiscoveryProvenanceBadgeClass');
|
||||
expect(discoveryTabSource).toContain('DiscoveryProvenanceMarker');
|
||||
expect(webInterfaceUrlFieldSource).toContain('DiscoveryProvenanceMarker');
|
||||
expect(guestDrawerOverviewSource).toContain('DiscoveryProvenanceMarker');
|
||||
expect(discoveryPresentationSource).toContain(
|
||||
'export function getDiscoverySuggestedURLCardClass',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -27,6 +27,9 @@ export interface DiscoveryIdentifiedSummary {
|
|||
}
|
||||
|
||||
const OBSERVED_SOURCE_LABEL = 'Observed by Discovery';
|
||||
const DISCOVERY_PROVENANCE_LABEL = 'Discovery';
|
||||
const DISCOVERY_PROVENANCE_TITLE =
|
||||
'Surfaced by opt-in Discovery from agent-observed workload context.';
|
||||
|
||||
const toSentence = (text?: string | null): string => {
|
||||
const trimmed = (text || '').trim();
|
||||
|
|
@ -100,6 +103,22 @@ export function getDiscoveryObservedSourceLabel(): string {
|
|||
return OBSERVED_SOURCE_LABEL;
|
||||
}
|
||||
|
||||
export function getDiscoveryProvenanceLabel(): string {
|
||||
return DISCOVERY_PROVENANCE_LABEL;
|
||||
}
|
||||
|
||||
export function getDiscoveryProvenanceTitle(): string {
|
||||
return DISCOVERY_PROVENANCE_TITLE;
|
||||
}
|
||||
|
||||
export function getDiscoveryProvenanceBadgeClass(): string {
|
||||
return 'inline-flex h-5 shrink-0 items-center gap-1 rounded border border-cyan-200 bg-cyan-50 px-1.5 text-[10px] font-medium leading-none text-cyan-700 dark:border-cyan-800 dark:bg-cyan-950 dark:text-cyan-200';
|
||||
}
|
||||
|
||||
export function getDiscoveryProvenanceIconClass(): string {
|
||||
return 'inline-flex h-4 w-4 shrink-0 items-center justify-center rounded border border-cyan-200 bg-cyan-50 text-cyan-700 dark:border-cyan-800 dark:bg-cyan-950 dark:text-cyan-200';
|
||||
}
|
||||
|
||||
export function normalizeDiscoverySuggestedUrl(value?: string | null): string | undefined {
|
||||
const trimmed = (value || '').trim();
|
||||
return trimmed || undefined;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue