fix: 修正 HWPOD 控制台状态与有界布局
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -351,6 +351,24 @@ test("R2 visual constraints keep industrial controls bounded and component-owned
|
||||
assert.match(read(relativePath), /ViewModeSwitch/u, `${relativePath} should use the shared view switch`);
|
||||
});
|
||||
|
||||
test("HWPOD topology keeps connection state separate and owns bounded scrolling", () => {
|
||||
const routes = read("src/router/index.ts");
|
||||
for (const path of ["/hwpods/devices/:hwpodId?", "/hwpods/nodes/:nodeId?", "/hwpods/onboarding/:nodeId?"]) {
|
||||
const route = routes.split(`{ path: "${path}"`)[1]?.split("},")[0] ?? "";
|
||||
assert.match(route, /boundedLayout: true/u, `${path} should use bounded AppShell layout`);
|
||||
}
|
||||
|
||||
const view = read("src/views/admin/HwpodGroupsView.vue");
|
||||
assert.match(view, /\.hwpod-console \{[^}]*height: 100%;[^}]*overflow: hidden;/u);
|
||||
assert.match(view, /\.hwpod-workspace \{[^}]*flex: 1 1 auto;/u);
|
||||
assert.doesNotMatch(view, /\.hwpod-workspace \{[^}]*height: clamp\(/u);
|
||||
|
||||
const deviceWorkspace = read("src/components/hwpod/HwpodDevicesWorkspace.vue");
|
||||
assert.match(deviceWorkspace, /capabilityCoverage\.matched/u);
|
||||
assert.match(deviceWorkspace, /capabilityCoverage\.declared/u);
|
||||
assert.doesNotMatch(deviceWorkspace, /actualCapabilities\.length\s*\}\}\/\{\{\s*row\.declaredCapabilities\.length/u);
|
||||
});
|
||||
|
||||
test("R6 Sub2API source comparison is recorded in parent/child issue scope", () => {
|
||||
const issueBody = readIssueBodyFixture();
|
||||
assert.match(issueBody, /Wei-Shaw\/sub2api@v0\.1\.136=a2f76e4/u);
|
||||
|
||||
@@ -14,12 +14,8 @@ const props = withDefaults(
|
||||
const emit = defineEmits<{ select: [id: string] }>();
|
||||
|
||||
const statusLabel = computed(() => ({
|
||||
available: "可用",
|
||||
busy: "执行中",
|
||||
online: "在线",
|
||||
offline: "节点离线",
|
||||
"capability-mismatch": "能力不匹配",
|
||||
"workspace-blocked": "工作区阻塞",
|
||||
unprobed: "待探测"
|
||||
}[props.device.status] ?? props.device.status));
|
||||
|
||||
function linkState(kind: "target" | "workspace" | "debug" | "io"): string {
|
||||
@@ -80,7 +76,7 @@ function linkState(kind: "target" | "workspace" | "debug" | "io"): string {
|
||||
|
||||
<p v-if="device.blockers[0]" class="pcb-blocker"><strong>{{ device.blockers[0].code }}</strong>{{ device.blockers[0].summary }}</p>
|
||||
<footer>
|
||||
<span>{{ device.declaredCapabilities.length }} 项声明能力</span>
|
||||
<span>能力覆盖 {{ device.capabilityCoverage.matched }}/{{ device.capabilityCoverage.declared }} · 上报 {{ device.capabilityCoverage.actual }}</span>
|
||||
<span v-if="device.operation">{{ device.operation.status }} · {{ device.operation.opCount }} ops</span>
|
||||
<button
|
||||
type="button"
|
||||
@@ -95,17 +91,13 @@ function linkState(kind: "target" | "workspace" | "debug" | "io"): string {
|
||||
.pcb-card { position: relative; min-width: 0; border: 1px solid #cbd2cf; border-top: 3px solid #2e6b67; border-radius: 8px; background: #fbfaf6; box-shadow: 0 12px 28px rgb(20 34 32 / 8%); overflow: hidden; transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease; }
|
||||
.pcb-card[data-selected="true"] { border-color: #1f7469; border-top-color: #145f57; box-shadow: 0 0 0 3px rgb(31 116 105 / 18%), 0 16px 34px rgb(20 55 48 / 14%); transform: translateY(-1px); }
|
||||
.pcb-card[data-selected="true"] footer button { border-color: #145f57; background: #dff2ed; color: #145f57; }
|
||||
.pcb-card[data-status="busy"] { border-top-color: #b86d1d; }
|
||||
.pcb-card[data-status="offline"], .pcb-card[data-status="workspace-blocked"] { border-top-color: #8d453b; }
|
||||
.pcb-card[data-status="capability-mismatch"] { border-top-color: #8a6b16; }
|
||||
.pcb-card[data-status="offline"] { border-top-color: #8d453b; }
|
||||
.pcb-card__header { display: flex; justify-content: space-between; gap: 16px; align-items: flex-start; padding: 18px 20px 12px; }
|
||||
.pcb-card__header h2 { margin: 3px 0 0; color: #152421; font-size: 1.08rem; letter-spacing: 0; overflow-wrap: anywhere; }
|
||||
.pcb-card__eyebrow { color: #687572; font: 600 .72rem/1.2 ui-monospace, SFMono-Regular, Consolas, monospace; text-transform: uppercase; }
|
||||
.pcb-status { display: inline-flex; align-items: center; gap: 7px; flex: 0 0 auto; padding: 5px 9px; border: 1px solid #b7c6c1; border-radius: 999px; color: #244943; background: #eef7f2; font-size: .76rem; font-weight: 700; }
|
||||
.pcb-status i { width: 7px; height: 7px; border-radius: 50%; background: currentcolor; }
|
||||
.pcb-status[data-status="busy"] { color: #8b4c0a; background: #fff4df; border-color: #e6bd80; }
|
||||
.pcb-status[data-status="offline"], .pcb-status[data-status="workspace-blocked"] { color: #7b332c; background: #fff0ed; border-color: #ddb2ac; }
|
||||
.pcb-status[data-status="capability-mismatch"] { color: #6f560b; background: #fff8d8; border-color: #d8c376; }
|
||||
.pcb-status[data-status="offline"] { color: #7b332c; background: #fff0ed; border-color: #ddb2ac; }
|
||||
.pcb-map { display: block; width: calc(100% - 28px); max-height: 220px; margin: 0 14px; }
|
||||
.pcb-board { fill: #142c28; stroke: #31514a; stroke-width: 2; }
|
||||
.pcb-grid-line { fill: none; stroke: rgb(137 184 168 / 9%); stroke-width: 1; }
|
||||
|
||||
@@ -11,7 +11,7 @@ import HwpodDeviceCard from "./HwpodDeviceCard.vue";
|
||||
defineProps<{ devices: HwpodDeviceTopologyItem[]; selectedId: string | null; view: "cards" | "list"; density: "compact" | "comfortable"; sort: string }>();
|
||||
const emit = defineEmits<{ select: [device: HwpodDeviceTopologyItem]; sort: [key: string] }>();
|
||||
const columns: DataGridColumn[] = [{ key: "name", label: "HWPOD", sortable: true, width: "190px" }, { key: "nodeName", label: "Node", width: "170px" }, { key: "status", label: "状态", sortable: true, width: "100px" }, { key: "elements", label: "目标 / 工作区", width: "230px" }, { key: "debugIo", label: "调试 / IO", width: "210px" }, { key: "capabilities", label: "能力", width: "140px" }, { key: "blocker", label: "主要 blocker", width: "220px" }, { key: "operation", label: "用户操作", width: "160px" }];
|
||||
const statusLabel = (status: string): string => ({ online: "在线", offline: "离线", busy: "忙碌", available: "可用", error: "异常", mismatch: "能力不匹配", "capability-mismatch": "能力不匹配" }[status] || status || "unknown");
|
||||
const statusLabel = (status: string): string => ({ online: "在线", offline: "离线" }[status] || status || "unknown");
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -49,7 +49,7 @@ const statusLabel = (status: string): string => ({ online: "在线", offline: "
|
||||
<template #cell-status="{ row }"><span class="status-chip" :data-status="row.status">{{ statusLabel(row.status) }}</span></template>
|
||||
<template #cell-elements="{ row }"><span class="grid-stack"><strong>{{ row.elements.target.label }}</strong><small>{{ row.elements.workspace.label }}</small></span></template>
|
||||
<template #cell-debugIo="{ row }"><span class="grid-stack"><strong>{{ row.elements.debugProbe.label }}</strong><small>{{ row.elements.ioProbe.label }}</small></span></template>
|
||||
<template #cell-capabilities="{ row }"><span class="grid-stack"><strong>{{ row.actualCapabilities.length }}/{{ row.declaredCapabilities.length }}</strong><small v-if="row.missingCapabilities.length">缺 {{ row.missingCapabilities.join(', ') }}</small></span></template>
|
||||
<template #cell-capabilities="{ row }"><span class="grid-stack"><strong>覆盖 {{ row.capabilityCoverage.matched }}/{{ row.capabilityCoverage.declared }}</strong><small>上报 {{ row.capabilityCoverage.actual }} · 缺 {{ row.capabilityCoverage.missing }} · 额外 {{ row.capabilityCoverage.extra }}</small></span></template>
|
||||
<template #cell-blocker="{ row }"><span class="grid-stack"><strong>{{ row.blockers[0]?.code || '无' }}</strong><small>{{ row.blockers[0]?.summary || '当前可操作' }}</small></span></template>
|
||||
<template #cell-operation="{ row }"><span class="grid-stack"><strong>{{ row.operation?.status || '无' }}</strong><small>{{ row.operation ? `${row.operation.opCount} ops` : '尚无用户操作' }}</small></span></template>
|
||||
</DataGrid>
|
||||
|
||||
@@ -11,7 +11,7 @@ import HwpodReadinessRail from "./HwpodReadinessRail.vue";
|
||||
defineProps<{ nodes: HwpodNodeTopologyItem[]; selectedId: string | null; view: "cards" | "list"; density: "compact" | "comfortable"; sort: string }>();
|
||||
const emit = defineEmits<{ select: [node: HwpodNodeTopologyItem]; sort: [key: string] }>();
|
||||
const columns: DataGridColumn[] = [{ key: "name", label: "Node", sortable: true, width: "210px" }, { key: "status", label: "连接", sortable: true, width: "100px" }, { key: "deviceCount", label: "HWPOD", align: "right", width: "80px" }, { key: "inFlight", label: "In-flight", width: "100px" }, { key: "capabilities", label: "能力", width: "130px" }, { key: "mounted", label: "挂载 HWPOD", width: "220px" }, { key: "readiness", label: "接入阶段", width: "180px" }, { key: "lastSeen", label: "最后心跳", width: "150px" }, { key: "blocker", label: "Blocker", width: "220px" }];
|
||||
const statusLabel = (status: string): string => ({ online: "在线", offline: "离线", busy: "忙碌", available: "可用", error: "异常", mismatch: "能力不匹配", "capability-mismatch": "能力不匹配" }[status] || status || "unknown");
|
||||
const statusLabel = (status: string): string => ({ online: "在线", offline: "离线" }[status] || status || "unknown");
|
||||
function formatTime(value: string | null): string { if (!value) return "-"; const date = new Date(value); return Number.isNaN(date.getTime()) ? value : date.toLocaleString(); }
|
||||
</script>
|
||||
|
||||
@@ -46,7 +46,7 @@ function formatTime(value: string | null): string { if (!value) return "-"; cons
|
||||
<section class="node-gauges">
|
||||
<div><small>HWPOD</small><strong>{{ node.deviceCount }}</strong></div>
|
||||
<div><small>in-flight</small><strong>{{ node.inFlight.count }}/{{ node.inFlight.max }}</strong></div>
|
||||
<div><small>capabilities</small><strong>{{ node.actualCapabilities.length }}/{{ node.declaredCapabilities.length }}</strong></div>
|
||||
<div><small>capability coverage</small><strong>{{ node.capabilityCoverage.matched }}/{{ node.capabilityCoverage.declared }}</strong></div>
|
||||
</section>
|
||||
<section class="mounted-hwpods" aria-label="挂载 HWPOD">
|
||||
<header><strong>挂载 HWPOD</strong><span>{{ node.hwpods.length }}</span></header>
|
||||
@@ -85,7 +85,7 @@ function formatTime(value: string | null): string { if (!value) return "-"; cons
|
||||
<template #cell-status="{ row }"><span class="status-chip" :data-status="row.status">{{ statusLabel(row.status) }}</span></template>
|
||||
<template #cell-deviceCount="{ row }"><strong>{{ row.deviceCount }}</strong></template>
|
||||
<template #cell-inFlight="{ row }"><strong>{{ row.inFlight.count }}/{{ row.inFlight.max }}</strong></template>
|
||||
<template #cell-capabilities="{ row }"><span class="grid-stack"><strong>{{ row.actualCapabilities.length }}/{{ row.declaredCapabilities.length }}</strong><small v-if="row.missingCapabilities.length">缺 {{ row.missingCapabilities.join(', ') }}</small></span></template>
|
||||
<template #cell-capabilities="{ row }"><span class="grid-stack"><strong>覆盖 {{ row.capabilityCoverage.matched }}/{{ row.capabilityCoverage.declared }}</strong><small>上报 {{ row.capabilityCoverage.actual }} · 缺 {{ row.capabilityCoverage.missing }} · 额外 {{ row.capabilityCoverage.extra }}</small></span></template>
|
||||
<template #cell-mounted="{ row }"><span class="grid-stack"><strong>{{ row.hwpods.map((item) => item.name).join(', ') || '无' }}</strong><small>{{ row.hwpods.map((item) => item.hwpodId).join(', ') || '未绑定 spec' }}</small></span></template>
|
||||
<template #cell-readiness="{ row }"><span class="grid-stack"><strong>{{ row.readiness.filter((stage: HwpodNodeReadinessStage) => stage.status === 'ready').length }}/{{ row.readiness.length }} ready</strong><small>{{ row.readiness.find((stage: HwpodNodeReadinessStage) => stage.status !== 'ready')?.label || '全部阶段 ready' }}</small></span></template>
|
||||
<template #cell-lastSeen="{ row }">{{ formatTime(row.lastSeenAt) }}</template>
|
||||
|
||||
@@ -49,9 +49,9 @@ const routes: RouteRecordRaw[] = [
|
||||
{ path: "/admin/billing", name: "AdminBilling", component: () => import("@/views/admin/BillingView.vue"), meta: { requiresAuth: true, requiresAdmin: true, navId: "admin.billing", title: "兑换与订阅", section: "admin" } },
|
||||
{ path: "/admin/hwpod-groups", redirect: "/hwpods/devices" },
|
||||
{ path: "/hwpods", redirect: "/hwpods/devices" },
|
||||
{ path: "/hwpods/devices/:hwpodId?", name: "HwpodDevices", component: () => import("@/views/admin/HwpodGroupsView.vue"), meta: { requiresAuth: true, requiresAdmin: true, navId: "admin.hwpodGroups", navParent: "HwpodGroups", title: "HWPOD 设备资源", section: "admin" } },
|
||||
{ path: "/hwpods/nodes/:nodeId?", name: "HwpodNodes", component: () => import("@/views/admin/HwpodGroupsView.vue"), meta: { requiresAuth: true, requiresAdmin: true, navId: "admin.hwpodGroups", navParent: "HwpodGroups", title: "HWPOD Node", section: "admin" } },
|
||||
{ path: "/hwpods/onboarding/:nodeId?", name: "HwpodOnboarding", component: () => import("@/views/admin/HwpodGroupsView.vue"), meta: { requiresAuth: true, requiresAdmin: true, navId: "admin.hwpodGroups", navParent: "HwpodGroups", title: "HWPOD 节点接入", section: "admin" } },
|
||||
{ path: "/hwpods/devices/:hwpodId?", name: "HwpodDevices", component: () => import("@/views/admin/HwpodGroupsView.vue"), meta: { requiresAuth: true, requiresAdmin: true, navId: "admin.hwpodGroups", navParent: "HwpodGroups", title: "HWPOD 设备资源", section: "admin", boundedLayout: true } },
|
||||
{ path: "/hwpods/nodes/:nodeId?", name: "HwpodNodes", component: () => import("@/views/admin/HwpodGroupsView.vue"), meta: { requiresAuth: true, requiresAdmin: true, navId: "admin.hwpodGroups", navParent: "HwpodGroups", title: "HWPOD Node", section: "admin", boundedLayout: true } },
|
||||
{ path: "/hwpods/onboarding/:nodeId?", name: "HwpodOnboarding", component: () => import("@/views/admin/HwpodGroupsView.vue"), meta: { requiresAuth: true, requiresAdmin: true, navId: "admin.hwpodGroups", navParent: "HwpodGroups", title: "HWPOD 节点接入", section: "admin", boundedLayout: true } },
|
||||
{ path: "/admin/provider-profiles", name: "ProviderProfiles", component: () => import("@/views/admin/ProviderProfilesView.vue"), meta: { requiresAuth: true, requiresAdmin: true, navId: "admin.providerProfiles", title: "Provider Profiles", section: "admin" } },
|
||||
{ path: "/admin/provider-profiles/:profile", name: "ProviderProfileDetail", component: () => import("@/views/admin/ProviderProfilesView.vue"), meta: { requiresAuth: true, requiresAdmin: true, navId: "admin.providerProfiles", navParent: "ProviderProfiles", title: "Provider Profile 详情", section: "admin" } },
|
||||
{ path: "/skills", name: "Skills", component: () => import("@/views/SkillsView.vue"), meta: { requiresAuth: true, navId: "system.skills", title: "技能包", section: "system" } },
|
||||
|
||||
@@ -389,9 +389,17 @@ export type AgentChatResultResponse = AgentChatResponse & {
|
||||
export interface HwpodSpecsResponse { specs?: Array<Record<string, unknown>>; [key: string]: unknown }
|
||||
export interface HwpodNodeOpsResponse { ok?: boolean; status?: string; events?: TraceEvent[]; groups?: Array<Record<string, unknown>>; [key: string]: unknown }
|
||||
|
||||
export type HwpodResourceStatus = "available" | "busy" | "offline" | "workspace-blocked" | "capability-mismatch" | "unprobed" | "online" | "unconfigured" | string;
|
||||
export type HwpodConnectionStatus = "online" | "offline";
|
||||
export type HwpodReadinessStatus = "ready" | "blocked" | "unknown" | string;
|
||||
|
||||
export interface HwpodCapabilityCoverage {
|
||||
matched: number;
|
||||
declared: number;
|
||||
actual: number;
|
||||
missing: number;
|
||||
extra: number;
|
||||
}
|
||||
|
||||
export interface HwpodBlocker {
|
||||
code: string;
|
||||
summary: string;
|
||||
@@ -431,7 +439,7 @@ export interface HwpodDeviceTopologyItem {
|
||||
name: string;
|
||||
nodeId: string;
|
||||
nodeName: string;
|
||||
status: HwpodResourceStatus;
|
||||
status: HwpodConnectionStatus;
|
||||
online: boolean;
|
||||
available: boolean;
|
||||
busy: boolean;
|
||||
@@ -439,6 +447,7 @@ export interface HwpodDeviceTopologyItem {
|
||||
declaredCapabilities: string[];
|
||||
actualCapabilities: string[];
|
||||
missingCapabilities: string[];
|
||||
capabilityCoverage: HwpodCapabilityCoverage;
|
||||
elements: HwpodElements;
|
||||
availability: { ok: boolean | null; status: string; checkedAt: string | null; blocker: HwpodBlocker | null };
|
||||
operation: HwpodOperationSummary | null;
|
||||
@@ -461,7 +470,7 @@ export interface HwpodNodeTopologyItem {
|
||||
resourceType: "hwpod-node";
|
||||
nodeId: string;
|
||||
name: string;
|
||||
status: HwpodResourceStatus;
|
||||
status: HwpodConnectionStatus;
|
||||
online: boolean;
|
||||
platform: string;
|
||||
version: string | null;
|
||||
@@ -470,10 +479,11 @@ export interface HwpodNodeTopologyItem {
|
||||
lastSeenAt: string | null;
|
||||
ageMs: number | null;
|
||||
deviceCount: number;
|
||||
hwpods: Array<{ hwpodId: string; name: string; status: HwpodResourceStatus; busy: boolean; capabilityMismatch: boolean }>;
|
||||
hwpods: Array<{ hwpodId: string; name: string; status: HwpodConnectionStatus; busy: boolean; capabilityMismatch: boolean }>;
|
||||
declaredCapabilities: string[];
|
||||
actualCapabilities: string[];
|
||||
missingCapabilities: string[];
|
||||
capabilityCoverage: HwpodCapabilityCoverage;
|
||||
capabilityMismatch: boolean;
|
||||
inFlight: { count: number; max: number; busy: boolean };
|
||||
operation: HwpodOperationSummary | null;
|
||||
|
||||
@@ -70,18 +70,11 @@ const statusItems = computed(() => [
|
||||
const statusFilterOptions = computed(() => tab.value === "nodes"
|
||||
? [
|
||||
{ value: "online", label: "在线" },
|
||||
{ value: "busy", label: "执行中" },
|
||||
{ value: "offline", label: "离线" },
|
||||
{ value: "capability-mismatch", label: "能力不匹配" },
|
||||
{ value: "unconfigured", label: "无 Spec" }
|
||||
{ value: "offline", label: "离线" }
|
||||
]
|
||||
: [
|
||||
{ value: "available", label: "可用" },
|
||||
{ value: "busy", label: "执行中" },
|
||||
{ value: "offline", label: "离线" },
|
||||
{ value: "capability-mismatch", label: "能力不匹配" },
|
||||
{ value: "workspace-blocked", label: "工作区阻塞" },
|
||||
{ value: "unprobed", label: "待探测" }
|
||||
{ value: "online", label: "在线" },
|
||||
{ value: "offline", label: "离线" }
|
||||
]);
|
||||
const collectionPage = computed(() => tab.value === "devices" ? hwpod.deviceTopology?.page : tab.value === "nodes" ? hwpod.nodeTopology?.page : null);
|
||||
|
||||
@@ -195,7 +188,7 @@ async function copySha(): Promise<void> {
|
||||
}
|
||||
|
||||
function statusLabel(status: string): string {
|
||||
return ({ available: "可用", busy: "执行中", offline: "离线", "capability-mismatch": "能力不匹配", "workspace-blocked": "工作区阻塞", unprobed: "待探测", online: "在线", unconfigured: "无 Spec" }[status] ?? status);
|
||||
return ({ online: "在线", offline: "离线" }[status] ?? status);
|
||||
}
|
||||
|
||||
function formatTime(value: string | null): string {
|
||||
@@ -310,20 +303,24 @@ function text(value: unknown): string {
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.hwpod-console { display: grid; min-width: 0; min-height: 100%; align-content: start; gap: 12px; padding: 8px; color: var(--console-graphite-900); }
|
||||
.hwpod-console { display: grid; height: 100%; min-width: 0; min-height: 0; grid-template-rows: auto auto minmax(0, 1fr); align-content: stretch; gap: 12px; padding: 8px; color: var(--console-graphite-900); overflow: hidden; }
|
||||
.hwpod-console :deep(.console-async-boundary) { display: flex; min-height: 0; flex-direction: column; overflow: hidden; }
|
||||
.hwpod-console :deep(.page-command-filters) { flex-wrap: nowrap; align-items: center; gap: 6px; padding-bottom: 1px; overflow-x: auto; overscroll-behavior-inline: contain; }
|
||||
.console-tabs { display: flex; gap: 2px; width: fit-content; max-width: 100%; padding: 3px; border: 1px solid var(--console-border-strong); border-radius: var(--console-radius-sm); background: var(--console-border); overflow-x: auto; }
|
||||
.console-tabs button { min-height: 32px; border: 0; border-radius: 5px; background: var(--console-surface-raised); color: var(--console-graphite-600); padding: 0 12px; font-size: 12px; font-weight: 750; white-space: nowrap; cursor: pointer; }
|
||||
.console-tabs button[data-active="true"] { color: #fff; background: var(--console-graphite-900); }
|
||||
.console-search { display: flex; min-width: min(420px, 100%); align-items: center; gap: 7px; }
|
||||
.console-search { display: flex; width: min(340px, 100%); min-width: min(340px, 100%); align-items: center; gap: 6px; }
|
||||
.console-search label, .status-filter { color: var(--console-graphite-600); font-family: var(--console-font-mono); font-size: 10px; font-weight: 760; letter-spacing: 0; text-transform: uppercase; }
|
||||
.console-search input { min-width: 0; flex: 1; border: 1px solid var(--console-border-strong); border-radius: var(--console-radius-sm); background: var(--console-surface-raised); padding: 8px 10px; }
|
||||
.console-search button { border: 1px solid var(--console-cyan-700); border-radius: var(--console-radius-sm); background: var(--console-cyan-100); color: var(--console-cyan-700); padding: 7px 10px; font-weight: 750; cursor: pointer; }
|
||||
.console-search input { height: 32px; min-width: 0; flex: 1; border: 1px solid var(--console-border-strong); border-radius: var(--console-radius-sm); background: var(--console-surface-raised); padding: 0 9px; font-size: 11px; }
|
||||
.console-search button { height: 32px; border: 1px solid var(--console-cyan-700); border-radius: var(--console-radius-sm); background: var(--console-cyan-100); color: var(--console-cyan-700); padding: 0 9px; font-size: 11px; font-weight: 750; cursor: pointer; }
|
||||
.status-filter { display: flex; align-items: center; gap: 7px; }
|
||||
.status-filter select { border: 1px solid var(--console-border-strong); border-radius: var(--console-radius-sm); background: var(--console-surface-raised); padding: 7px 9px; color: var(--console-graphite-800); }
|
||||
.probe-button { border: 1px solid var(--console-amber-500); border-radius: var(--console-radius-sm); background: var(--console-amber-100); color: var(--console-amber-700); padding: 7px 10px; font-size: 11px; font-weight: 750; cursor: pointer; }
|
||||
.status-filter select { height: 32px; border: 1px solid var(--console-border-strong); border-radius: var(--console-radius-sm); background: var(--console-surface-raised); padding: 0 8px; color: var(--console-graphite-800); font-size: 11px; }
|
||||
.probe-button { height: 32px; border: 1px solid var(--console-amber-500); border-radius: var(--console-radius-sm); background: var(--console-amber-100); color: var(--console-amber-700); padding: 0 9px; font-size: 11px; font-weight: 750; cursor: pointer; white-space: nowrap; }
|
||||
.hwpod-console :deep(.view-mode-switch) { height: 32px; flex: 0 0 auto; }
|
||||
.hwpod-console :deep(.view-mode-switch button) { height: 28px; min-height: 28px; padding-block: 0; font-size: 11px; }
|
||||
.spec-warning { display: flex; gap: 8px 20px; align-items: center; margin-bottom: 10px; padding: 10px 13px; border: 1px solid #dfc47d; border-left: 4px solid #aa7a18; border-radius: 7px; background: #fff8df; color: #6b5317; font-size: .78rem; }
|
||||
.stale-warning { display: flex; gap: 8px 20px; align-items: center; margin-bottom: 10px; padding: 10px 13px; border: 1px solid #e4a8a2; border-left: 4px solid var(--console-red-700); border-radius: var(--console-radius-sm); background: var(--console-red-100); color: var(--console-red-700); font-size: .78rem; }
|
||||
.hwpod-workspace { height: clamp(420px, calc(100dvh - 300px), 760px); }
|
||||
.hwpod-workspace { min-height: 0; flex: 1 1 auto; }
|
||||
.hwpod-workspace :deep(.bounded-workspace-main) { padding: 12px; }
|
||||
.hwpod-workspace :deep(.resource-stage) { display: grid; height: 100%; min-width: 0; min-height: 0; grid-template-rows: minmax(0, 1fr); }
|
||||
.hwpod-workspace :deep(.grid-stack) { display: grid; min-width: 0; gap: 3px; }
|
||||
@@ -346,6 +343,6 @@ dd { margin: 3px 0 0; overflow-wrap: anywhere; }
|
||||
.collection-pagination div { display: flex; gap: 6px; }
|
||||
.collection-pagination button { border: 1px solid var(--console-border-strong); border-radius: var(--console-radius-sm); background: var(--console-surface-raised); color: var(--console-graphite-800); padding: 6px 10px; font-weight: 700; cursor: pointer; }
|
||||
.collection-pagination button:disabled { opacity: .45; cursor: not-allowed; }
|
||||
@media (max-width: 680px) { .hwpod-console { padding: 2px; } .console-tabs { width: 100%; } .console-tabs button { flex: 1 0 auto; } .console-search { min-width: 100%; } .status-filter { width: 100%; justify-content: space-between; } .spec-warning, .stale-warning { align-items: flex-start; flex-direction: column; } .hwpod-workspace { height: clamp(520px, calc(100dvh - 260px), 680px); } .hwpod-workspace :deep(.bounded-workspace-main) { padding: 8px; } }
|
||||
@media (max-width: 680px) { .hwpod-console { padding: 2px; } .console-tabs { width: 100%; } .console-tabs button { flex: 1 0 auto; } .console-search { width: 100%; min-width: 100%; } .status-filter { flex: 0 0 auto; } .spec-warning, .stale-warning { align-items: flex-start; flex-direction: column; } .hwpod-workspace :deep(.bounded-workspace-main) { padding: 8px; } }
|
||||
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; animation: none !important; } }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user