mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-09-10 02:25:56 +00:00
Merge dead-code cleanup: remove 198 unreachable Go funcs + 103 unused frontend exports
Verified via deadcode/ts-prune detection + 317-agent cross-repo verification swarm. Gated: go build + go vet + test-compile (pulse module), tsc --noEmit (full project incl tests), npm run lint. Net -2549 lines across 161 files.
This commit is contained in:
@@ -1376,24 +1376,6 @@ func applyRemoteSettings(cfg *Config, settings map[string]interface{}, logger *z
|
||||
}
|
||||
}
|
||||
|
||||
func remoteBoolSetting(settings map[string]interface{}, key string) (bool, bool) {
|
||||
value, ok := settings[key]
|
||||
if !ok {
|
||||
return false, false
|
||||
}
|
||||
parsed, ok := value.(bool)
|
||||
return parsed, ok
|
||||
}
|
||||
|
||||
func remoteStringSetting(settings map[string]interface{}, key string) (string, bool) {
|
||||
value, ok := settings[key]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
parsed, ok := value.(string)
|
||||
return parsed, ok
|
||||
}
|
||||
|
||||
func remoteDurationSetting(settings map[string]interface{}, key string) (time.Duration, bool) {
|
||||
value, ok := settings[key]
|
||||
if !ok {
|
||||
|
||||
@@ -556,20 +556,6 @@ export interface StepResult {
|
||||
run_at: string;
|
||||
}
|
||||
|
||||
export interface RemediationExecution {
|
||||
id: string;
|
||||
plan_id: string;
|
||||
status: 'pending' | 'approved' | 'running' | 'completed' | 'failed' | 'rolled_back';
|
||||
approved_by?: string;
|
||||
approved_at?: string;
|
||||
started_at?: string;
|
||||
completed_at?: string;
|
||||
current_step: number;
|
||||
step_results?: StepResult[];
|
||||
error?: string;
|
||||
rollback_error?: string;
|
||||
}
|
||||
|
||||
// Compatibility response shape for older execution endpoints.
|
||||
export interface RemediationExecutionResult {
|
||||
execution_id: string;
|
||||
|
||||
@@ -16,29 +16,6 @@ import {
|
||||
parseOptionalAPIResponseOrNull,
|
||||
} from './responseUtils';
|
||||
|
||||
export interface RemovedDockerHost {
|
||||
id: string;
|
||||
hostname?: string;
|
||||
displayName?: string;
|
||||
removedAt: number;
|
||||
}
|
||||
|
||||
export interface RemovedHostAgent {
|
||||
id: string;
|
||||
hostname?: string;
|
||||
displayName?: string;
|
||||
linkedVmId?: string;
|
||||
linkedContainerId?: string;
|
||||
removedAt: number;
|
||||
}
|
||||
|
||||
export interface RemovedKubernetesCluster {
|
||||
id: string;
|
||||
name?: string;
|
||||
displayName?: string;
|
||||
removedAt: number;
|
||||
}
|
||||
|
||||
async function deleteResource<T extends object>(
|
||||
url: string,
|
||||
parseErrorMessage: string,
|
||||
|
||||
@@ -201,14 +201,6 @@ export function apiErrorCode(error: unknown): string | null {
|
||||
return trimmedOptionalString((error as APIErrorLike).code);
|
||||
}
|
||||
|
||||
export function apiErrorDetail(error: unknown): string | null {
|
||||
if (!error || typeof error !== 'object') {
|
||||
return null;
|
||||
}
|
||||
|
||||
return trimmedOptionalString((error as APIErrorLike).detail);
|
||||
}
|
||||
|
||||
export function apiErrorDetails(error: unknown): Record<string, string> | null {
|
||||
if (!error || typeof error !== 'object') {
|
||||
return null;
|
||||
|
||||
@@ -359,5 +359,3 @@ export const formatInteger = (value?: number): string => {
|
||||
if (value === undefined || value === null || Number.isNaN(value)) return '—';
|
||||
return new Intl.NumberFormat().format(Math.round(value));
|
||||
};
|
||||
|
||||
export const ALIAS_COLLAPSE_THRESHOLD = 4;
|
||||
|
||||
@@ -490,7 +490,3 @@ export const useResourceDetailDrawerDerivedState = (
|
||||
tabs,
|
||||
};
|
||||
};
|
||||
|
||||
export type ResourceDetailDrawerDerivedState = ReturnType<
|
||||
typeof useResourceDetailDrawerDerivedState
|
||||
>;
|
||||
|
||||
-4
@@ -95,7 +95,3 @@ export const useResourceDetailDrawerDockerActionsState = (
|
||||
queueDockerUpdateAll,
|
||||
};
|
||||
};
|
||||
|
||||
export type UseResourceDetailDrawerDockerActionsStateResult = ReturnType<
|
||||
typeof useResourceDetailDrawerDockerActionsState
|
||||
>;
|
||||
|
||||
@@ -234,7 +234,3 @@ export const useResourceDetailDrawerHistoryState = (
|
||||
refetchHistoryFacets,
|
||||
};
|
||||
};
|
||||
|
||||
export type ResourceDetailDrawerHistoryState = ReturnType<
|
||||
typeof useResourceDetailDrawerHistoryState
|
||||
>;
|
||||
|
||||
@@ -45,7 +45,3 @@ export function useUnifiedResourceTableViewportSync(
|
||||
setHostBodyRef,
|
||||
};
|
||||
}
|
||||
|
||||
export type UnifiedResourceTableViewportSync = ReturnType<
|
||||
typeof useUnifiedResourceTableViewportSync
|
||||
>;
|
||||
|
||||
@@ -216,16 +216,3 @@ export function isModelProviderConfigured(
|
||||
const provider = getProviderFromModelId(modelId);
|
||||
return isAIProviderConfigured(provider, settings);
|
||||
}
|
||||
|
||||
export function groupModelsByProvider(models: AIAvailableModel[]): Map<string, AIAvailableModel[]> {
|
||||
const grouped = new Map<string, AIAvailableModel[]>();
|
||||
|
||||
for (const model of models) {
|
||||
const provider = getProviderFromModelId(model.id);
|
||||
const existing = grouped.get(provider) || [];
|
||||
existing.push(model);
|
||||
grouped.set(provider, existing);
|
||||
}
|
||||
|
||||
return grouped;
|
||||
}
|
||||
|
||||
@@ -48,34 +48,6 @@ export const matchConfiguredNodeToResource = (
|
||||
});
|
||||
};
|
||||
|
||||
export const collectConfiguredInfrastructureHosts = (nodes: NodeConfigWithStatus[]) => {
|
||||
const configuredHosts = new Set<string>();
|
||||
const clusterMemberIPs = new Set<string>();
|
||||
|
||||
nodes.forEach((node) => {
|
||||
const cleanedHost = node.host.replace(/^https?:\/\//, '').replace(/:\d+$/, '');
|
||||
configuredHosts.add(cleanedHost.toLowerCase());
|
||||
|
||||
if (!('isCluster' in node) || !node.isCluster || !('clusterEndpoints' in node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
node.clusterEndpoints?.forEach((endpoint: ClusterEndpoint) => {
|
||||
if (endpoint.ip) {
|
||||
clusterMemberIPs.add(endpoint.ip.toLowerCase());
|
||||
}
|
||||
if (endpoint.host) {
|
||||
clusterMemberIPs.add(endpoint.host.toLowerCase());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return {
|
||||
clusterMemberIPs,
|
||||
configuredHosts,
|
||||
};
|
||||
};
|
||||
|
||||
const createRepresentedDiscoveryHosts = (): RepresentedDiscoveryHosts => ({
|
||||
pve: new Set<string>(),
|
||||
pbs: new Set<string>(),
|
||||
|
||||
@@ -12,8 +12,6 @@ import type { DiscoverySettingsFormProps } from './discoverySettingsModel';
|
||||
import type { TrueNASSettingsPanelState } from './useTrueNASSettingsPanelState';
|
||||
import type { VMwareSettingsPanelState } from './useVMwareSettingsPanelState';
|
||||
|
||||
export type DiscoveryMode = 'auto' | 'custom';
|
||||
|
||||
export interface InfrastructurePlatformSettingsProps {
|
||||
selectedAgent: Accessor<NodeType>;
|
||||
onSelectAgent: (agent: NodeType) => void;
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export type { ReportingResourceType } from '@/utils/reportingResourceTypes';
|
||||
export { toReportingResourceType } from '@/utils/reportingResourceTypes';
|
||||
|
||||
|
||||
@@ -1,16 +1,3 @@
|
||||
export {
|
||||
DEFAULT_SETTINGS_TAB,
|
||||
agentKeyFromPlatformType,
|
||||
deriveTabFromPath,
|
||||
deriveTabFromQuery,
|
||||
isRetiredSettingsCompatibilityPath,
|
||||
isRouteableSettingsPath,
|
||||
resolveCanonicalSettingsPath,
|
||||
settingsAgentLabel,
|
||||
settingsAgentNodeLabel,
|
||||
settingsAgentPlatformType,
|
||||
settingsTabPath,
|
||||
type AgentKey,
|
||||
type ProxmoxPlatformType,
|
||||
type SettingsTab,
|
||||
} from './settingsNavigationModel';
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
export type {
|
||||
SettingsHeaderMeta,
|
||||
SettingsHeaderMetaMap,
|
||||
SettingsNavGroup,
|
||||
SettingsNavGroupId,
|
||||
SettingsNavItem,
|
||||
SettingsTab,
|
||||
} from './settingsNavigationModel';
|
||||
|
||||
@@ -559,5 +559,3 @@ Pulse prepares the first-host install token from setup so you can move straight
|
||||
tokenName,
|
||||
};
|
||||
};
|
||||
|
||||
export type InfrastructureInstallState = ReturnType<typeof useInfrastructureInstallState>;
|
||||
|
||||
@@ -6,12 +6,6 @@ import { useInfrastructureDiscoveryRuntimeState } from './useInfrastructureDisco
|
||||
import { useTrueNASSettingsPanelState } from './useTrueNASSettingsPanelState';
|
||||
import { useVMwareSettingsPanelState } from './useVMwareSettingsPanelState';
|
||||
|
||||
export type {
|
||||
DiscoveryScanStatus,
|
||||
DiscoveredServer,
|
||||
NodeType,
|
||||
} from './infrastructureSettingsModel';
|
||||
|
||||
type InfrastructureEventBus = {
|
||||
on<T extends EventType>(event: T, handler: (data?: EventDataMap[T]) => void): () => void;
|
||||
};
|
||||
|
||||
@@ -1,3 +1,2 @@
|
||||
export { SetupWizard } from './SetupWizard';
|
||||
export { StepIndicator } from './StepIndicator';
|
||||
export type { WizardState, WizardStep } from './SetupWizard';
|
||||
export type { WizardState, } from './SetupWizard';
|
||||
|
||||
@@ -68,7 +68,6 @@ export const WORKLOAD_TABLE_HISTORY_RANGE_LABELS: Record<WorkloadTableMetricHist
|
||||
'7d': '7d',
|
||||
};
|
||||
export const WORKLOAD_TABLE_HISTORY_DEFAULT_RANGE: WorkloadTableMetricHistoryRange = '1h';
|
||||
export const WORKLOAD_TABLE_HISTORY_RANGE = WORKLOAD_TABLE_HISTORY_DEFAULT_RANGE;
|
||||
export const WORKLOAD_TABLE_HISTORY_MAX_POINTS = 72;
|
||||
export const WORKLOAD_TABLE_HISTORY_POLL_MS = 30_000;
|
||||
|
||||
|
||||
@@ -88,7 +88,6 @@ export const DEFAULT_WORKLOADS_SORT_KEY: WorkloadsSortKey = 'type';
|
||||
export const DEFAULT_WORKLOADS_SORT_DIRECTION = 'asc';
|
||||
export const DEFAULT_WORKLOADS_VIEW_MODE: ViewMode = 'all';
|
||||
export const DEFAULT_WORKLOADS_STATUS_MODE: WorkloadsStatusMode = 'all';
|
||||
export const DEFAULT_WORKLOADS_GROUPING_MODE: WorkloadsGroupingMode = 'grouped';
|
||||
export const DEFAULT_WORKLOADS_METRIC_DISPLAY_MODE: WorkloadsMetricDisplayMode = 'bars';
|
||||
|
||||
export const countActiveWorkloadsFilters = (
|
||||
|
||||
@@ -4,7 +4,6 @@ export { InvestigationSection } from './InvestigationSection';
|
||||
export { InvestigationMessages } from './InvestigationMessages';
|
||||
export { RemediationStatus } from './RemediationStatus';
|
||||
export { RunToolCallTrace } from './RunToolCallTrace';
|
||||
export { PatrolStatusBar } from './PatrolStatusBar';
|
||||
export { RunHistoryEntry } from './RunHistoryEntry';
|
||||
export { RunHistoryPanel } from './RunHistoryPanel';
|
||||
export { CountdownTimer } from './CountdownTimer';
|
||||
|
||||
@@ -3,16 +3,13 @@ export { FilterChip } from './FilterChip';
|
||||
export { AddFilterMenu } from './AddFilterMenu';
|
||||
export { SavedViewsMenu } from './SavedViewsMenu';
|
||||
export { useSavedViews } from './useSavedViews';
|
||||
export type { SavedView, UseSavedViewsResult } from './useSavedViews';
|
||||
export type { SavedView, } from './useSavedViews';
|
||||
export {
|
||||
clearFilter,
|
||||
formatFilterChipValue,
|
||||
isFilterSet,
|
||||
} from './filterCatalog';
|
||||
export type {
|
||||
FilterBarProps,
|
||||
FilterBarSearch,
|
||||
FilterDef,
|
||||
FilterGroupKey,
|
||||
FilterSelectOption,
|
||||
} from './filterCatalog';
|
||||
|
||||
@@ -10,7 +10,6 @@ import { useFilterButtonGroupState } from './useFilterButtonGroupState';
|
||||
|
||||
export type {
|
||||
FilterButtonGroupProps,
|
||||
FilterButtonGroupVariant,
|
||||
FilterOption,
|
||||
} from './filterButtonGroupModel';
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ import {
|
||||
} from './pulseDataGridModel';
|
||||
import { usePulseDataGridState } from './usePulseDataGridState';
|
||||
|
||||
export type { PulseDataGridProps, TableColumn } from './pulseDataGridModel';
|
||||
export type { PulseDataGridProps, } from './pulseDataGridModel';
|
||||
|
||||
/**
|
||||
* A standardized, responsive datagrid component for Pulse.
|
||||
|
||||
@@ -3,9 +3,6 @@ import { type SearchFieldProps } from './searchFieldModel';
|
||||
import { useSearchFieldState } from './useSearchFieldState';
|
||||
|
||||
export type {
|
||||
SearchFieldFocusEvent,
|
||||
SearchFieldKeyboardEvent,
|
||||
SearchFieldMouseEvent,
|
||||
SearchFieldProps,
|
||||
} from './searchFieldModel';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
import { type SearchInputProps } from './searchInputModel';
|
||||
import { useSearchInputState } from './useSearchInputState';
|
||||
|
||||
export type { SearchInputKeyboardEvent, SearchInputProps } from './searchInputModel';
|
||||
export type { SearchInputProps } from './searchInputModel';
|
||||
|
||||
export const SearchInput: Component<SearchInputProps> = (props) => {
|
||||
const search = useSearchInputState(props);
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from './searchTipsPopoverModel';
|
||||
import { useSearchTipsPopoverState } from './useSearchTipsPopoverState';
|
||||
|
||||
export type { SearchTip, SearchTipsPopoverProps } from './searchTipsPopoverModel';
|
||||
export type { SearchTipsPopoverProps } from './searchTipsPopoverModel';
|
||||
|
||||
export const SearchTipsPopover: Component<SearchTipsPopoverProps> = (props) => {
|
||||
const triggerVariant = () => getSearchTipsPopoverTriggerVariant(props.triggerVariant);
|
||||
|
||||
@@ -11,9 +11,7 @@ import { useSelectionCardGroupState } from './useSelectionCardGroupState';
|
||||
|
||||
export type {
|
||||
SelectionCardGroupProps,
|
||||
SelectionCardGroupVariant,
|
||||
SelectionCardOption,
|
||||
SelectionCardTone,
|
||||
} from './selectionCardGroupModel';
|
||||
|
||||
export function SelectionCardGroup<T extends string | number>(props: SelectionCardGroupProps<T>) {
|
||||
|
||||
@@ -153,85 +153,3 @@ export const ResponsiveMetricCell: Component<ResponsiveMetricCellProps> = (props
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Simpler metric text component for when you just want colored percentage
|
||||
* without the MetricBar complexity
|
||||
*/
|
||||
export const MetricText: Component<{
|
||||
value: number;
|
||||
type: 'cpu' | 'memory' | 'disk';
|
||||
label?: string;
|
||||
class?: string;
|
||||
thresholds?: MetricDisplayThresholds | null;
|
||||
}> = (props) => {
|
||||
const displayLabel = createMemo(() => props.label ?? formatPercent(props.value));
|
||||
const colorClass = createMemo(() => metricTextClass(props.value, props.type, props.thresholds));
|
||||
|
||||
return (
|
||||
<span class={`text-xs text-center ${colorClass()} ${props.class || ''}`}>
|
||||
<Show when={!props.label} fallback={displayLabel()}>
|
||||
<AnimatedNumber value={props.value} format={formatPercent} />
|
||||
</Show>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
* Metric cell with explicit mobile/desktop rendering
|
||||
* Use this when you need full control over what renders in each mode
|
||||
*/
|
||||
export const DualMetricCell: Component<{
|
||||
value: number;
|
||||
type: 'cpu' | 'memory' | 'disk';
|
||||
label?: string;
|
||||
sublabel?: string;
|
||||
resourceId?: string;
|
||||
isRunning?: boolean;
|
||||
showMobile: boolean;
|
||||
mobileContent?: JSX.Element;
|
||||
desktopContent?: JSX.Element;
|
||||
fallback?: JSX.Element;
|
||||
class?: string;
|
||||
thresholds?: MetricDisplayThresholds | null;
|
||||
}> = (props) => {
|
||||
const displayLabel = createMemo(() => props.label ?? formatPercent(props.value));
|
||||
const colorClass = createMemo(() => metricTextClass(props.value, props.type, props.thresholds));
|
||||
const isRunning = () => props.isRunning !== false;
|
||||
|
||||
const defaultFallback = (
|
||||
<div class="h-4 flex items-center justify-center">
|
||||
<span class="text-xs text-muted" aria-hidden="true">—</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
const defaultMobileContent = (
|
||||
<div class={`text-xs text-center ${colorClass()}`}>
|
||||
<Show when={!props.label} fallback={displayLabel()}>
|
||||
<AnimatedNumber value={props.value} format={formatPercent} />
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
|
||||
const defaultDesktopContent = (
|
||||
<MetricBar
|
||||
value={props.value}
|
||||
label={displayLabel()}
|
||||
animatedLabelValue={props.label ? undefined : props.value}
|
||||
sublabel={props.sublabel}
|
||||
type={props.type}
|
||||
resourceId={props.resourceId}
|
||||
thresholds={props.thresholds}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Show when={isRunning()} fallback={props.fallback ?? defaultFallback}>
|
||||
<div class={props.class}>
|
||||
<Show when={props.showMobile} fallback={props.desktopContent ?? defaultDesktopContent}>
|
||||
{props.mobileContent ?? defaultMobileContent}
|
||||
</Show>
|
||||
</div>
|
||||
</Show>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export const SUMMARY_CHART_SLOT_CLASS = 'h-[136px] sm:h-[150px]';
|
||||
|
||||
export const SUMMARY_CHART_SLOT_COMPACT_CLASS = 'h-[108px] sm:h-[120px]';
|
||||
export const SUMMARY_CHART_PLOT_AREA_CLASS = 'h-[120px] sm:h-[134px]';
|
||||
|
||||
@@ -8,6 +8,3 @@ export const SUMMARY_TIME_RANGE_LABEL: Record<SummaryTimeRange, string> = {
|
||||
'24h': '24h',
|
||||
'7d': '7d',
|
||||
};
|
||||
|
||||
export const isSummaryTimeRange = (value: string): value is SummaryTimeRange =>
|
||||
(SUMMARY_TIME_RANGES as readonly string[]).includes(value);
|
||||
|
||||
@@ -73,5 +73,3 @@ export function useCommandPaletteState(props: CommandPaletteModalProps) {
|
||||
setQuery,
|
||||
};
|
||||
}
|
||||
|
||||
export type CommandPaletteState = ReturnType<typeof useCommandPaletteState>;
|
||||
|
||||
@@ -126,5 +126,3 @@ export function useContainerUpdateButtonState(props: UpdateButtonProps) {
|
||||
shouldHideButton,
|
||||
};
|
||||
}
|
||||
|
||||
export type ContainerUpdateButtonState = ReturnType<typeof useContainerUpdateButtonState>;
|
||||
|
||||
@@ -98,5 +98,3 @@ export function useHelpIconState(props: HelpIconProps) {
|
||||
toggleOpen,
|
||||
};
|
||||
}
|
||||
|
||||
export type HelpIconState = ReturnType<typeof useHelpIconState>;
|
||||
|
||||
@@ -80,5 +80,3 @@ export function useMobileNavBarState(props: MobileNavBarProps) {
|
||||
showLeftFade,
|
||||
};
|
||||
}
|
||||
|
||||
export type MobileNavBarState = ReturnType<typeof useMobileNavBarState>;
|
||||
|
||||
@@ -211,5 +211,3 @@ export function useWebInterfaceUrlFieldState(props: WebInterfaceUrlFieldProps) {
|
||||
urlValue,
|
||||
};
|
||||
}
|
||||
|
||||
export type WebInterfaceUrlFieldState = ReturnType<typeof useWebInterfaceUrlFieldState>;
|
||||
|
||||
@@ -124,16 +124,6 @@ export const getGuestOverrideIdentity = (
|
||||
};
|
||||
};
|
||||
|
||||
export const canonicalGuestOverrideResourceId = (
|
||||
resource: GuestOverrideResourceLike,
|
||||
): string | undefined => {
|
||||
const identity = getGuestOverrideIdentity(resource);
|
||||
if (!identity) {
|
||||
return undefined;
|
||||
}
|
||||
return `${identity.instance}:${identity.node}:${identity.vmid}`;
|
||||
};
|
||||
|
||||
export const guestOverrideStorageId = (resource: GuestOverrideResourceLike): string => {
|
||||
const identity = getGuestOverrideIdentity(resource);
|
||||
if (!identity) {
|
||||
|
||||
@@ -40,17 +40,3 @@ export const stripStateKeys = (
|
||||
delete (next as Record<string, unknown>).poweredOffSeverity;
|
||||
return next;
|
||||
};
|
||||
|
||||
export const removeOverrideState = (
|
||||
overrides: Override[],
|
||||
rawOverridesConfig: Record<string, RawOverrideConfig>,
|
||||
resourceId: string,
|
||||
) => {
|
||||
const nextRawConfig = { ...rawOverridesConfig };
|
||||
delete nextRawConfig[resourceId];
|
||||
|
||||
return {
|
||||
nextOverrides: overrides.filter((override) => override.id !== resourceId),
|
||||
nextRawConfig,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -81,14 +81,6 @@ export const getStoragePoolTableColumns = (
|
||||
colClassName: 'hidden xl:table-column xl:w-[11%]',
|
||||
},
|
||||
];
|
||||
|
||||
export const STORAGE_CONTROLS_NODE_SELECT_CLASS =
|
||||
'px-2 py-1 text-xs border border-border rounded-md bg-surface text-base-content focus:ring-2 focus:ring-blue-500 focus:border-blue-500';
|
||||
|
||||
export const STORAGE_CONTROLS_NODE_DIVIDER_CLASS = 'h-5 w-px bg-surface-hover hidden sm:block';
|
||||
|
||||
export const STORAGE_CONTENT_CARD_HEADER_CLASS =
|
||||
'border-b border-border bg-surface-hover px-3 py-2 text-[11px] font-semibold uppercase tracking-wide text-muted';
|
||||
export const STORAGE_CONTENT_CARD_BODY_CLASS = 'p-2';
|
||||
|
||||
export const STORAGE_POOLS_EMPTY_STATE_CLASS = 'p-6 text-sm text-muted';
|
||||
|
||||
@@ -854,18 +854,6 @@ const matchesTrueNASStorageSearch = (resource: Resource, search: string): boolea
|
||||
return haystack.includes(needle);
|
||||
};
|
||||
|
||||
export function filterTrueNASStorageResources(
|
||||
resources: Resource[],
|
||||
search: string,
|
||||
status: TrueNASStorageStatusFilter,
|
||||
): Resource[] {
|
||||
return resources.filter((resource) => {
|
||||
if (!matchesTrueNASStorageSearch(resource, search)) return false;
|
||||
if (status === 'all') return true;
|
||||
return mapTrueNASStorageStatus(resource) === status;
|
||||
});
|
||||
}
|
||||
|
||||
const resourceIncidentLabel = (resource: Resource, incident: ResourceIncident): string => {
|
||||
const label = asTrimmedString(resource.incidentLabel);
|
||||
if (label) return label;
|
||||
|
||||
@@ -29,7 +29,6 @@ export const TRUENAS_DEFAULT_TAB = 'overview';
|
||||
export const VMWARE_PATH = '/vmware';
|
||||
export const VMWARE_DEFAULT_TAB = 'overview';
|
||||
export const PMG_THRESHOLDS_PATH = '/alerts/thresholds/mail-gateway';
|
||||
export const ALERTS_OVERVIEW_PATH = '/alerts/overview';
|
||||
export const PATROL_PATH = '/patrol';
|
||||
|
||||
export const STORAGE_QUERY_PARAMS = {
|
||||
|
||||
@@ -191,18 +191,6 @@ export interface AIConversationMessage {
|
||||
content: string;
|
||||
}
|
||||
|
||||
// AI Execute request/response types
|
||||
export interface AIExecuteRequest {
|
||||
prompt: string;
|
||||
target_type?: string; // "agent", "system-container", "vm", etc.
|
||||
target_id?: string;
|
||||
context?: Record<string, unknown>;
|
||||
history?: AIConversationMessage[]; // Previous conversation messages
|
||||
finding_id?: string; // If fixing a patrol finding, the ID to resolve on success
|
||||
model?: string; // Override model for this request (user selection in chat)
|
||||
use_case?: 'chat' | 'patrol'; // Optional server-side routing/model selection
|
||||
}
|
||||
|
||||
// Tool execution info
|
||||
export interface AIToolExecution {
|
||||
name: string; // "run_command", "read_file"
|
||||
@@ -387,15 +375,6 @@ export interface AIChatMessage {
|
||||
toolCalls?: AIChatToolCall[];
|
||||
}
|
||||
|
||||
export interface AIChatSession {
|
||||
id: string;
|
||||
username: string;
|
||||
title: string;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
messages: AIChatMessage[];
|
||||
}
|
||||
|
||||
// Summary returned by list endpoint (no messages)
|
||||
export interface AIChatSessionHandoffResource {
|
||||
id?: string;
|
||||
|
||||
@@ -63,30 +63,6 @@ export interface ConnectedInfrastructureItem {
|
||||
surfaces: ConnectedInfrastructureSurface[];
|
||||
}
|
||||
|
||||
export interface KubernetesCluster {
|
||||
id: string;
|
||||
agentId: string;
|
||||
name?: string;
|
||||
displayName?: string;
|
||||
customDisplayName?: string;
|
||||
server?: string;
|
||||
context?: string;
|
||||
version?: string;
|
||||
status: string;
|
||||
lastSeen: number;
|
||||
intervalSeconds: number;
|
||||
agentVersion?: string;
|
||||
tokenId?: string;
|
||||
tokenName?: string;
|
||||
tokenHint?: string;
|
||||
tokenLastUsedAt?: number;
|
||||
hidden?: boolean;
|
||||
pendingUninstall?: boolean;
|
||||
nodes?: KubernetesNode[];
|
||||
pods?: KubernetesPod[];
|
||||
deployments?: KubernetesDeployment[];
|
||||
}
|
||||
|
||||
export interface KubernetesNode {
|
||||
uid: string;
|
||||
name: string;
|
||||
@@ -1068,11 +1044,6 @@ export interface PhysicalDisk {
|
||||
smartAttributes?: SMARTAttributes;
|
||||
}
|
||||
|
||||
/** Returns the best resource ID for disk metrics queries (serial preferred, WWN fallback). */
|
||||
export function diskResourceId(disk: PhysicalDisk): string | null {
|
||||
return disk.serial || disk.wwn || null;
|
||||
}
|
||||
|
||||
export interface CPUInfo {
|
||||
model: string;
|
||||
cores: number;
|
||||
@@ -1259,7 +1230,3 @@ export type WSMessage =
|
||||
scanning?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
// Utility types
|
||||
export type Status = 'running' | 'stopped' | 'paused' | 'unknown';
|
||||
export type GuestType = 'vm' | 'system-container';
|
||||
|
||||
@@ -87,15 +87,6 @@ export interface NodesConfig {
|
||||
pbsInstances: PBSNodeConfig[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete configuration structure
|
||||
*/
|
||||
export interface PulseConfig {
|
||||
auth: Partial<AuthConfig>; // From .env
|
||||
system: SystemConfig; // From system.json
|
||||
nodes: NodesConfig; // From nodes.enc
|
||||
}
|
||||
|
||||
/**
|
||||
* API response for security status
|
||||
*/
|
||||
@@ -180,47 +171,3 @@ export interface SSOProviderInfo {
|
||||
iconUrl?: string;
|
||||
loginUrl: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* First-run setup request
|
||||
*/
|
||||
export interface SetupRequest {
|
||||
username: string;
|
||||
password: string;
|
||||
apiToken?: string;
|
||||
enableNotifications?: boolean;
|
||||
darkMode?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Type guards for configuration validation
|
||||
*/
|
||||
export const isValidUpdateChannel = (value: string): value is UpdateChannel => {
|
||||
return value === 'stable' || value === 'rc';
|
||||
};
|
||||
|
||||
export const isValidTimeFormat = (value: string): boolean => {
|
||||
return /^([01]\d|2[0-3]):([0-5]\d)$/.test(value);
|
||||
};
|
||||
|
||||
/**
|
||||
* Default values for configuration
|
||||
*/
|
||||
export const DEFAULT_CONFIG: {
|
||||
system: SystemConfig;
|
||||
} = {
|
||||
system: {
|
||||
connectionTimeout: 60,
|
||||
autoUpdateEnabled: false,
|
||||
updateChannel: 'stable',
|
||||
autoUpdateCheckInterval: 24,
|
||||
autoUpdateTime: '03:00',
|
||||
backupPollingEnabled: true,
|
||||
backupPollingInterval: 0,
|
||||
temperatureMonitoringEnabled: true,
|
||||
telemetryEnabled: true,
|
||||
sshPort: 22,
|
||||
allowedOrigins: '',
|
||||
frontendPort: 7655,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -162,10 +162,6 @@ export interface UpdateNotesRequest {
|
||||
user_secrets?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface UpdateSettingsRequest {
|
||||
max_discovery_age_days?: number; // Days before rediscovery (default 30)
|
||||
}
|
||||
|
||||
// AI provider information for discovery transparency
|
||||
export interface AIProviderInfo {
|
||||
provider: string; // e.g., "anthropic", "openai", "ollama"
|
||||
|
||||
@@ -104,18 +104,3 @@ export type NodeConfigWithStatus = NodeConfig & {
|
||||
hasToken?: boolean;
|
||||
status: 'connected' | 'disconnected' | 'offline' | 'error' | 'pending';
|
||||
};
|
||||
|
||||
export interface NodesResponse {
|
||||
pve_instances: PVENodeConfig[];
|
||||
pbs_instances: PBSNodeConfig[];
|
||||
pmg_instances?: PMGNodeConfig[];
|
||||
}
|
||||
|
||||
export interface NodeUpdateRequest {
|
||||
node: NodeConfig;
|
||||
}
|
||||
|
||||
export interface NodeDeleteResponse {
|
||||
success: boolean;
|
||||
message: string;
|
||||
}
|
||||
|
||||
@@ -137,10 +137,6 @@ export interface RecoveryPointsSeriesBucket {
|
||||
remote: number;
|
||||
}
|
||||
|
||||
export interface RecoveryPointsSeriesResponse {
|
||||
data: RecoveryPointsSeriesBucket[];
|
||||
}
|
||||
|
||||
export interface RecoveryPointsFacets {
|
||||
clusters?: string[];
|
||||
nodesAgents?: string[];
|
||||
@@ -150,7 +146,3 @@ export interface RecoveryPointsFacets {
|
||||
hasVerification?: boolean;
|
||||
hasEntityId?: boolean;
|
||||
}
|
||||
|
||||
export interface RecoveryPointsFacetsResponse {
|
||||
data: RecoveryPointsFacets;
|
||||
}
|
||||
|
||||
@@ -196,8 +196,6 @@ export const requiresGovernedResourceDisplay = (policy?: ResourcePolicy | null):
|
||||
if (!policy) return false;
|
||||
return policy.routing.scope === 'local-only' || (policy.routing.redact?.length ?? 0) > 0;
|
||||
};
|
||||
|
||||
export type ResourceApprovalLevel = 'none' | 'dry_run_only' | 'admin' | 'mfa';
|
||||
export type ResourceChangeConfidence = 'high' | 'medium' | 'low';
|
||||
export type ResourceChangeKind =
|
||||
| 'state_transition'
|
||||
|
||||
@@ -342,23 +342,6 @@ export const getFindingSeverityCompactLabel = (
|
||||
severity: UnifiedFinding['severity'] | string,
|
||||
): string => FINDING_SEVERITY_COMPACT_LABELS[severity] || String(severity).toUpperCase();
|
||||
|
||||
export const getFindingCompactBadgePresentation = (
|
||||
finding: Pick<UnifiedFinding, 'severity' | 'resourceId' | 'resourceName' | 'title'>,
|
||||
): FindingCompactBadgePresentation => {
|
||||
if (isPatrolRuntimeFinding(finding)) {
|
||||
const severityPresentation = getFindingSeverityPresentation(finding);
|
||||
return {
|
||||
label: severityPresentation.label,
|
||||
badgeClasses: severityPresentation.badgeClasses,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
label: getFindingSeverityCompactLabel(finding.severity),
|
||||
badgeClasses: getFindingSeverityBadgeClasses(finding.severity),
|
||||
};
|
||||
};
|
||||
|
||||
export const isPatrolRuntimeFinding = (
|
||||
finding: Pick<UnifiedFinding, 'resourceId' | 'resourceName' | 'title'>,
|
||||
): boolean => {
|
||||
@@ -617,25 +600,6 @@ export const doesFindingNeedAttention = (
|
||||
);
|
||||
};
|
||||
|
||||
// True when the finding has an investigation outcome indicating that some
|
||||
// remediation step has run against it — anything past "fix queued." For these
|
||||
// states, Verify fix is a meaningful action; for fix_queued (still awaiting
|
||||
// approval) and earlier states there is nothing applied yet to verify, and
|
||||
// for fix_failed the fix didn't complete so verification doesn't apply.
|
||||
export const findingHasAppliedFix = (
|
||||
finding: Pick<UnifiedFinding, 'investigationOutcome'>,
|
||||
): boolean => {
|
||||
switch (finding.investigationOutcome) {
|
||||
case 'fix_executed':
|
||||
case 'fix_verified':
|
||||
case 'fix_verification_failed':
|
||||
case 'fix_verification_unknown':
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const getFindingLoopStateBadgeClasses = (loopState: string): string =>
|
||||
FINDING_LOOP_STATE_CLASSES[loopState] || DEFAULT_LOOP_STATE_CLASSES;
|
||||
|
||||
|
||||
@@ -190,10 +190,6 @@ export function getAlertConfigDiscardLabel(isReloading: boolean) {
|
||||
return isReloading ? ALERT_CONFIG_DISCARDING_LABEL : ALERT_CONFIG_DISCARD_LABEL;
|
||||
}
|
||||
|
||||
export function getAlertConfigSwarmGapValidationError() {
|
||||
return ALERT_CONFIG_SWARM_GAP_VALIDATION;
|
||||
}
|
||||
|
||||
export function getAlertConfigQuietHourSuppressOptions() {
|
||||
return ALERT_CONFIG_QUIET_HOUR_SUPPRESS_OPTIONS;
|
||||
}
|
||||
|
||||
@@ -275,17 +275,6 @@ export function getAlertWebhookUrlPlaceholder(urlPattern?: string) {
|
||||
return urlPattern || ALERT_WEBHOOK_URL_PLACEHOLDER;
|
||||
}
|
||||
|
||||
export function getAlertWebhookMentionPlaceholder(service: string) {
|
||||
return (
|
||||
ALERT_WEBHOOK_SERVICE_PRESENTATION[service as AlertWebhookService]?.mentionPlaceholder ||
|
||||
ALERT_WEBHOOK_MENTION_FALLBACK_PLACEHOLDER
|
||||
);
|
||||
}
|
||||
|
||||
export function getAlertWebhookMentionHelp(service: string) {
|
||||
return ALERT_WEBHOOK_SERVICE_PRESENTATION[service as AlertWebhookService]?.mentionHelp || '';
|
||||
}
|
||||
|
||||
export function getAlertWebhookSummaryLabel(enabledCount: number, totalCount: number) {
|
||||
return `${enabledCount} of ${totalCount} webhooks enabled`;
|
||||
}
|
||||
|
||||
@@ -146,10 +146,6 @@ export function parseCloudTier(value?: string | null): CloudTierKey {
|
||||
}
|
||||
}
|
||||
|
||||
export function getCloudPlanForTier(value?: string | null): CloudPlanDefinition {
|
||||
return CLOUD_PLAN_BY_TIER[parseCloudTier(value)];
|
||||
}
|
||||
|
||||
export function getCloudPlanPricePresentation(
|
||||
plan: CloudPlanDefinition,
|
||||
): CloudPlanPricePresentation {
|
||||
|
||||
@@ -48,8 +48,6 @@ export interface HostedCommercialModelInput {
|
||||
guestUsage: number;
|
||||
renewsOrExpires: string;
|
||||
}
|
||||
|
||||
export const SELF_HOSTED_NOT_METERED_LABEL = 'Not metered';
|
||||
export const LIFETIME_DAYS_REMAINING_LABEL = 'Permanent';
|
||||
|
||||
const asUnlimitedLimit = (value?: number) =>
|
||||
|
||||
@@ -307,14 +307,6 @@ export function readInfrastructureSummaryCache(
|
||||
}
|
||||
}
|
||||
|
||||
export function hasFreshInfrastructureSummaryCache(
|
||||
range: TimeRange,
|
||||
maxAgeMs: number = INFRA_SUMMARY_CACHE_MAX_AGE_MS,
|
||||
metrics?: readonly InfrastructureSummaryMetric[] | null,
|
||||
): boolean {
|
||||
return readInfrastructureSummaryCache(range, maxAgeMs, undefined, metrics) !== null;
|
||||
}
|
||||
|
||||
const inFlightFetches = new Map<string, Promise<InfrastructureSummaryFetchResult>>();
|
||||
let infraSummaryFetchSeq = 0;
|
||||
|
||||
|
||||
@@ -60,10 +60,6 @@ export interface LicenseInlineNotice {
|
||||
body: string;
|
||||
}
|
||||
|
||||
export interface LicenseActionNotice extends LicenseInlineNotice {
|
||||
actionLabel: string;
|
||||
}
|
||||
|
||||
export interface BillingAdminOrganizationBadge {
|
||||
label: string;
|
||||
badgeClass: string;
|
||||
|
||||
@@ -758,13 +758,3 @@ export function getContainerRuntimeBadgeForRuntime(runtime?: string | null): Res
|
||||
title: `Runtime: ${label}`,
|
||||
};
|
||||
}
|
||||
|
||||
export function getContainerRuntimeBadge(
|
||||
platformType?: PlatformType,
|
||||
platformData?: Record<string, unknown> | null,
|
||||
): ResourceBadge | null {
|
||||
if (platformType !== 'docker' || !platformData) return null;
|
||||
|
||||
const docker = (platformData as { docker?: { runtime?: string } } | undefined)?.docker;
|
||||
return getContainerRuntimeBadgeForRuntime(docker?.runtime);
|
||||
}
|
||||
|
||||
@@ -163,9 +163,3 @@ export const buildStorageSourceOptionsFromKeys = (
|
||||
const orderedKeys = orderStorageSourceKeys(keys).filter((key) => key !== 'all');
|
||||
return [ALL_STORAGE_SOURCE_OPTION, ...orderedKeys.map((key) => getStorageSourceOption(key))];
|
||||
};
|
||||
|
||||
export const DEFAULT_STORAGE_SOURCE_OPTIONS: StorageSourceOption[] =
|
||||
buildStorageSourceOptionsFromKeys(['proxmox-pve', 'proxmox-pbs', 'ceph', 'truenas']);
|
||||
|
||||
export const buildStorageSourceOptions = (storageList: Storage[]): StorageSourceOption[] =>
|
||||
buildStorageSourceOptionsFromKeys(storageList.map((storage) => resolveStorageSourceKey(storage)));
|
||||
|
||||
@@ -48,11 +48,6 @@ export function fetchStorageSummaryAndCache(
|
||||
return request;
|
||||
}
|
||||
|
||||
export function __resetStorageSummaryCacheForTests(): void {
|
||||
inMemoryCache.clear();
|
||||
inFlightFetches.clear();
|
||||
}
|
||||
|
||||
const unsubscribeStorageOrgSwitch = eventBus.on('org_switched', () => {
|
||||
inMemoryCache.clear();
|
||||
inFlightFetches.clear();
|
||||
|
||||
@@ -39,12 +39,6 @@ export function getUnifiedAgentLastSeenLabel(
|
||||
return row.lastSeen ? formatRelativeTime(row.lastSeen) : '—';
|
||||
}
|
||||
|
||||
export function getMonitoringStoppedEmptyState(hasFilters: boolean): string {
|
||||
return hasFilters
|
||||
? 'No monitoring-stopped items match the current filters.'
|
||||
: 'No infrastructure currently has monitoring stopped.';
|
||||
}
|
||||
|
||||
export function getUnifiedAgentStopMonitoringUnavailableMessage(): string {
|
||||
return 'No host identifiers are available to stop monitoring.';
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ export interface UnifiedAgentLookupStatusPresentation {
|
||||
}
|
||||
|
||||
export const MONITORING_STOPPED_STATUS_LABEL = 'Monitoring stopped';
|
||||
export const ALLOW_RECONNECT_LABEL = 'Allow reconnect';
|
||||
|
||||
export function getUnifiedAgentStatusPresentation(
|
||||
state: UnifiedAgentMonitoringState,
|
||||
|
||||
@@ -9,7 +9,6 @@ import { normalizeOrgScope } from '@/utils/orgScope';
|
||||
import { eventBus } from '@/stores/events';
|
||||
|
||||
export const WORKLOADS_SUMMARY_CACHE_VERSION = 6;
|
||||
export const WORKLOAD_CHART_DEFAULT_POINT_LIMIT = 180;
|
||||
|
||||
const WORKLOADS_SUMMARY_CACHE_PREFIX = 'pulse.workloadsSummaryCharts.';
|
||||
const WORKLOADS_SUMMARY_CACHE_MAX_AGE_MS = 5 * 60_000;
|
||||
|
||||
@@ -21,16 +21,16 @@ const (
|
||||
FieldContainerStatus PostconditionField = "status"
|
||||
|
||||
// Systemd unit fields read via DBus or systemctl show.
|
||||
FieldUnitActiveState PostconditionField = "ActiveState"
|
||||
FieldUnitSubState PostconditionField = "SubState"
|
||||
FieldUnitActiveEnterTimestamp PostconditionField = "ActiveEnterTimestamp"
|
||||
FieldUnitActiveState PostconditionField = "ActiveState"
|
||||
FieldUnitSubState PostconditionField = "SubState"
|
||||
FieldUnitActiveEnterTimestamp PostconditionField = "ActiveEnterTimestamp"
|
||||
|
||||
// Docker container fields.
|
||||
FieldDockerStatus PostconditionField = "status"
|
||||
FieldDockerLastStarted PostconditionField = "last_started"
|
||||
|
||||
// Kubernetes deployment fields.
|
||||
FieldDeploymentReadyReplicas PostconditionField = "readyReplicas"
|
||||
FieldDeploymentReadyReplicas PostconditionField = "readyReplicas"
|
||||
FieldDeploymentDesiredReplicas PostconditionField = "desiredReplicas"
|
||||
)
|
||||
|
||||
@@ -69,11 +69,11 @@ type PostconditionCheck struct {
|
||||
// postcondition to be observed; capabilities with no natural settle
|
||||
// (a single-shot status read) use the default.
|
||||
type CapabilityPostcondition struct {
|
||||
Capability string `json:"capability"`
|
||||
VerifyRead string `json:"verifyRead"`
|
||||
Window time.Duration `json:"window"`
|
||||
Description string `json:"description"`
|
||||
Checks []PostconditionCheck `json:"checks"`
|
||||
Capability string `json:"capability"`
|
||||
VerifyRead string `json:"verifyRead"`
|
||||
Window time.Duration `json:"window"`
|
||||
Description string `json:"description"`
|
||||
Checks []PostconditionCheck `json:"checks"`
|
||||
}
|
||||
|
||||
// defaultVerifyWindow is the per-capability fallback window. The agentexec
|
||||
@@ -82,23 +82,6 @@ type CapabilityPostcondition struct {
|
||||
// this surface" hint.
|
||||
const defaultVerifyWindow = 2 * time.Minute
|
||||
|
||||
// CapabilityPostconditions returns the canonical capability -> postcondition
|
||||
// map for the verifier substrate. The returned map is a copy so callers
|
||||
// cannot mutate the shared substrate.
|
||||
//
|
||||
// Capabilities not present in this map have no automated postcondition and
|
||||
// will surface as VerificationUnknown on the audit record.
|
||||
func CapabilityPostconditions() map[string]CapabilityPostcondition {
|
||||
out := make(map[string]CapabilityPostcondition, len(capabilityPostconditions))
|
||||
for k, v := range capabilityPostconditions {
|
||||
copyChecks := make([]PostconditionCheck, len(v.Checks))
|
||||
copy(copyChecks, v.Checks)
|
||||
v.Checks = copyChecks
|
||||
out[k] = v
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// LookupCapabilityPostcondition returns the postcondition for the given
|
||||
// capability name, or false if no postcondition is registered.
|
||||
func LookupCapabilityPostcondition(capability string) (CapabilityPostcondition, bool) {
|
||||
|
||||
@@ -333,14 +333,6 @@ func (u *Updater) CheckAndUpdate(ctx context.Context) {
|
||||
u.logger.Info().Msg("agent updated successfully, restarting")
|
||||
}
|
||||
|
||||
// setAuthHeaders adds authentication headers to the request if an API token is configured.
|
||||
func (u *Updater) setAuthHeaders(req *http.Request) {
|
||||
if u.cfg.APIToken != "" {
|
||||
req.Header.Set(apiTokenHeader, u.cfg.APIToken)
|
||||
req.Header.Set(authorizationHeader, bearerTokenPrefix+u.cfg.APIToken)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *Updater) startCheck() bool {
|
||||
u.checkMu.Lock()
|
||||
defer u.checkMu.Unlock()
|
||||
|
||||
@@ -85,12 +85,6 @@ func sessionFSMState(fsm *SessionFSM) string {
|
||||
return string(fsm.State)
|
||||
}
|
||||
|
||||
func (a *AgenticLoop) currentFSMState() string {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
return sessionFSMState(a.sessionFSM)
|
||||
}
|
||||
|
||||
func fallbackProviderStreamErrorMessage(err error) string {
|
||||
const defaultMessage = "AI response stream interrupted before completion. Please retry."
|
||||
if err == nil {
|
||||
|
||||
@@ -50,26 +50,6 @@ type mockBaselineStore struct {
|
||||
anomalies map[string]baselineResult
|
||||
}
|
||||
|
||||
func (m *mockBaselineStore) CheckAnomaly(resourceID, metric string, value float64) (baseline.AnomalySeverity, float64, *baseline.MetricBaseline) {
|
||||
res, ok := m.anomalies[resourceID+":"+metric]
|
||||
if !ok {
|
||||
return baseline.AnomalyNone, 0, &baseline.MetricBaseline{}
|
||||
}
|
||||
return res.severity, res.zScore, res.bl
|
||||
}
|
||||
|
||||
func (m *mockBaselineStore) GetBaseline(resourceID, metric string) (*baseline.MetricBaseline, bool) {
|
||||
res, ok := m.anomalies[resourceID+":"+metric]
|
||||
if !ok {
|
||||
return nil, false
|
||||
}
|
||||
return res.bl, true
|
||||
}
|
||||
|
||||
func (m *mockBaselineStore) Update(resourceID, metric string, value float64) {}
|
||||
func (m *mockBaselineStore) Save() error { return nil }
|
||||
func (m *mockBaselineStore) Load() error { return nil }
|
||||
|
||||
func TestGenerateRemediationSummary(t *testing.T) {
|
||||
tests := []struct {
|
||||
command string
|
||||
@@ -643,19 +623,3 @@ func TestService_BuildRecentResourceChangesContext_FallsBackToMemoryFormatter(t
|
||||
|
||||
type mockIncidentStore struct {
|
||||
}
|
||||
|
||||
func (m *mockIncidentStore) FormatForAlert(alertID string, limit int) string {
|
||||
return "alert:" + alertID
|
||||
}
|
||||
|
||||
func (m *mockIncidentStore) FormatForResource(resourceID string, limit int) string {
|
||||
return "res:" + resourceID
|
||||
}
|
||||
|
||||
func (m *mockIncidentStore) FormatForPatrol(limit int) string {
|
||||
return "patrol"
|
||||
}
|
||||
|
||||
func (m *mockIncidentStore) Record(resourceID, resourceType, alertID, analysis, remediation string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -157,14 +157,3 @@ func cloneStringSlice(values []string) []string {
|
||||
copy(cloned, values)
|
||||
return cloned
|
||||
}
|
||||
|
||||
func cloneStringMap(values map[string]string) map[string]string {
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
cloned := make(map[string]string, len(values))
|
||||
for k, v := range values {
|
||||
cloned[k] = v
|
||||
}
|
||||
return cloned
|
||||
}
|
||||
|
||||
@@ -194,13 +194,6 @@ func normalizeConfig(cfg Config) Config {
|
||||
return cfg
|
||||
}
|
||||
|
||||
func normalizeContext(ctx context.Context) context.Context {
|
||||
if ctx == nil {
|
||||
return context.Background()
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
// NewService creates a new infrastructure discovery service.
|
||||
func NewService(knowledgeStore *knowledge.Store, cfg Config) *Service {
|
||||
cfg = normalizeConfig(cfg)
|
||||
@@ -245,21 +238,6 @@ func (s *Service) SetReadState(rs unifiedresources.ReadState) {
|
||||
}
|
||||
}
|
||||
|
||||
// goRecover launches fn in a goroutine with panic recovery logging.
|
||||
func goRecover(label string, fn func()) {
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
log.Error().
|
||||
Interface("panic", r).
|
||||
Stack().
|
||||
Msgf("Recovered from panic in %s", label)
|
||||
}
|
||||
}()
|
||||
fn()
|
||||
}()
|
||||
}
|
||||
|
||||
// Start begins the background discovery service.
|
||||
func (s *Service) Start(ctx context.Context) {
|
||||
if ctx == nil {
|
||||
@@ -914,22 +892,6 @@ func (s *Service) saveDiscoveries(apps []DiscoveredApp) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) tryStartDiscovery() bool {
|
||||
s.discoveryMu.Lock()
|
||||
defer s.discoveryMu.Unlock()
|
||||
if s.discoveryRun {
|
||||
return false
|
||||
}
|
||||
s.discoveryRun = true
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *Service) finishDiscovery() {
|
||||
s.discoveryMu.Lock()
|
||||
s.discoveryRun = false
|
||||
s.discoveryMu.Unlock()
|
||||
}
|
||||
|
||||
// GetDiscoveries returns the cached list of discovered applications.
|
||||
func (s *Service) GetDiscoveries() []DiscoveredApp {
|
||||
s.mu.RLock()
|
||||
|
||||
@@ -1315,15 +1315,6 @@ func (i *Intelligence) getResourcesAtRisk(limit int) []ResourceRiskSummary {
|
||||
return summaries
|
||||
}
|
||||
|
||||
func (i *Intelligence) detectCurrentAnomalies(resourceID string) []AnomalyReport {
|
||||
if i.anomalyDetector != nil {
|
||||
return i.anomalyDetector(resourceID)
|
||||
}
|
||||
// This would be called with current metrics from state
|
||||
// For now, return empty - will be integrated with patrol
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *Intelligence) getRecentChangesForResource(resourceID string, limit int) []unifiedresources.ResourceChange {
|
||||
resourceID = strings.TrimSpace(resourceID)
|
||||
if resourceID == "" || limit <= 0 {
|
||||
|
||||
@@ -174,41 +174,6 @@ func (d *ChangeDetector) DetectChanges(currentSnapshots []ResourceSnapshot) []Ch
|
||||
return newChanges
|
||||
}
|
||||
|
||||
func (d *ChangeDetector) requestAsyncSave() {
|
||||
if d.dataDir == "" {
|
||||
return
|
||||
}
|
||||
|
||||
d.saveStateMu.Lock()
|
||||
d.saveRequested = true
|
||||
if d.saveRunning {
|
||||
d.saveStateMu.Unlock()
|
||||
return
|
||||
}
|
||||
d.saveRunning = true
|
||||
d.saveStateMu.Unlock()
|
||||
|
||||
go d.runSaveLoop()
|
||||
}
|
||||
|
||||
func (d *ChangeDetector) runSaveLoop() {
|
||||
for {
|
||||
d.saveStateMu.Lock()
|
||||
shouldSave := d.saveRequested
|
||||
d.saveRequested = false
|
||||
if !shouldSave {
|
||||
d.saveRunning = false
|
||||
d.saveStateMu.Unlock()
|
||||
return
|
||||
}
|
||||
d.saveStateMu.Unlock()
|
||||
|
||||
if err := d.saveToDisk(); err != nil {
|
||||
log.Warn().Err(err).Msg("Failed to save change history")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// detectResourceChanges checks for changes between two snapshots of the same resource
|
||||
func (d *ChangeDetector) detectResourceChanges(prev, current ResourceSnapshot, now time.Time) []Change {
|
||||
var changes []Change
|
||||
|
||||
@@ -85,16 +85,6 @@ type ContextStoreConfig struct {
|
||||
RelevanceDecayDays int // Days after which relevance starts decaying
|
||||
}
|
||||
|
||||
// DefaultContextStoreConfig returns sensible defaults
|
||||
func DefaultContextStoreConfig() ContextStoreConfig {
|
||||
return ContextStoreConfig{
|
||||
MaxMemoriesPerType: 1000,
|
||||
MaxResourceNotes: 20,
|
||||
RetentionDays: 90,
|
||||
RelevanceDecayDays: 7,
|
||||
}
|
||||
}
|
||||
|
||||
// ContextStore stores and manages persistent AI context
|
||||
type ContextStore struct {
|
||||
mu sync.RWMutex
|
||||
@@ -370,31 +360,6 @@ func (s *ContextStore) Recall(resourceID string) []Memory {
|
||||
return result
|
||||
}
|
||||
|
||||
// RecallByType retrieves memories of a specific type
|
||||
func (s *ContextStore) RecallByType(memType MemoryType, limit int) []Memory {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
var result []Memory
|
||||
if memories, ok := s.memories[memType]; ok {
|
||||
for _, mem := range memories {
|
||||
s.markUsedLocked(mem)
|
||||
result = append(result, *mem)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort by relevance
|
||||
sort.Slice(result, func(i, j int) bool {
|
||||
return result[i].Relevance > result[j].Relevance
|
||||
})
|
||||
|
||||
if limit > 0 && len(result) > limit {
|
||||
result = result[:limit]
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetResourceMemory returns the memory for a specific resource
|
||||
func (s *ContextStore) GetResourceMemory(resourceID string) *ResourceMemory {
|
||||
s.mu.RLock()
|
||||
|
||||
@@ -1253,13 +1253,6 @@ func maxInt(a, b int) int {
|
||||
return b
|
||||
}
|
||||
|
||||
func minInt(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (p *PatrolService) assembleSeedWithinBudget(sections []seedSection, budgetTokens int) string {
|
||||
if len(sections) == 0 {
|
||||
return ""
|
||||
|
||||
@@ -1872,10 +1872,6 @@ func patrolRegisterResourceMetrics(dest map[string]map[string]float64, metrics m
|
||||
}
|
||||
}
|
||||
|
||||
func patrolGuestMatches(guestID, id, name string, vmid int) bool {
|
||||
return id == guestID || name == guestID || fmt.Sprintf("%d", vmid) == guestID
|
||||
}
|
||||
|
||||
func patrolFirstIP(ips []string) string {
|
||||
if len(ips) == 0 {
|
||||
return ""
|
||||
|
||||
@@ -419,17 +419,6 @@ func patrolRuntimeSortedResourceIDs(s patrolRuntimeState) []string {
|
||||
return ids
|
||||
}
|
||||
|
||||
func patrolRuntimeStorageResourceCount(s patrolRuntimeState) int {
|
||||
count := 0
|
||||
patrolVisitRuntimeResources(s, func(record patrolRuntimeResourceRecord) bool {
|
||||
if record.kind == patrolRuntimeResourceStorage || record.kind == patrolRuntimeResourcePhysicalDisk {
|
||||
count++
|
||||
}
|
||||
return true
|
||||
})
|
||||
return count
|
||||
}
|
||||
|
||||
type patrolRuntimeResourceCounts struct {
|
||||
nodes int
|
||||
guests int
|
||||
|
||||
@@ -180,14 +180,6 @@ func (c *OpenAIClient) shouldSendReasoningContent() bool {
|
||||
return c.isDeepSeek()
|
||||
}
|
||||
|
||||
func normalizeOpenAICompatibleModelName(model string) string {
|
||||
model = strings.ToLower(strings.TrimSpace(model))
|
||||
for _, prefix := range []string{"openai:", "openrouter:", "deepseek:"} {
|
||||
model = strings.TrimPrefix(model, prefix)
|
||||
}
|
||||
return model
|
||||
}
|
||||
|
||||
func (c *OpenAIClient) applyProviderHeaders(req *http.Request) {
|
||||
if !c.isOpenRouter() {
|
||||
return
|
||||
|
||||
@@ -127,10 +127,6 @@ type ChatResponse struct {
|
||||
OutputTokens int `json:"output_tokens,omitempty"`
|
||||
}
|
||||
|
||||
func EmptyChatResponse() ChatResponse {
|
||||
return ChatResponse{}.NormalizeCollections()
|
||||
}
|
||||
|
||||
func (r ChatResponse) NormalizeCollections() ChatResponse {
|
||||
if r.ToolCalls == nil {
|
||||
r.ToolCalls = []ToolCall{}
|
||||
|
||||
@@ -2,18 +2,6 @@ package tools
|
||||
|
||||
import "sync"
|
||||
|
||||
// KnownToolNames returns the canonical list of registered Pulse tool names,
|
||||
// keyed off the registry built by registerTools(). The list is built lazily
|
||||
// on first call via a throwaway executor so adding a new tool to
|
||||
// registerTools() automatically extends the allowlist — no separate
|
||||
// hand-maintained list to drift out of sync.
|
||||
func KnownToolNames() []string {
|
||||
initKnownToolNames()
|
||||
out := make([]string, len(knownToolNamesList))
|
||||
copy(out, knownToolNamesList)
|
||||
return out
|
||||
}
|
||||
|
||||
// IsKnownToolName reports whether name is one of the canonical Pulse tool
|
||||
// names. Used by chat-content sanitisers to gate stripping on a closed
|
||||
// allowlist (e.g. JSON tool-call shapes leaked by weak local models that
|
||||
|
||||
@@ -238,23 +238,6 @@ const (
|
||||
ErrCodeNoAgent = "NO_AGENT"
|
||||
)
|
||||
|
||||
// NewToolSuccess creates a successful tool response
|
||||
func NewToolSuccess(data interface{}) ToolResponse {
|
||||
return ToolResponse{
|
||||
OK: true,
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// NewToolSuccessWithMeta creates a successful tool response with metadata
|
||||
func NewToolSuccessWithMeta(data interface{}, meta map[string]interface{}) ToolResponse {
|
||||
return ToolResponse{
|
||||
OK: true,
|
||||
Data: data,
|
||||
Meta: meta,
|
||||
}
|
||||
}
|
||||
|
||||
// NewToolBlockedError creates a policy/validation blocked error
|
||||
func NewToolBlockedError(code, message string, details map[string]interface{}) ToolResponse {
|
||||
return ToolResponse{
|
||||
@@ -268,20 +251,6 @@ func NewToolBlockedError(code, message string, details map[string]interface{}) T
|
||||
}
|
||||
}
|
||||
|
||||
// NewToolFailedError creates a runtime failure error
|
||||
func NewToolFailedError(code, message string, retryable bool, details map[string]interface{}) ToolResponse {
|
||||
return ToolResponse{
|
||||
OK: false,
|
||||
Error: &ToolError{
|
||||
Code: code,
|
||||
Message: message,
|
||||
Failed: true,
|
||||
Retryable: retryable,
|
||||
Details: details,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
// NewTextContent creates a text content object
|
||||
|
||||
@@ -1581,24 +1581,6 @@ func extractServiceUnitName(command string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// extractContainerName parses the container name out of a docker/podman
|
||||
// command of the form "docker restart <name>" or "podman stop <name>".
|
||||
func extractContainerName(command string) string {
|
||||
fields := strings.Fields(strings.TrimSpace(command))
|
||||
if len(fields) < 3 {
|
||||
return ""
|
||||
}
|
||||
first := strings.ToLower(fields[0])
|
||||
if first != "docker" && first != "podman" {
|
||||
return ""
|
||||
}
|
||||
verb := strings.ToLower(fields[1])
|
||||
if verb != "restart" && verb != "stop" && verb != "start" {
|
||||
return ""
|
||||
}
|
||||
return fields[2]
|
||||
}
|
||||
|
||||
// shellQuoteSingle wraps the value in single quotes and escapes any
|
||||
// embedded single quotes for shell-safe inclusion in the verification
|
||||
// command. The verification path runs through the same agent dispatch as
|
||||
|
||||
@@ -28,10 +28,6 @@ type stubDiskHealthProvider struct {
|
||||
hosts []*unifiedresources.HostView
|
||||
}
|
||||
|
||||
func (s *stubDiskHealthProvider) GetHosts() []*unifiedresources.HostView {
|
||||
return s.hosts
|
||||
}
|
||||
|
||||
type stubUpdatesProvider struct {
|
||||
pending []ContainerUpdateInfo
|
||||
enabled bool
|
||||
|
||||
@@ -39,18 +39,6 @@ func newSummarizeTestEnvironment(t *testing.T) (*PulseToolExecutor, func()) {
|
||||
return exec, cleanup
|
||||
}
|
||||
|
||||
func writeMetricSamples(t *testing.T, dir string, store *metrics.Store, resourceID string, value float64, count int) {
|
||||
t.Helper()
|
||||
now := time.Now()
|
||||
for i := 0; i < count; i++ {
|
||||
ts := now.Add(time.Duration(-30+i*2) * time.Minute)
|
||||
store.Write("node", resourceID, "cpu", value, ts)
|
||||
store.Write("node", resourceID, "memory", value-10, ts)
|
||||
}
|
||||
store.Flush()
|
||||
_ = dir
|
||||
}
|
||||
|
||||
func TestSummarizeTool_RegisteredAndDiscoverable(t *testing.T) {
|
||||
exec, cleanup := newSummarizeTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
@@ -91,21 +91,6 @@ func (i *Integration) SetPatrolTrigger(fn PatrolTriggerFunc) {
|
||||
i.bridge.SetPatrolTrigger(fn)
|
||||
}
|
||||
|
||||
// Start starts the unified system
|
||||
func (i *Integration) Start() {
|
||||
i.bridge.Start()
|
||||
log.Info().Msg("unified alert/finding system started")
|
||||
}
|
||||
|
||||
// Stop stops the unified system
|
||||
func (i *Integration) Stop() {
|
||||
i.bridge.Stop()
|
||||
if err := i.store.ForceSave(); err != nil {
|
||||
log.Error().Err(err).Msg("failed to save unified findings on shutdown")
|
||||
}
|
||||
log.Info().Msg("unified alert/finding system stopped")
|
||||
}
|
||||
|
||||
// GetStore returns the unified store
|
||||
func (i *Integration) GetStore() *UnifiedStore {
|
||||
return i.store
|
||||
|
||||
@@ -198,13 +198,6 @@ func (b *callbackBus) alertForAICallbacks() []func(alert *Alert) {
|
||||
return callbacks
|
||||
}
|
||||
|
||||
func (b *callbackBus) resolvedCallback() func(alertID string) {
|
||||
b.mu.RLock()
|
||||
cb := b.onResolved
|
||||
b.mu.RUnlock()
|
||||
return cb
|
||||
}
|
||||
|
||||
func (b *callbackBus) resolvedCallbacks() []func(alertID string) {
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
@@ -330,10 +323,6 @@ func (m *Manager) getAlertForAICallbacks() []func(alert *Alert) {
|
||||
return m.callbacks.alertForAICallbacks()
|
||||
}
|
||||
|
||||
func (m *Manager) getResolvedCallback() func(alertID string) {
|
||||
return m.callbacks.resolvedCallback()
|
||||
}
|
||||
|
||||
func (m *Manager) getResolvedCallbacks() []func(alertID string) {
|
||||
return m.callbacks.resolvedCallbacks()
|
||||
}
|
||||
|
||||
@@ -66,28 +66,7 @@ func ensureValidHysteresis(threshold *HysteresisThreshold, metricName string) {
|
||||
alertconfig.EnsureValidHysteresis(threshold, metricName)
|
||||
}
|
||||
|
||||
func normalizeStorageDefaults(config *AlertConfig) { alertconfig.NormalizeStorageDefaults(config) }
|
||||
|
||||
func normalizeDockerThreshold(th HysteresisThreshold, defaultTrigger float64, metricName string) HysteresisThreshold {
|
||||
return alertconfig.NormalizeDockerThreshold(th, defaultTrigger, metricName)
|
||||
}
|
||||
|
||||
func normalizeDockerDefaults(config *AlertConfig) { alertconfig.NormalizeDockerDefaults(config) }
|
||||
func normalizePMGDefaults(config *AlertConfig) { alertconfig.NormalizePMGDefaults(config) }
|
||||
func normalizeSnapshotDefaults(config *AlertConfig) { alertconfig.NormalizeSnapshotDefaults(config) }
|
||||
func normalizeBackupDefaults(config *AlertConfig) { alertconfig.NormalizeBackupDefaults(config) }
|
||||
func normalizeNodeDefaults(config *AlertConfig) { alertconfig.NormalizeNodeDefaults(config) }
|
||||
func normalizeAgentDefaults(config *AlertConfig) { alertconfig.NormalizeAgentDefaults(config) }
|
||||
func normalizeKubernetesDefaults(config *AlertConfig) {
|
||||
alertconfig.NormalizeKubernetesDefaults(config)
|
||||
}
|
||||
func normalizeTrueNASDefaults(config *AlertConfig) { alertconfig.NormalizeTrueNASDefaults(config) }
|
||||
func normalizeVMwareDefaults(config *AlertConfig) { alertconfig.NormalizeVMwareDefaults(config) }
|
||||
func normalizeGeneralSettings(config *AlertConfig) { alertconfig.NormalizeGeneralSettings(config) }
|
||||
func normalizeTimeThresholds(config *AlertConfig) { alertconfig.NormalizeTimeThresholds(config) }
|
||||
|
||||
func validateHysteresisThresholds(config *AlertConfig) {
|
||||
alertconfig.ValidateHysteresisThresholds(config)
|
||||
}
|
||||
|
||||
func validateQuietHoursTimezone(config *AlertConfig) { alertconfig.ValidateQuietHoursTimezone(config) }
|
||||
|
||||
@@ -1066,16 +1066,6 @@ type OverridePolicy struct {
|
||||
ExpiresAt *time.Time `json:"expiresAt,omitempty"`
|
||||
}
|
||||
|
||||
func (p OverridePolicy) Validate() error {
|
||||
if err := p.Target.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if p.ExpiresAt != nil && p.ExpiresAt.IsZero() {
|
||||
return fmt.Errorf("expires at must be set when provided")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func isKnownResourceType(rt unifiedresources.ResourceType) bool {
|
||||
if rt == "" {
|
||||
return false
|
||||
|
||||
@@ -109,16 +109,6 @@ func unifiedAlertType(typeKey string) string {
|
||||
}
|
||||
}
|
||||
|
||||
// isUnifiedGuestType returns true for resource types that support I/O metrics.
|
||||
func isUnifiedGuestType(typeKey string) bool {
|
||||
switch typeKey {
|
||||
case "vm", "system-container", "app-container":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func supportsUnifiedIOMetrics(typeKey string) bool {
|
||||
switch typeKey {
|
||||
case "vm", "system-container", "app-container", "k8s-cluster", "k8s-deployment", "pod", "truenas-system", "vmware-host", "vmware-vm":
|
||||
@@ -597,32 +587,6 @@ func unifiedResourceAlertInstance(resource unifiedresources.Resource, typeKey st
|
||||
return ""
|
||||
}
|
||||
|
||||
func mergeMetricOptions(base *metricOptions, extra map[string]interface{}) *metricOptions {
|
||||
if len(extra) == 0 {
|
||||
return base
|
||||
}
|
||||
|
||||
merged := &metricOptions{}
|
||||
if base != nil {
|
||||
*merged = *base
|
||||
}
|
||||
if len(extra) > 0 {
|
||||
if merged.Metadata == nil {
|
||||
merged.Metadata = make(map[string]interface{}, len(extra))
|
||||
} else {
|
||||
copied := make(map[string]interface{}, len(merged.Metadata)+len(extra))
|
||||
for k, v := range merged.Metadata {
|
||||
copied[k] = v
|
||||
}
|
||||
merged.Metadata = copied
|
||||
}
|
||||
for k, v := range extra {
|
||||
merged.Metadata[k] = v
|
||||
}
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
func (i *UnifiedResourceInput) CPUValue() float64 {
|
||||
if i == nil || i.CPU == nil {
|
||||
return 0
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
"time"
|
||||
|
||||
alertspecs "github.com/rcourtman/pulse-go-rewrite/internal/alerts/specs"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/storagehealth"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/unifiedresources"
|
||||
)
|
||||
|
||||
@@ -261,17 +260,6 @@ func resourceSupportsUnifiedIncidentAlerts(resource unifiedresources.Resource) b
|
||||
}
|
||||
}
|
||||
|
||||
func alertLevelFromIncidentSeverity(level storagehealth.RiskLevel) (AlertLevel, bool) {
|
||||
switch level {
|
||||
case storagehealth.RiskCritical:
|
||||
return AlertLevelCritical, true
|
||||
case storagehealth.RiskWarning:
|
||||
return AlertLevelWarning, true
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
}
|
||||
|
||||
func alertLevelFromCanonicalSeverity(level alertspecs.AlertSeverity) (AlertLevel, bool) {
|
||||
switch level {
|
||||
case alertspecs.AlertSeverityCritical:
|
||||
@@ -526,39 +514,6 @@ func unifiedIncidentConsumerSummary(storage *unifiedresources.StorageMeta) strin
|
||||
return unifiedresources.StorageConsumerImpactSummary(storage)
|
||||
}
|
||||
|
||||
func unifiedIncidentDependentConsumerSummary(storage *unifiedresources.StorageMeta) string {
|
||||
if storage == nil || storage.ConsumerCount <= 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
names := make([]string, 0, len(storage.TopConsumers))
|
||||
for _, consumer := range storage.TopConsumers {
|
||||
name := strings.TrimSpace(consumer.Name)
|
||||
if name == "" {
|
||||
continue
|
||||
}
|
||||
names = append(names, name)
|
||||
if len(names) == 3 {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
resourceLabel := "dependent resource"
|
||||
if storage.ConsumerCount != 1 {
|
||||
resourceLabel = "dependent resources"
|
||||
}
|
||||
|
||||
if len(names) == 0 {
|
||||
return "Affects " + intLabel(storage.ConsumerCount) + " " + resourceLabel
|
||||
}
|
||||
|
||||
if remaining := storage.ConsumerCount - len(names); remaining > 0 {
|
||||
return "Affects " + intLabel(storage.ConsumerCount) + " " + resourceLabel + ": " + strings.Join(names, ", ") + ", and " + intLabel(remaining) + " more"
|
||||
}
|
||||
|
||||
return "Affects " + intLabel(storage.ConsumerCount) + " " + resourceLabel + ": " + strings.Join(names, ", ")
|
||||
}
|
||||
|
||||
func unifiedIncidentBackupConsumerSummary(storage *unifiedresources.StorageMeta) string {
|
||||
if storage == nil || storage.ConsumerCount <= 0 {
|
||||
return ""
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -384,10 +383,3 @@ func (b *AgentEventBroadcaster) PublishActionCompletedRecord(record unifiedresou
|
||||
// call sites don't need this — they hold a direct reference — but
|
||||
// keeping it here makes the dependency explicit.
|
||||
type agentEventBroadcasterContextKey struct{}
|
||||
|
||||
// ContextWithAgentEventBroadcaster attaches a broadcaster to a
|
||||
// context. Used by integration tests that need to inject a fake
|
||||
// broadcaster.
|
||||
func ContextWithAgentEventBroadcaster(ctx context.Context, b *AgentEventBroadcaster) context.Context {
|
||||
return context.WithValue(ctx, agentEventBroadcasterContextKey{}, b)
|
||||
}
|
||||
|
||||
@@ -19,14 +19,6 @@ func loadHostedAwareAIConfig(hostedMode bool, billingBaseDir, orgID string, pers
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func shouldAutoBootstrapHostedAIConfig(hostedMode bool, persistence *config.ConfigPersistence) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func hostedAIAutoBootstrapEligible(state *billingState) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func hostedModeEnabledFromEnv() bool {
|
||||
return os.Getenv("PULSE_HOSTED_MODE") == "true"
|
||||
}
|
||||
|
||||
@@ -584,10 +584,6 @@ type incidentEventView struct {
|
||||
Details map[string]interface{} `json:"details"`
|
||||
}
|
||||
|
||||
func emptyIncidentEventView() incidentEventView {
|
||||
return incidentEventView{}.NormalizeCollections()
|
||||
}
|
||||
|
||||
func (v incidentEventView) NormalizeCollections() incidentEventView {
|
||||
if v.Details == nil {
|
||||
v.Details = map[string]interface{}{}
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/ai/chat"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
@@ -14,8 +13,6 @@ import (
|
||||
// Mock implementation of chat.StateProvider
|
||||
type mockChatStateProvider struct{}
|
||||
|
||||
func (m *mockChatStateProvider) ReadSnapshot() models.StateSnapshot { return models.StateSnapshot{} }
|
||||
|
||||
func TestChatServiceAdapter_CreateSession(t *testing.T) {
|
||||
// Setup real chat service with minimal config
|
||||
cfg := chat.Config{
|
||||
|
||||
@@ -30,15 +30,6 @@ func healthEntry(lastSuccess *time.Time, errMessage, errCategory string, breaker
|
||||
}
|
||||
}
|
||||
|
||||
func desiredAgentConfigFingerprint(t *testing.T, commandsEnabled *bool, settings map[string]interface{}) ConnectionFleetConfigFingerprint {
|
||||
t.Helper()
|
||||
desired := desiredAgentConfig(t, commandsEnabled, settings)
|
||||
if desired.Fingerprint == nil {
|
||||
t.Fatal("expected desired config fingerprint")
|
||||
}
|
||||
return *desired.Fingerprint
|
||||
}
|
||||
|
||||
func desiredAgentConfig(t *testing.T, commandsEnabled *bool, settings map[string]interface{}) connectionAgentDesiredConfig {
|
||||
t.Helper()
|
||||
metadata, err := remoteconfig.BuildDesiredConfigMetadata(commandsEnabled, settings)
|
||||
|
||||
@@ -17,15 +17,6 @@ type aggregatorRuntimeSources struct {
|
||||
truenasPoller *monitoring.TrueNASPoller
|
||||
}
|
||||
|
||||
// buildAggregatorInputs gathers the same inputs the HTTP connections handler
|
||||
// uses so the alerts pipeline can derive identical Connection rows without
|
||||
// going through the HTTP layer. Returning an empty aggregatorInputs when a
|
||||
// dependency is unavailable keeps the alerts loop a no-op rather than a hard
|
||||
// failure.
|
||||
func buildAggregatorInputs(ctx context.Context, cfg *config.Config, persistence *config.ConfigPersistence, monitor *monitoring.Monitor) aggregatorInputs {
|
||||
return buildAggregatorInputsWithRuntimeSources(ctx, cfg, persistence, monitor, aggregatorRuntimeSources{})
|
||||
}
|
||||
|
||||
func buildAggregatorInputsWithRuntimeSources(
|
||||
ctx context.Context,
|
||||
cfg *config.Config,
|
||||
@@ -137,14 +128,6 @@ func snapshotConnectionsForAlerts(connections []Connection) []alerts.ConnectionS
|
||||
return out
|
||||
}
|
||||
|
||||
// BuildAlertConnectionSnapshots returns the platform-connection snapshots the
|
||||
// alerts package consumes for the connection-degraded check. This is the
|
||||
// monitor-loop counterpart to the HTTP connections handler — it runs the
|
||||
// same derivation but skips the JSON envelope.
|
||||
func BuildAlertConnectionSnapshots(ctx context.Context, cfg *config.Config, persistence *config.ConfigPersistence, monitor *monitoring.Monitor) []alerts.ConnectionSnapshot {
|
||||
return buildAlertConnectionSnapshotsWithRuntimeSources(ctx, cfg, persistence, monitor, aggregatorRuntimeSources{})
|
||||
}
|
||||
|
||||
func buildAlertConnectionSnapshotsWithRuntimeSources(
|
||||
ctx context.Context,
|
||||
cfg *config.Config,
|
||||
|
||||
@@ -113,14 +113,6 @@ func pbsHandler(body string) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func pmgHandler(body string) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Server", "pmg-api-daemon/8.1")
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
fmt.Fprint(w, body)
|
||||
}
|
||||
}
|
||||
|
||||
func vmwareHandler() http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/xml")
|
||||
|
||||
@@ -111,14 +111,6 @@ func publicDemoCommercialPolicyForRequest(
|
||||
return "", false
|
||||
}
|
||||
|
||||
func publicDemoCommercialRouteInventory() []string {
|
||||
routes := make([]string, 0, len(publicDemoCommercialPolicies))
|
||||
for _, policy := range publicDemoCommercialPolicies {
|
||||
routes = append(routes, policy.route)
|
||||
}
|
||||
return routes
|
||||
}
|
||||
|
||||
func sanitizeRuntimeCapabilitiesPayloadForPublicDemo(
|
||||
payload RuntimeCapabilitiesPayload,
|
||||
) RuntimeCapabilitiesPayload {
|
||||
|
||||
@@ -986,18 +986,3 @@ func TestEntitlementHandler_CommercialMigrationDoesNotExposeTrialStartReason(t *
|
||||
t.Fatalf("trial_eligibility_reason=%q, want empty", payload.TrialEligibilityReason)
|
||||
}
|
||||
}
|
||||
|
||||
// countProMinusFreeFeatures returns the number of Pro features not included in Free.
|
||||
func countProMinusFreeFeatures() int {
|
||||
freeSet := make(map[string]struct{}, len(license.TierFeatures[license.TierFree]))
|
||||
for _, f := range license.TierFeatures[license.TierFree] {
|
||||
freeSet[f] = struct{}{}
|
||||
}
|
||||
count := 0
|
||||
for _, f := range license.TierFeatures[license.TierPro] {
|
||||
if _, ok := freeSet[f]; !ok {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
@@ -279,11 +279,6 @@ func writeLicenseRequiredFromLicensing(w http.ResponseWriter, feature, message s
|
||||
// licenseTierFreeValue is the canonical free-tier constant for use outside the bridge.
|
||||
const licenseTierFreeValue = pkglicensing.TierFree
|
||||
|
||||
// overflowBonusFromLicensing returns the number of bonus host slots granted by the onboarding overflow.
|
||||
func overflowBonusFromLicensing(tier licenseTier, overflowGrantedAt *int64, now time.Time) int {
|
||||
return pkglicensing.OverflowBonus(tier, overflowGrantedAt, now)
|
||||
}
|
||||
|
||||
// overflowDaysRemainingFromLicensing returns the number of days remaining in the overflow window.
|
||||
func overflowDaysRemainingFromLicensing(tier licenseTier, overflowGrantedAt *int64, now time.Time) int {
|
||||
return pkglicensing.OverflowDaysRemaining(tier, overflowGrantedAt, now)
|
||||
|
||||
@@ -670,17 +670,6 @@ func (h *LicenseHandlers) syncReleaseDemoFixtureRuntime(orgID string, service *l
|
||||
}
|
||||
}
|
||||
|
||||
func (h *LicenseHandlers) canonicalMonitoredSystemGrandfatherFloor(ctx context.Context) (int, bool) {
|
||||
usage := h.entitlementUsageSnapshot(ctx)
|
||||
if !usage.MonitoredSystemsAvailable {
|
||||
return 0, false
|
||||
}
|
||||
if usage.MonitoredSystems < 0 {
|
||||
return 0, false
|
||||
}
|
||||
return int(usage.MonitoredSystems), true
|
||||
}
|
||||
|
||||
func (h *LicenseHandlers) ensureEvaluatorForOrg(orgID string, service *licenseService) error {
|
||||
if h == nil || service == nil || h.mtPersistence == nil {
|
||||
return nil
|
||||
|
||||
@@ -172,33 +172,6 @@ func RequireMultiTenant(next http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
// RequireMultiTenantHandler returns middleware for http.Handler.
|
||||
func RequireMultiTenantHandler(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
orgID := GetOrgID(r.Context())
|
||||
|
||||
// Default org is always allowed (backward compatibility)
|
||||
if orgID == "" || orgID == "default" {
|
||||
next.ServeHTTP(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
// Feature flag check - multi-tenant must be explicitly enabled
|
||||
if !multiTenantEnabled {
|
||||
writeMultiTenantDisabledError(w)
|
||||
return
|
||||
}
|
||||
|
||||
// Non-default orgs require multi-tenant license
|
||||
if !hasMultiTenantFeatureForContext(r.Context()) {
|
||||
writeMultiTenantRequiredError(w)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
// writeMultiTenantRequiredError writes a 402 Payment Required response
|
||||
// indicating that multi-tenant requires an Enterprise license.
|
||||
func writeMultiTenantRequiredError(w http.ResponseWriter) {
|
||||
|
||||
@@ -87,11 +87,6 @@ func NewTenantMiddlewareWithConfig(cfg TenantMiddlewareConfig) *TenantMiddleware
|
||||
}
|
||||
}
|
||||
|
||||
// SetAuthChecker sets the authorization checker for the middleware.
|
||||
func (m *TenantMiddleware) SetAuthChecker(checker AuthorizationChecker) {
|
||||
m.authChecker = checker
|
||||
}
|
||||
|
||||
func (m *TenantMiddleware) Middleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// 1. Extract Org ID
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -10,7 +9,6 @@ import (
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
pkglicensing "github.com/rcourtman/pulse-go-rewrite/pkg/licensing"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
@@ -22,14 +20,6 @@ type mockLicenseProvider struct {
|
||||
hasFeatures bool
|
||||
}
|
||||
|
||||
func (p *mockLicenseProvider) Service(ctx context.Context) *pkglicensing.Service {
|
||||
// In a real scenario, we'd return a mocked service control structure.
|
||||
// Since license.Service is concrete, we rely on its default state (no features)
|
||||
// or we'd need a way to inject state.
|
||||
// For now, testing the negative case (no license) is most important for security.
|
||||
return pkglicensing.NewService()
|
||||
}
|
||||
|
||||
func TestTenantMiddleware_Enforcement_Permanent(t *testing.T) {
|
||||
// Cleanup env after test
|
||||
defer func() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user