diff --git a/web/hwlab-cloud-web/scripts/r6-architecture-parity.test.ts b/web/hwlab-cloud-web/scripts/r6-architecture-parity.test.ts
index 2d69b01b..42b87482 100644
--- a/web/hwlab-cloud-web/scripts/r6-architecture-parity.test.ts
+++ b/web/hwlab-cloud-web/scripts/r6-architecture-parity.test.ts
@@ -91,9 +91,9 @@ test("R6 common module map keeps HWLAB views on shared console primitives", () =
[
"AsyncBoundary",
"OperationsWorkspace",
- "EntityIndex",
- "InspectorPanel",
- "ViewModeSwitch",
+ "AccessUserIndex",
+ "AccessPermissionWorkspace",
+ "AccessUserInspector",
],
],
[
@@ -101,9 +101,8 @@ test("R6 common module map keeps HWLAB views on shared console primitives", () =
[
"AsyncBoundary",
"OperationsWorkspace",
- "EntityIndex",
- "FactPath",
- "ViewModeSwitch",
+ "SecretInventoryIndex",
+ "SecretInventoryWorkspace",
],
],
[
@@ -111,8 +110,9 @@ test("R6 common module map keeps HWLAB views on shared console primitives", () =
[
"AsyncBoundary",
"OperationsWorkspace",
- "EntityIndex",
- "InspectorPanel",
+ "UserDirectoryIndex",
+ "UserDirectoryCollection",
+ "UserAccountInspector",
"ViewModeSwitch",
],
],
@@ -121,8 +121,8 @@ test("R6 common module map keeps HWLAB views on shared console primitives", () =
[
"AsyncBoundary",
"OperationsWorkspace",
- "EntityIndex",
- "InspectorPanel",
+ "BillingCodeIndex",
+ "BillingCodeInspector",
"ViewModeSwitch",
],
],
@@ -228,16 +228,16 @@ test("R2 console rewrite owns the final visual layer and removes private letter
for (const icon of ["menu", "info", "logout", "empty", "project", "key", "provider", "skills"])
assert.match(iconSource, new RegExp(`\\b${icon}:`, "u"), `ConsoleIcon should define ${icon}`);
- const migratedViews = [
- "src/components/common/EmptyState.vue",
- "src/views/SkillsView.vue",
- "src/views/projects/ProjectsView.vue",
- "src/views/admin/ProviderProfilesView.vue",
- "src/views/user/ApiKeysView.vue",
+ const migratedViews: Array<[string, string]> = [
+ ["src/components/common/EmptyState.vue", "ConsoleIcon"],
+ ["src/views/SkillsView.vue", "EntityCard"],
+ ["src/views/projects/ProjectsView.vue", "ProjectResourceCard"],
+ ["src/views/admin/ProviderProfilesView.vue", "EntityCard"],
+ ["src/views/user/ApiKeysView.vue", "ApiKeyResourceCard"],
];
- for (const relativePath of migratedViews) {
+ for (const [relativePath, sharedComponent] of migratedViews) {
const source = read(relativePath);
- assert.match(source, /ConsoleIcon/u, `${relativePath} should use the shared icon system`);
+ assert.match(source, new RegExp(sharedComponent, "u"), `${relativePath} should use ${sharedComponent}`);
assert.doesNotMatch(source, />\s*(?:SK|PJ|AI|KEY|HW)\s* {
+ const moduleExpectations: Array<[string, string[]]> = [
+ ["src/components/access/AccessUserIndex.vue", ["EntityIndex", "AccessUserRow"]],
+ ["src/components/access/AccessPermissionWorkspace.vue", ["permission-matrix", "FactPath", "ViewModeSwitch"]],
+ ["src/components/access/AccessUserInspector.vue", ["InspectorPanel", "Tool grants", "Tuples"]],
+ ["src/components/secrets/SecretInventoryIndex.vue", ["EntityIndex", "StatusBadge"]],
+ ["src/components/secrets/SecretInventoryWorkspace.vue", ["FactPath", "SectionFrame", "安全边界"]],
+ ["src/components/users/UserDirectoryIndex.vue", ["EntityIndex", "Pagination"]],
+ ["src/components/users/UserDirectoryCollection.vue", ["ConsoleIcon", "DataGrid", "用户实体网格"]],
+ ["src/components/users/UserAccountInspector.vue", ["InspectorPanel", "entitlements", "reservations", "ledger"]],
+ ["src/components/billing/BillingCodeIndex.vue", ["EntityIndex", "RedeemCodeSummary"]],
+ ["src/components/billing/BillingCodeInspector.vue", ["InspectorPanel", "Credits", "Usage"]],
+ ["src/components/hwpod/HwpodDevicesWorkspace.vue", ["HwpodDeviceCard", "DataGrid", "ResourceCollection"]],
+ ["src/components/hwpod/HwpodNodesWorkspace.vue", ["挂载 HWPOD", "HwpodReadinessRail", "DataGrid"]],
+ ["src/components/hwpod/HwpodOnboardingWorkspace.vue", ["Python single file", "SHA-256", "Staged readiness"]],
+ ["src/components/common/OverlaySurface.vue", ["useOverlaySurface", "ConsoleIcon", "Teleport"]],
+ ["src/components/mdtodo/MdtodoTaskPanel.vue", ["ContentViewer", "editingBody", "Markdown"]],
+ ["src/components/mdtodo/MdtodoReportPanel.vue", ["ContentViewer", "openFullscreen", "closeFullscreen"]],
+ ];
+ for (const [relativePath, terms] of moduleExpectations) {
+ const source = read(relativePath);
+ for (const term of terms) assert.match(source, new RegExp(term, "u"), `${relativePath} should contain ${term}`);
+ }
+
+ const cardMigrations: Array<[string, string]> = [
+ ["src/views/SkillsView.vue", "EntityCard"],
+ ["src/views/admin/ProviderProfilesView.vue", "EntityCard"],
+ ["src/views/projects/ProjectsView.vue", "ProjectResourceCard"],
+ ["src/views/user/ApiKeysView.vue", "ApiKeyResourceCard"],
+ ];
+ for (const [relativePath, component] of cardMigrations) assert.match(read(relativePath), new RegExp(component, "u"));
+
+ assert.doesNotMatch(read("src/views/SkillsView.vue"), /\.skill-card\b|\.skill-glyph\b/u);
+ assert.doesNotMatch(read("src/views/admin/ProviderProfilesView.vue"), /\.provider-card\b|\.provider-glyph\b/u);
+ assert.doesNotMatch(read("src/views/projects/ProjectsView.vue"), /\.project-card\b|\.project-glyph\b/u);
+ assert.doesNotMatch(read("src/views/user/ApiKeysView.vue"), /\.api-key-card\b|\.api-key-glyph\b/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);
diff --git a/web/hwlab-cloud-web/src/components/access/AccessMatrixPanel.vue b/web/hwlab-cloud-web/src/components/access/AccessMatrixPanel.vue
index 64eaf31e..8ecbe935 100644
--- a/web/hwlab-cloud-web/src/components/access/AccessMatrixPanel.vue
+++ b/web/hwlab-cloud-web/src/components/access/AccessMatrixPanel.vue
@@ -3,13 +3,11 @@ import { computed, onMounted, watch } from "vue";
import { useRoute, useRouter } from "vue-router";
import AsyncBoundary from "@/components/common/AsyncBoundary.vue";
import EmptyState from "@/components/common/EmptyState.vue";
-import EntityIndex from "@/components/console/EntityIndex.vue";
-import FactPath from "@/components/console/FactPath.vue";
-import InspectorPanel from "@/components/console/InspectorPanel.vue";
import OperationsWorkspace from "@/components/console/OperationsWorkspace.vue";
-import ViewModeSwitch from "@/components/console/ViewModeSwitch.vue";
-import SectionFrame from "@/components/layout/SectionFrame.vue";
import StatusStrip from "@/components/layout/StatusStrip.vue";
+import AccessPermissionWorkspace from "./AccessPermissionWorkspace.vue";
+import AccessUserIndex from "./AccessUserIndex.vue";
+import AccessUserInspector from "./AccessUserInspector.vue";
import { useConsoleViewState } from "@/composables/useConsoleViewState";
import { useAccessStore } from "@/stores/access";
import {
@@ -135,16 +133,14 @@ function closeInspector(): void {
access.clearSelection();
void router.push({ name: "Access", query: route.query });
}
-function onRoleChange(event: Event): void {
- const role = (event.target as HTMLSelectElement).value as AccessUserRole;
+function onRoleChange(role: AccessUserRole): void {
void access.updateSelectedUser({ role });
}
-function onStatusChange(event: Event): void {
- const status = (event.target as HTMLSelectElement).value as AccessUserStatus;
+function onStatusChange(status: AccessUserStatus): void {
void access.updateSelectedUser({ status });
}
-function onToolChange(toolId: string, event: Event): void {
- void access.setTool(toolId, (event.target as HTMLInputElement).checked);
+function onToolChange(toolId: string, allowed: boolean): void {
+ void access.setTool(toolId, allowed);
}
function valueText(value: unknown): string {
return typeof value === "string" && value.trim()
@@ -182,219 +178,18 @@ function valueText(value: unknown): string {
:inspector-open="Boolean(access.selectedUser)"
>
-
- {{ access.mutationError }} -
-- 暂无 tuple。 -
-{{ valueText(tuple.userId) }}{{ valueText(tuple.relation) }}{{ valueText(tuple.object) }}
- {{ selected.targetSecretResourceVersion || "-" }}
- - 本页不展示 Secret 值、原始 provider payload - 或可逆凭据。Plan、事件、fingerprint 与原始诊断仅在授权详情层披露。 -
-{{ key.id }}{{ key.prefix || '-' }}
- {{ key.revokedAt ? 'revoked' : 'active' }}
- {{ key.id }}{{ key.prefix || '-' }}
- {{ key.revokedAt ? 'revoked' : 'active' }}
-