diff --git a/07-framework-security/cms/directus/INDEX.md b/07-framework-security/cms/directus/INDEX.md index 14c539fb..cda7d821 100644 --- a/07-framework-security/cms/directus/INDEX.md +++ b/07-framework-security/cms/directus/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/cms/discourse/INDEX.md b/07-framework-security/cms/discourse/INDEX.md index 2147cd27..7e6e58b3 100644 --- a/07-framework-security/cms/discourse/INDEX.md +++ b/07-framework-security/cms/discourse/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/cms/drupal/INDEX.md b/07-framework-security/cms/drupal/INDEX.md index 687b954d..7a561fee 100644 --- a/07-framework-security/cms/drupal/INDEX.md +++ b/07-framework-security/cms/drupal/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/cms/ghost/INDEX.md b/07-framework-security/cms/ghost/INDEX.md index 4b4498bb..17472c28 100644 --- a/07-framework-security/cms/ghost/INDEX.md +++ b/07-framework-security/cms/ghost/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/cms/joomla/INDEX.md b/07-framework-security/cms/joomla/INDEX.md index 63ca2843..4128536d 100644 --- a/07-framework-security/cms/joomla/INDEX.md +++ b/07-framework-security/cms/joomla/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/cms/mediawiki/INDEX.md b/07-framework-security/cms/mediawiki/INDEX.md index 3fadf9ee..1f68ae6f 100644 --- a/07-framework-security/cms/mediawiki/INDEX.md +++ b/07-framework-security/cms/mediawiki/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/cms/moodle/INDEX.md b/07-framework-security/cms/moodle/INDEX.md index bb5ce77c..df25e386 100644 --- a/07-framework-security/cms/moodle/INDEX.md +++ b/07-framework-security/cms/moodle/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/cms/strapi/INDEX.md b/07-framework-security/cms/strapi/INDEX.md index 31a0f64a..229a812b 100644 --- a/07-framework-security/cms/strapi/INDEX.md +++ b/07-framework-security/cms/strapi/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/cms/wordpress/INDEX.md b/07-framework-security/cms/wordpress/INDEX.md index 72eb7d3a..0e341617 100644 --- a/07-framework-security/cms/wordpress/INDEX.md +++ b/07-framework-security/cms/wordpress/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/adobe-commerce/INDEX.md b/07-framework-security/ecommerce/adobe-commerce/INDEX.md index 992eb053..4338a9be 100644 --- a/07-framework-security/ecommerce/adobe-commerce/INDEX.md +++ b/07-framework-security/ecommerce/adobe-commerce/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/magento-open-source/INDEX.md b/07-framework-security/ecommerce/magento-open-source/INDEX.md index 3e15b559..c39a99d9 100644 --- a/07-framework-security/ecommerce/magento-open-source/INDEX.md +++ b/07-framework-security/ecommerce/magento-open-source/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/medusa/INDEX.md b/07-framework-security/ecommerce/medusa/INDEX.md index 5a5cb9e3..46332d21 100644 --- a/07-framework-security/ecommerce/medusa/INDEX.md +++ b/07-framework-security/ecommerce/medusa/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/opencart/INDEX.md b/07-framework-security/ecommerce/opencart/INDEX.md index 2a98c3f8..58205a13 100644 --- a/07-framework-security/ecommerce/opencart/INDEX.md +++ b/07-framework-security/ecommerce/opencart/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/openmage/INDEX.md b/07-framework-security/ecommerce/openmage/INDEX.md index 8baa8684..135ca753 100644 --- a/07-framework-security/ecommerce/openmage/INDEX.md +++ b/07-framework-security/ecommerce/openmage/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/prestashop/INDEX.md b/07-framework-security/ecommerce/prestashop/INDEX.md index 8756d94b..0bc5ca6a 100644 --- a/07-framework-security/ecommerce/prestashop/INDEX.md +++ b/07-framework-security/ecommerce/prestashop/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/saleor/INDEX.md b/07-framework-security/ecommerce/saleor/INDEX.md index ba517942..ffeed565 100644 --- a/07-framework-security/ecommerce/saleor/INDEX.md +++ b/07-framework-security/ecommerce/saleor/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/shopware/INDEX.md b/07-framework-security/ecommerce/shopware/INDEX.md index 8bf1229d..3f4ab6ab 100644 --- a/07-framework-security/ecommerce/shopware/INDEX.md +++ b/07-framework-security/ecommerce/shopware/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/woocommerce/INDEX.md b/07-framework-security/ecommerce/woocommerce/INDEX.md index a05d182d..a9ce6c71 100644 --- a/07-framework-security/ecommerce/woocommerce/INDEX.md +++ b/07-framework-security/ecommerce/woocommerce/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/angular/INDEX.md b/07-framework-security/frameworks/angular/INDEX.md index 0232c7ee..1c703c9b 100644 --- a/07-framework-security/frameworks/angular/INDEX.md +++ b/07-framework-security/frameworks/angular/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:01+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/aspnet-core/INDEX.md b/07-framework-security/frameworks/aspnet-core/INDEX.md index ab2d48ae..6d108406 100644 --- a/07-framework-security/frameworks/aspnet-core/INDEX.md +++ b/07-framework-security/frameworks/aspnet-core/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/astro/INDEX.md b/07-framework-security/frameworks/astro/INDEX.md index 0b9f2619..11b9a78e 100644 --- a/07-framework-security/frameworks/astro/INDEX.md +++ b/07-framework-security/frameworks/astro/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:01+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/django/INDEX.md b/07-framework-security/frameworks/django/INDEX.md index 2e1cb403..cd30dd8d 100644 --- a/07-framework-security/frameworks/django/INDEX.md +++ b/07-framework-security/frameworks/django/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/echo/INDEX.md b/07-framework-security/frameworks/echo/INDEX.md index a982acce..4609701b 100644 --- a/07-framework-security/frameworks/echo/INDEX.md +++ b/07-framework-security/frameworks/echo/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/esbuild/INDEX.md b/07-framework-security/frameworks/esbuild/INDEX.md index ab50ea61..4afd5a52 100644 --- a/07-framework-security/frameworks/esbuild/INDEX.md +++ b/07-framework-security/frameworks/esbuild/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/express/INDEX.md b/07-framework-security/frameworks/express/INDEX.md index 7dd4dc28..c48f616a 100644 --- a/07-framework-security/frameworks/express/INDEX.md +++ b/07-framework-security/frameworks/express/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:01+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/fastify/INDEX.md b/07-framework-security/frameworks/fastify/INDEX.md index c8fcc792..31e97a4b 100644 --- a/07-framework-security/frameworks/fastify/INDEX.md +++ b/07-framework-security/frameworks/fastify/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:01+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/flask/INDEX.md b/07-framework-security/frameworks/flask/INDEX.md index 379fd5f3..ae61d244 100644 --- a/07-framework-security/frameworks/flask/INDEX.md +++ b/07-framework-security/frameworks/flask/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/gin/INDEX.md b/07-framework-security/frameworks/gin/INDEX.md index 70b31f6a..17450e48 100644 --- a/07-framework-security/frameworks/gin/INDEX.md +++ b/07-framework-security/frameworks/gin/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/hapi/INDEX.md b/07-framework-security/frameworks/hapi/INDEX.md index 4356c7de..d1f1fde2 100644 --- a/07-framework-security/frameworks/hapi/INDEX.md +++ b/07-framework-security/frameworks/hapi/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:01+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/koa/INDEX.md b/07-framework-security/frameworks/koa/INDEX.md index 71ef803b..1cae7fb1 100644 --- a/07-framework-security/frameworks/koa/INDEX.md +++ b/07-framework-security/frameworks/koa/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:01+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/laravel/INDEX.md b/07-framework-security/frameworks/laravel/INDEX.md index cd08e163..8ad280fa 100644 --- a/07-framework-security/frameworks/laravel/INDEX.md +++ b/07-framework-security/frameworks/laravel/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/nestjs/INDEX.md b/07-framework-security/frameworks/nestjs/INDEX.md index 8caca222..414ae4b8 100644 --- a/07-framework-security/frameworks/nestjs/INDEX.md +++ b/07-framework-security/frameworks/nestjs/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:01+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/nextjs/INDEX.md b/07-framework-security/frameworks/nextjs/INDEX.md index 6a545cea..9f78e6e4 100644 --- a/07-framework-security/frameworks/nextjs/INDEX.md +++ b/07-framework-security/frameworks/nextjs/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `26` -- 最近渲染时间: `2026-03-17T09:03:00+00:00` +- 最近渲染时间: `2026-03-17T09:29:49+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/nodejs/INDEX.md b/07-framework-security/frameworks/nodejs/INDEX.md index 69330fb1..95214752 100644 --- a/07-framework-security/frameworks/nodejs/INDEX.md +++ b/07-framework-security/frameworks/nodejs/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:01+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/nuxt/INDEX.md b/07-framework-security/frameworks/nuxt/INDEX.md index 8b8bb158..57df4e7c 100644 --- a/07-framework-security/frameworks/nuxt/INDEX.md +++ b/07-framework-security/frameworks/nuxt/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:00+00:00` +- 最近渲染时间: `2026-03-17T09:29:49+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/rails/INDEX.md b/07-framework-security/frameworks/rails/INDEX.md index f4c71218..762e42ff 100644 --- a/07-framework-security/frameworks/rails/INDEX.md +++ b/07-framework-security/frameworks/rails/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/react/INDEX.md b/07-framework-security/frameworks/react/INDEX.md index c5868704..5b8f7269 100644 --- a/07-framework-security/frameworks/react/INDEX.md +++ b/07-framework-security/frameworks/react/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:02:59+00:00` +- 最近渲染时间: `2026-03-17T09:29:47+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/spring-boot/INDEX.md b/07-framework-security/frameworks/spring-boot/INDEX.md index 38a91730..44daad9a 100644 --- a/07-framework-security/frameworks/spring-boot/INDEX.md +++ b/07-framework-security/frameworks/spring-boot/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/spring-framework/INDEX.md b/07-framework-security/frameworks/spring-framework/INDEX.md index 1f5bae8a..4f93df79 100644 --- a/07-framework-security/frameworks/spring-framework/INDEX.md +++ b/07-framework-security/frameworks/spring-framework/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/spring-security/INDEX.md b/07-framework-security/frameworks/spring-security/INDEX.md index b9cdcec0..54a7898b 100644 --- a/07-framework-security/frameworks/spring-security/INDEX.md +++ b/07-framework-security/frameworks/spring-security/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/sveltekit/INDEX.md b/07-framework-security/frameworks/sveltekit/INDEX.md index 8ec76b12..ccd84f3c 100644 --- a/07-framework-security/frameworks/sveltekit/INDEX.md +++ b/07-framework-security/frameworks/sveltekit/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:01+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/symfony/INDEX.md b/07-framework-security/frameworks/symfony/INDEX.md index 1a384cda..9795b63f 100644 --- a/07-framework-security/frameworks/symfony/INDEX.md +++ b/07-framework-security/frameworks/symfony/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/undici/INDEX.md b/07-framework-security/frameworks/undici/INDEX.md index ddf409cd..a33725e2 100644 --- a/07-framework-security/frameworks/undici/INDEX.md +++ b/07-framework-security/frameworks/undici/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `14` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/vite/INDEX.md b/07-framework-security/frameworks/vite/INDEX.md index 2e24527d..beec6553 100644 --- a/07-framework-security/frameworks/vite/INDEX.md +++ b/07-framework-security/frameworks/vite/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `12` -- 最近渲染时间: `2026-03-17T09:03:01+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/vue/INDEX.md b/07-framework-security/frameworks/vue/INDEX.md index c70bd8f2..6fa1c917 100644 --- a/07-framework-security/frameworks/vue/INDEX.md +++ b/07-framework-security/frameworks/vue/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:00+00:00` +- 最近渲染时间: `2026-03-17T09:29:49+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/webpack/INDEX.md b/07-framework-security/frameworks/webpack/INDEX.md index 49092df4..3c60df0d 100644 --- a/07-framework-security/frameworks/webpack/INDEX.md +++ b/07-framework-security/frameworks/webpack/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/werkzeug/INDEX.md b/07-framework-security/frameworks/werkzeug/INDEX.md index cef253eb..c09614fa 100644 --- a/07-framework-security/frameworks/werkzeug/INDEX.md +++ b/07-framework-security/frameworks/werkzeug/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/adminer/INDEX.md b/07-framework-security/platforms/adminer/INDEX.md index 94eb8a9e..a47f759c 100644 --- a/07-framework-security/platforms/adminer/INDEX.md +++ b/07-framework-security/platforms/adminer/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/gitea/INDEX.md b/07-framework-security/platforms/gitea/INDEX.md index b8a7a54b..1d2a9379 100644 --- a/07-framework-security/platforms/gitea/INDEX.md +++ b/07-framework-security/platforms/gitea/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `1` - 待人工/缺浏览器证据: `36` -- 最近渲染时间: `2026-03-17T09:03:04+00:00` +- 最近渲染时间: `2026-03-17T09:29:53+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/gitlab-ce/INDEX.md b/07-framework-security/platforms/gitlab-ce/INDEX.md index 99a42d02..213c0d53 100644 --- a/07-framework-security/platforms/gitlab-ce/INDEX.md +++ b/07-framework-security/platforms/gitlab-ce/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:04+00:00` +- 最近渲染时间: `2026-03-17T09:29:53+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/grafana/INDEX.md b/07-framework-security/platforms/grafana/INDEX.md index e55114c9..79bdf52c 100644 --- a/07-framework-security/platforms/grafana/INDEX.md +++ b/07-framework-security/platforms/grafana/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:04+00:00` +- 最近渲染时间: `2026-03-17T09:29:53+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/jenkins/INDEX.md b/07-framework-security/platforms/jenkins/INDEX.md index 57dd6e90..3fa70e04 100644 --- a/07-framework-security/platforms/jenkins/INDEX.md +++ b/07-framework-security/platforms/jenkins/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:04+00:00` +- 最近渲染时间: `2026-03-17T09:29:53+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/kibana/INDEX.md b/07-framework-security/platforms/kibana/INDEX.md index 32b40520..09f38528 100644 --- a/07-framework-security/platforms/kibana/INDEX.md +++ b/07-framework-security/platforms/kibana/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:04+00:00` +- 最近渲染时间: `2026-03-17T09:29:53+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/mattermost/INDEX.md b/07-framework-security/platforms/mattermost/INDEX.md index 01c44763..5ab64fef 100644 --- a/07-framework-security/platforms/mattermost/INDEX.md +++ b/07-framework-security/platforms/mattermost/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:04+00:00` +- 最近渲染时间: `2026-03-17T09:29:53+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/phpmyadmin/INDEX.md b/07-framework-security/platforms/phpmyadmin/INDEX.md index 2576b17a..b0a024d7 100644 --- a/07-framework-security/platforms/phpmyadmin/INDEX.md +++ b/07-framework-security/platforms/phpmyadmin/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/redmine/INDEX.md b/07-framework-security/platforms/redmine/INDEX.md index b2c9c404..b6c9e056 100644 --- a/07-framework-security/platforms/redmine/INDEX.md +++ b/07-framework-security/platforms/redmine/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:04+00:00` +- 最近渲染时间: `2026-03-17T09:29:53+00:00` ## 目标约束 diff --git a/07-framework-security/servers/apache-httpd/INDEX.md b/07-framework-security/servers/apache-httpd/INDEX.md index d18092d9..30ee679d 100644 --- a/07-framework-security/servers/apache-httpd/INDEX.md +++ b/07-framework-security/servers/apache-httpd/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/servers/apache-tomcat/INDEX.md b/07-framework-security/servers/apache-tomcat/INDEX.md index 1b0ba0c6..68c5903c 100644 --- a/07-framework-security/servers/apache-tomcat/INDEX.md +++ b/07-framework-security/servers/apache-tomcat/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/servers/caddy/INDEX.md b/07-framework-security/servers/caddy/INDEX.md index 87de3a45..970eb358 100644 --- a/07-framework-security/servers/caddy/INDEX.md +++ b/07-framework-security/servers/caddy/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/servers/haproxy/INDEX.md b/07-framework-security/servers/haproxy/INDEX.md index 75c9b13f..0b95f0f0 100644 --- a/07-framework-security/servers/haproxy/INDEX.md +++ b/07-framework-security/servers/haproxy/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/servers/nginx/INDEX.md b/07-framework-security/servers/nginx/INDEX.md index 8f0603de..9c487dff 100644 --- a/07-framework-security/servers/nginx/INDEX.md +++ b/07-framework-security/servers/nginx/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/07-framework-security/servers/traefik/INDEX.md b/07-framework-security/servers/traefik/INDEX.md index 9fce52b1..18147461 100644 --- a/07-framework-security/servers/traefik/INDEX.md +++ b/07-framework-security/servers/traefik/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-17T09:03:02+00:00` +- 最近渲染时间: `2026-03-17T09:29:50+00:00` ## 目标约束 diff --git a/08-threat-intel/generated/dashboard/architecture.json b/08-threat-intel/generated/dashboard/architecture.json index 391514f3..8dc46ce9 100644 --- a/08-threat-intel/generated/dashboard/architecture.json +++ b/08-threat-intel/generated/dashboard/architecture.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-03-17T09:03:14+00:00", + "generated_at": "2026-03-17T09:27:20+00:00", "title": "\u5f53\u524d\u67b6\u6784\u5e93", "summary": "\u5de5\u4f5c\u53f0\u3001\u63a7\u5236\u9762\u3001\u6570\u636e\u5c42\u3001\u6388\u6743\u8fb9\u754c\u4e0e\u7cfb\u7edf\u8986\u76d6\u7684\u5f53\u524d\u771f\u503c\u89c6\u56fe\u3002", "sections": [ @@ -49,29 +49,29 @@ }, { "label": "\u751f\u6210\u65f6\u95f4", - "value": "2026-03-17T09:03:14+00:00" + "value": "2026-03-17T09:27:20+00:00" } ], "links": [ { - "label": "\u6b63\u5f0f\u5de5\u4f5c\u53f0", - "href": "./index.html", - "description": "\u672c\u5730\u5316\u4e3b UI\uff0c\u9ed8\u8ba4\u5165\u53e3\u3002" + "label": "\u603b\u89c8\u9996\u9875", + "href": "/overview/index.html", + "description": "\u5de5\u4f5c\u53f0\u603b\u89c8\u3001\u6700\u65b0\u8fd0\u884c\u548c\u5168\u5c40\u6458\u8981\u3002" }, { - "label": "\u65e7\u7248\u5de5\u4f5c\u53f0", - "href": "./legacy/index.html", - "description": "\u4fdd\u7559\u7684 legacy \u56de\u9000\u5165\u53e3\u3002" + "label": "\u8fd0\u884c\u4e2d\u5fc3", + "href": "/runs/index.html", + "description": "\u8fd0\u884c\u961f\u5217\u3001\u8be6\u60c5\u3001\u8bc1\u636e\u548c\u65e5\u5fd7\u5165\u53e3\u3002" }, { - "label": "\u9879\u76ee\u529f\u80fd\u6587\u6863", - "href": "./docs/project-features.html", - "description": "\u9879\u76ee\u80fd\u529b\u3001\u76ee\u5f55\u7ed3\u6784\u4e0e\u81ea\u52a8\u5316\u94fe\u8def\u603b\u89c8\u3002" + "label": "\u7cfb\u7edf\u5206\u7ec4", + "href": "/systems/index.html", + "description": "\u6309\u7cfb\u7edf\u548c\u5206\u7c7b\u6d4f\u89c8\u8986\u76d6\u60c5\u51b5\u3002" }, { - "label": "\u524d\u7aef\u8bbe\u8ba1\u6587\u6863", - "href": "./docs/frontend-dashboard-design.html", - "description": "\u5f53\u524d\u672c\u5730\u5de5\u4f5c\u53f0\u7684\u4ea4\u4e92\u4e0e\u89c6\u89c9\u89c4\u8303\u3002" + "label": "\u67b6\u6784\u5e93", + "href": "/architecture/index.html", + "description": "\u67e5\u770b\u63a7\u5236\u9762\u3001\u6570\u636e\u5c42\u548c\u6388\u6743\u8fb9\u754c\u3002" } ] }, @@ -199,93 +199,118 @@ "open": false, "links": [ { - "label": "\u6b63\u5f0f\u5de5\u4f5c\u53f0", - "href": "./index.html", - "description": "\u672c\u5730\u5316\u4e3b UI\uff0c\u9ed8\u8ba4\u5165\u53e3\u3002" + "label": "\u603b\u89c8\u9996\u9875", + "href": "/overview/index.html", + "description": "\u5de5\u4f5c\u53f0\u603b\u89c8\u3001\u6700\u65b0\u8fd0\u884c\u548c\u5168\u5c40\u6458\u8981\u3002" + }, + { + "label": "\u8fd0\u884c\u4e2d\u5fc3", + "href": "/runs/index.html", + "description": "\u8fd0\u884c\u961f\u5217\u3001\u8be6\u60c5\u3001\u8bc1\u636e\u548c\u65e5\u5fd7\u5165\u53e3\u3002" + }, + { + "label": "\u7cfb\u7edf\u5206\u7ec4", + "href": "/systems/index.html", + "description": "\u6309\u7cfb\u7edf\u548c\u5206\u7c7b\u6d4f\u89c8\u8986\u76d6\u60c5\u51b5\u3002" + }, + { + "label": "\u67b6\u6784\u5e93", + "href": "/architecture/index.html", + "description": "\u67e5\u770b\u63a7\u5236\u9762\u3001\u6570\u636e\u5c42\u548c\u6388\u6743\u8fb9\u754c\u3002" + }, + { + "label": "\u6587\u6863\u4e2d\u5fc3", + "href": "/docs/index.html", + "description": "\u96c6\u4e2d\u67e5\u770b\u9879\u76ee\u6587\u6863\u3001\u672c\u5730\u955c\u50cf\u548c\u8bf4\u660e\u3002" + }, + { + "label": "\u6570\u636e\u4e2d\u5fc3", + "href": "/data/index.html", + "description": "\u67e5\u770b summary\u3001runs\u3001systems \u7b49 JSON \u5165\u53e3\u3002" }, { "label": "\u65e7\u7248\u5de5\u4f5c\u53f0", - "href": "./legacy/index.html", + "href": "/legacy/index.html", "description": "\u4fdd\u7559\u7684 legacy \u56de\u9000\u5165\u53e3\u3002" }, { "label": "\u9879\u76ee\u529f\u80fd\u6587\u6863", - "href": "./docs/project-features.html", + "href": "/docs/project-features.html", "description": "\u9879\u76ee\u80fd\u529b\u3001\u76ee\u5f55\u7ed3\u6784\u4e0e\u81ea\u52a8\u5316\u94fe\u8def\u603b\u89c8\u3002" }, { "label": "\u524d\u7aef\u8bbe\u8ba1\u6587\u6863", - "href": "./docs/frontend-dashboard-design.html", + "href": "/docs/frontend-dashboard-design.html", "description": "\u5f53\u524d\u672c\u5730\u5de5\u4f5c\u53f0\u7684\u4ea4\u4e92\u4e0e\u89c6\u89c9\u89c4\u8303\u3002" }, { "label": "\u5b89\u5168\u7f16\u7801\u7d22\u5f15", - "href": "./docs/secure-code-index.html", + "href": "/docs/secure-code-index.html", "description": "secure-code \u4fee\u590d\u5e93\u672c\u5730\u955c\u50cf\u3002" }, { "label": "\u4ed3\u5e93\u5165\u53e3\u955c\u50cf", - "href": "./docs/root-readme.html", + "href": "/docs/root-readme.html", "description": "\u4ed3\u5e93\u6839 README \u7684\u672c\u5730\u955c\u50cf\u3002" }, { "label": "\u6388\u6743\u6a21\u578b", - "href": "./docs/authorization-model.html", + "href": "/docs/authorization-model.html", "description": "\u5141\u8bb8\u76ee\u6807\u8303\u56f4\u3001\u5168\u5c40\u539f\u5219\u4e0e\u8bb0\u5f55\u8981\u6c42\u3002" }, { "label": "source-map \u771f\u503c", - "href": "./docs/source-map.html", + "href": "/docs/source-map.html", "description": "\u7cfb\u7edf\u8986\u76d6\u3001\u6765\u6e90\u548c\u8f93\u51fa\u76ee\u5f55\u771f\u503c\u3002" }, { "label": "repro-map \u771f\u503c", - "href": "./docs/repro-map.html", + "href": "/docs/repro-map.html", "description": "\u590d\u73b0\u65cf\u8def\u7531\u3001\u6d4f\u89c8\u5668\u8981\u6c42\u548c\u65e5\u5fd7\u7b56\u7565\u3002" }, { "label": "\u8986\u76d6\u77e9\u9635", - "href": "./docs/coverage-matrix.html", + "href": "/docs/coverage-matrix.html", "description": "\u81ea\u52a8\u751f\u6210\u8986\u76d6\u6458\u8981\u7684\u672c\u5730\u955c\u50cf\u3002" }, { "label": "\u8bbe\u8ba1\u6765\u6e90\u6e05\u5355", - "href": "./docs/design-source.html", + "href": "/docs/design-source.html", "description": "Lovart \u6a21\u677f\u672c\u5730 vendor manifest\u3002" }, { "label": "\u67b6\u6784\u5e93\u955c\u50cf", - "href": "./docs/architecture-library.html", + "href": "/docs/architecture-library.html", "description": "\u5f53\u524d\u67b6\u6784\u5e93\u7684\u7ed3\u6784\u5316\u955c\u50cf\u9875\u3002" }, { "label": "summary.json", - "href": "./summary.json", + "href": "/summary.json", "description": "\u5168\u5c40\u6458\u8981\u3001\u72b6\u6001\u5206\u5e03\u548c\u6700\u8fd1\u5931\u8d25\u3002" }, { "label": "runs.json", - "href": "./runs.json", + "href": "/runs.json", "description": "\u6700\u8fd1 run \u7684\u7ed3\u6784\u5316\u8be6\u60c5\u3002" }, { "label": "systems.json", - "href": "./systems.json", + "href": "/systems.json", "description": "\u7cfb\u7edf\u7ea7\u8986\u76d6\u4e0e\u6d4f\u89c8\u5668\u8bc1\u636e\u6458\u8981\u3002" }, { "label": "advisories.json", - "href": "./advisories.json", - "description": "advisory \u5143\u6570\u636e\u4e0e\u6765\u6e90\u3002" + "href": "/advisories.json", + "description": "\u6f0f\u6d1e\u6761\u76ee\u5143\u6570\u636e\u4e0e\u6765\u6e90\u3002" }, { "label": "profiles.json", - "href": "./profiles.json", - "description": "repro profile \u5143\u6570\u636e\u3002" + "href": "/profiles.json", + "description": "\u590d\u73b0\u6863\u6848\u5143\u6570\u636e\u3002" }, { "label": "architecture.json", - "href": "./architecture.json", + "href": "/architecture.json", "description": "\u5f53\u524d\u67b6\u6784\u5e93\u7ed3\u6784\u5316 JSON\u3002" } ], @@ -299,9 +324,33 @@ "value": "06-case-studies/generated-runs//" }, { - "label": "\u5de5\u4f5c\u53f0\u5165\u53e3", + "label": "\u9ed8\u8ba4\u5165\u53e3", "value": "/index.html" }, + { + "label": "\u603b\u89c8\u5165\u53e3", + "value": "/overview/index.html" + }, + { + "label": "\u8fd0\u884c\u5165\u53e3", + "value": "/runs/index.html" + }, + { + "label": "\u7cfb\u7edf\u5165\u53e3", + "value": "/systems/index.html" + }, + { + "label": "\u67b6\u6784\u5165\u53e3", + "value": "/architecture/index.html" + }, + { + "label": "\u6587\u6863\u5165\u53e3", + "value": "/docs/index.html" + }, + { + "label": "\u6570\u636e\u5165\u53e3", + "value": "/data/index.html" + }, { "label": "\u65e7\u7248\u5165\u53e3", "value": "/legacy/index.html" diff --git a/08-threat-intel/generated/dashboard/architecture/index.html b/08-threat-intel/generated/dashboard/architecture/index.html new file mode 100644 index 00000000..be57391b --- /dev/null +++ b/08-threat-intel/generated/dashboard/architecture/index.html @@ -0,0 +1,104 @@ + + + + + + 授权攻防实验工作台 + + + + +
+
+ + + +
+
+
+ + 授权攻防实验工作台 +
+

本地攻防实证工作台

+

+ Lovart 设计外壳已本地化并接入真实 run bundle 数据。页面只面向授权实验资产, + 聚合漏洞条目、时间线、证据、日志、来源、原始 JSON、当前架构库与失败原因。 +

+
+ +
+ + +
+ +
+ 启动中 + 正在载入本地生成数据 +
+
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +

选择一个运行

+

从左侧队列选择 run,即可查看时间线、证据、日志、来源、原始 JSON 和当前架构库。

+
+
+
+ + +
+ + + + diff --git a/08-threat-intel/generated/dashboard/assets/app.js b/08-threat-intel/generated/dashboard/assets/app.js index 87a78e5a..c676bb1f 100644 --- a/08-threat-intel/generated/dashboard/assets/app.js +++ b/08-threat-intel/generated/dashboard/assets/app.js @@ -1,32 +1,18 @@ -const state = { - summary: null, - runs: [], - systems: [], - advisories: {}, - profiles: {}, - architecture: null, - selectedRunId: null, - selectedArtifact: null, - refreshHandle: null, - refreshMs: 5000, - autoRefresh: true, - filters: { - search: "", - system: "", - status: "", - profile: "" - }, - panels: { - timeline: true, - reasoning: true, - evidence: true, - logs: true, - sources: true, - architecture: true, - run_json: false, - advisory_json: false, - profile_json: false - } +const SECTION_META = [ + { id: "overview", label: "总览", path: "/overview/index.html", description: "查看全局指标、最新运行、架构摘要和入口。", icon: "report" }, + { id: "runs", label: "运行", path: "/runs/index.html", description: "查看运行队列、单次运行详情、证据与日志。", icon: "queue" }, + { id: "systems", label: "系统", path: "/systems/index.html", description: "按系统与分类查看覆盖情况和跳转入口。", icon: "systems" }, + { id: "architecture", label: "架构", path: "/architecture/index.html", description: "折叠查看控制面、数据层、授权边界与路由。", icon: "reasoning" }, + { id: "docs", label: "文档", path: "/docs/index.html", description: "集中访问功能文档、设计文档和镜像说明。", icon: "docs" }, + { id: "data", label: "数据", path: "/data/index.html", description: "集中访问 summary、runs、systems 等 JSON 产物。", icon: "json" } +]; + +const CATEGORY_LABELS = { + cms: "CMS / 内容平台", + ecommerce: "电商系统", + frameworks: "Web 框架与运行时", + servers: "服务器与边界层", + platforms: "开源平台与后台系统" }; const STATUS_LABELS = { @@ -49,9 +35,66 @@ const ARTIFACT_KIND_LABELS = { link: "链接" }; +const DOC_HUB_ITEMS = [ + { title: "项目功能总览", href: "/docs/project-features.html", description: "项目定位、功能版图、自动化链路和 CLI 入口。", badge: "docs" }, + { title: "前端设计文档", href: "/docs/frontend-dashboard-design.html", description: "工作台布局、交互、折叠逻辑和视觉规范。", badge: "ui" }, + { title: "架构库镜像", href: "/docs/architecture-library.html", description: "当前架构库的结构化镜像页,可直接查看 JSON 真值。", badge: "architecture" }, + { title: "仓库入口镜像", href: "/docs/root-readme.html", description: "根 README 的本地镜像,包含能力矩阵与主入口。", badge: "readme" }, + { title: "授权模型", href: "/docs/authorization-model.html", description: "目标范围、授权模型、最小化验证建议和记录要求。", badge: "scope" }, + { title: "source-map 镜像", href: "/docs/source-map.html", description: "系统覆盖、来源、输出目录和 secure-code 主题真值。", badge: "source-map" }, + { title: "repro-map 镜像", href: "/docs/repro-map.html", description: "默认漏洞家族、浏览器要求和日志策略真值。", badge: "repro-map" }, + { title: "覆盖矩阵镜像", href: "/docs/coverage-matrix.html", description: "当前全库覆盖矩阵的本地镜像。", badge: "coverage" }, + { title: "安全编码索引", href: "/docs/secure-code-index.html", description: "secure-code 修复主题索引镜像。", badge: "secure-code" }, + { title: "设计来源", href: "/docs/design-source.html", description: "Lovart 模板来源、本地化记录和 manifest 镜像。", badge: "vendor" } +]; + +const DATA_HUB_ITEMS = [ + { title: "summary.json", href: "/summary.json", description: "全局摘要、状态分布、最近失败与系统汇总。", badge: "json" }, + { title: "runs.json", href: "/runs.json", description: "最近运行的结构化详情,可用于 UI 和调试。", badge: "json" }, + { title: "systems.json", href: "/systems.json", description: "系统级覆盖、分类、更新时间和浏览器证据统计。", badge: "json" }, + { title: "advisories.json", href: "/advisories.json", description: "漏洞条目元数据、来源和 secure-code 主题。", badge: "json" }, + { title: "profiles.json", href: "/profiles.json", description: "复现档案元数据、成功判据和 browser assertions。", badge: "json" }, + { title: "architecture.json", href: "/architecture.json", description: "当前架构库的结构化真值。", badge: "json" }, + { title: "Manifest JSON", href: "/assets/design-source.json", description: "Lovart 模板本地 vendor manifest。", badge: "assets" }, + { title: "最新同步摘要", href: "/docs/coverage-matrix.html", description: "覆盖矩阵与本地生成态入口。", badge: "generated" } +]; + +const state = { + routeSection: resolveRouteSection(), + summary: null, + runs: [], + systems: [], + advisories: {}, + profiles: {}, + architecture: null, + selectedRunId: null, + selectedArtifact: null, + refreshHandle: null, + refreshMs: 5000, + autoRefresh: true, + filters: { + search: "", + status: "", + category: "", + family: "", + system: "" + }, + panels: { + timeline: true, + reasoning: true, + evidence: true, + logs: true, + sources: true, + architecture: true, + run_json: false, + advisory_json: false, + profile_json: false + } +}; + const $ = (id) => document.getElementById(id); const icon = (name, className = "icon") => - ``; + ``; const statusClass = (status) => ({ "verified-real": "status-pill status-verified-real", @@ -65,6 +108,24 @@ const statusClass = (status) => ({ skipped: "status-pill status-triage-manual" }[status] || "status-pill status-default"); +function resolveRouteSection() { + const parts = window.location.pathname.split("/").filter(Boolean); + const first = parts[0] || ""; + if (!first || first === "index.html" || first === "overview") return "overview"; + if (SECTION_META.some((section) => section.id === first)) return first; + return "overview"; +} + +function currentOverviewAlias() { + return window.location.pathname === "/" || window.location.pathname === "/index.html"; +} + +function routePath(sectionId) { + if (sectionId === "overview" && currentOverviewAlias()) return window.location.pathname || "/index.html"; + const section = SECTION_META.find((item) => item.id === sectionId); + return section ? section.path : "/overview/index.html"; +} + function escapeHtml(value) { return String(value ?? "") .replaceAll("&", "&") @@ -77,6 +138,10 @@ function formatStatus(value) { return STATUS_LABELS[value] || String(value || "unknown").replaceAll("-", " "); } +function formatCategory(value) { + return CATEGORY_LABELS[value] || String(value || "未分类"); +} + function formatDateTime(value) { if (!value) return "-"; const date = new Date(value); @@ -107,16 +172,69 @@ function timeAgo(value) { return `${days} 天前`; } +function distinct(values) { + return [...new Set(values.filter(Boolean))].sort(); +} + async function fetchJson(url) { const response = await fetch(`${url}?t=${Date.now()}`, { cache: "no-store" }); - if (!response.ok) { - throw new Error(`${url} -> ${response.status}`); - } + if (!response.ok) throw new Error(`${url} -> ${response.status}`); return response.json(); } -function distinct(values) { - return [...new Set(values.filter(Boolean))].sort(); +function sectionMeta(sectionId = state.routeSection) { + return SECTION_META.find((item) => item.id === sectionId) || SECTION_META[0]; +} + +function applyUrlState() { + const params = new URLSearchParams(window.location.search); + state.filters.search = params.get("search") || ""; + state.filters.status = params.get("status") || ""; + state.filters.category = params.get("category") || ""; + state.filters.family = params.get("family") || ""; + state.filters.system = params.get("system") || ""; + state.selectedRunId = params.get("run") || null; +} + +function buildUrl(sectionId = state.routeSection, overrides = {}) { + const nextFilters = { ...state.filters }; + for (const [key, value] of Object.entries(overrides)) { + if (key === "run") continue; + if (value === null || value === undefined || value === "") { + delete nextFilters[key]; + nextFilters[key] = ""; + } else { + nextFilters[key] = String(value); + } + } + + const nextRunId = Object.prototype.hasOwnProperty.call(overrides, "run") + ? (overrides.run ? String(overrides.run) : "") + : (state.selectedRunId || ""); + + const params = new URLSearchParams(); + if (["overview", "runs", "systems"].includes(sectionId)) { + if (nextFilters.search) params.set("search", nextFilters.search); + if (nextFilters.status && sectionId !== "systems") params.set("status", nextFilters.status); + if (nextFilters.category) params.set("category", nextFilters.category); + if (nextFilters.family && sectionId !== "systems") params.set("family", nextFilters.family); + if (nextFilters.system) params.set("system", nextFilters.system); + } + if (sectionId === "runs" && nextRunId) params.set("run", nextRunId); + const query = params.toString(); + return `${routePath(sectionId)}${query ? `?${query}` : ""}`; +} + +function updateUrl() { + const nextUrl = buildUrl(); + const currentUrl = `${window.location.pathname}${window.location.search}`; + if (nextUrl !== currentUrl) { + window.history.replaceState({}, "", nextUrl); + } +} + +function navigateToSection(sectionId, overrides = {}) { + window.location.assign(buildUrl(sectionId, overrides)); } function sumStatuses(predicate) { @@ -125,6 +243,14 @@ function sumStatuses(predicate) { .reduce((sum, [, value]) => sum + Number(value || 0), 0); } +function categoryOptions() { + return distinct(state.systems.map((system) => system.category)); +} + +function familyOptions() { + return distinct(state.runs.map((run) => run.profile_meta?.vuln_family || "")); +} + function metricCards() { const successCount = Number(state.summary?.statuses?.["verified-real"] || 0) + Number(state.summary?.statuses?.["verified-synthetic"] || 0); const blockedCount = sumStatuses((key) => key.startsWith("blocked")); @@ -187,40 +313,118 @@ function renderSyncState(kind, title, detail) { $("syncState").dataset.kind = kind; } -function optionLabel(kind, value) { - if (kind === "status") return formatStatus(value); - return value; +function renderSectionNav() { + const active = state.routeSection; + $("sectionNav").innerHTML = SECTION_META + .map((section) => ` + + ${icon(section.icon)}${escapeHtml(section.label)} + ${escapeHtml(section.description)} + + `) + .join(""); } -function hydrateFilters() { - const controls = [ - ["systemFilter", "system", state.runs.map((item) => item.system_id), "全部系统"], - ["statusFilter", "status", state.runs.map((item) => item.verification_status), "全部状态"], - ["profileFilter", "profile", state.runs.map((item) => item.repro_profile_id), "全部复现档案"] - ]; +function renderChipRow(label, key, options, formatter) { + return ` + + `; +} - for (const [id, key, values, label] of controls) { - const control = $(id); - const current = state.filters[key]; - control.innerHTML = ``; - control.innerHTML += distinct(values) - .map((value) => ``) - .join(""); - control.value = current; +function renderTopMenus() { + const meta = sectionMeta(); + const routeLine = ` +
+
+ ${escapeHtml(meta.label)} + ${escapeHtml(meta.description)} +
+
+ 当前地址 ${escapeHtml(window.location.pathname)} + ${window.location.search ? `${escapeHtml(window.location.search)}` : ""} +
+
+ `; + + const rows = []; + if (["overview", "runs"].includes(state.routeSection)) { + rows.push(renderChipRow("状态筛选", "status", distinct(state.runs.map((item) => item.verification_status)), formatStatus)); + rows.push(renderChipRow("系统板块", "category", categoryOptions(), formatCategory)); + rows.push(renderChipRow("漏洞家族", "family", familyOptions(), (value) => value)); + } else if (state.routeSection === "systems") { + rows.push(renderChipRow("系统板块", "category", categoryOptions(), formatCategory)); } + + if (state.filters.system && ["overview", "runs", "systems"].includes(state.routeSection)) { + rows.push(` + + `); + } + + if (["overview", "runs", "systems"].includes(state.routeSection)) { + rows.push(` + + `); + } + + $("topMenus").innerHTML = `${routeLine}${rows.join("")}`; } function filteredRuns() { return state.runs.filter((item) => { if (state.filters.system && item.system_id !== state.filters.system) return false; if (state.filters.status && item.verification_status !== state.filters.status) return false; - if (state.filters.profile && item.repro_profile_id !== state.filters.profile) return false; + if (state.filters.category && item.advisory_meta?.category !== state.filters.category) return false; + if (state.filters.family && item.profile_meta?.vuln_family !== state.filters.family) return false; if (!state.filters.search) return true; const haystack = [ item.run_id, item.advisory_id, item.system_id, item.repro_profile_id, + item.profile_meta?.vuln_family || "", item.advisory_meta?.title || "", item.advisory_meta?.summary || "" ] @@ -230,37 +434,58 @@ function filteredRuns() { }); } -function renderSystems() { - $("systemStats").innerHTML = state.systems.length - ? state.systems - .map((system) => { - const total = Math.max(Number(system.total || 0), 1); - const verified = Number(system.verified_real || 0) + Number(system.verified_synthetic || 0); - const coverage = Math.round((verified / total) * 100); - return ` -
-
- ${escapeHtml(system.display_name || system.system_id)} - ${escapeHtml(system.browser_present || 0)}/${escapeHtml(system.browser_required || 0)} 浏览器证据 -
-
${escapeHtml(system.system_id)} · 最近更新 ${escapeHtml(formatDateTime(system.latest_update || "-"))}
-
- 真实 ${escapeHtml(system.verified_real || 0)} - 合成 ${escapeHtml(system.verified_synthetic || 0)} - 阻塞 ${escapeHtml(system.blocked || 0)} -
-
-
- `; - }) - .join("") - : `
暂无系统覆盖数据。
`; +function filteredSystems() { + return state.systems.filter((system) => { + if (state.filters.system && system.system_id !== state.filters.system) return false; + if (state.filters.category && system.category !== state.filters.category) return false; + if (!state.filters.search) return true; + const haystack = [ + system.system_id, + system.display_name || "", + formatCategory(system.category) + ] + .join(" ") + .toLowerCase(); + return haystack.includes(state.filters.search); + }); } -function renderRecentFailures() { - const failures = state.summary?.recent_failures || []; - $("recentFailures").innerHTML = failures.length - ? failures +function recentFailures(limit = 6) { + return (state.summary?.recent_failures || []).slice(0, limit); +} + +function recentRuns(limit = 8) { + return filteredRuns().slice(0, limit); +} + +function renderSearchSection(countLabel, placeholder) { + const tags = [ + state.filters.status ? `状态: ${formatStatus(state.filters.status)}` : "", + state.filters.category ? `板块: ${formatCategory(state.filters.category)}` : "", + state.filters.family ? `家族: ${state.filters.family}` : "", + state.filters.system ? `系统: ${state.filters.system}` : "" + ].filter(Boolean); + return ` + + `; +} + +function renderFailureCards(items) { + return items.length + ? items .map( (item) => `
@@ -277,16 +502,14 @@ function renderRecentFailures() { : `
当前没有最近失败记录。
`; } -function renderRunQueue() { - const runs = filteredRuns(); - $("runCount").textContent = `${runs.length} 条`; - $("runQueue").innerHTML = runs.length - ? runs +function renderRunList(items, emptyText) { + return items.length + ? items .map((item) => { - const active = item.run_id === state.selectedRunId ? "is-active" : ""; + const active = item.run_id === state.selectedRunId && state.routeSection === "runs" ? "is-active" : ""; const browserState = item.browser_evidence?.present ? "已采集" : (item.browser_evidence?.required ? "必需待补" : "可选"); - const lead = item.reasoning_lines?.[0] || item.blocked_reason || item.advisory_meta?.summary || ""; const artifactCount = (item.artifact_groups || []).reduce((sum, group) => sum + Number(group.count || 0), 0); + const lead = item.reasoning_lines?.[0] || item.blocked_reason || item.advisory_meta?.summary || ""; return ` + 查看运行 + +
+ `; + }) + .join("") + : `
当前筛选下没有系统数据。
`; +} + +function renderHubCards(items) { + return ` +
+ ${items + .map( + (item) => ` + +
+ ${escapeHtml(item.title)} + ${item.badge ? `${escapeHtml(item.badge)}` : ""} +
+
${escapeHtml(item.description)}
+
+ ` + ) + .join("")} +
+ `; +} + +function renderSidebar() { + const sidebar = $("sidebar"); + const filteredRunItems = filteredRuns(); + const filteredSystemItems = filteredSystems(); + const architectureSections = state.architecture?.sections || []; + + if (state.routeSection === "runs") { + sidebar.innerHTML = [ + renderSearchSection(`${filteredRunItems.length} 条运行`, "搜索 run id、漏洞条目、标题、系统、家族"), + ` + + `, + ` + + ` + ].join(""); + return; } - const bar = order - .filter(([key]) => Number(progress?.[key] || 0) > 0) - .map(([key, _label, className]) => { - const pct = Math.max((Number(progress[key] || 0) / total) * 100, 4); - return ``; - }) - .join(""); - const legend = order - .filter(([key]) => Number(progress?.[key] || 0) > 0) - .map(([key, label, className]) => `${escapeHtml(label)} ${escapeHtml(progress[key] || 0)}`) - .join(""); - return { bar, legend }; -} -function timelineTone(status) { - if (status === "completed" || status === "verified-real" || status === "verified-synthetic") return "timeline-success"; - if (String(status || "").startsWith("blocked") || status === "failed") return "timeline-blocked"; - if (status === "planned") return "timeline-pending"; - return "timeline-neutral"; + if (state.routeSection === "systems") { + sidebar.innerHTML = [ + renderSearchSection(`${filteredSystemItems.length} 个系统`, "搜索系统名、display name、板块"), + ` + + ` + ].join(""); + return; + } + + if (state.routeSection === "architecture") { + sidebar.innerHTML = ` + + `; + return; + } + + if (state.routeSection === "docs") { + sidebar.innerHTML = ` + + `; + return; + } + + if (state.routeSection === "data") { + sidebar.innerHTML = ` + + `; + return; + } + + sidebar.innerHTML = [ + renderSearchSection(`${filteredRunItems.length} 条运行`, "搜索运行、漏洞条目、标题、系统、家族"), + ` + + `, + ` + + `, + ` + + ` + ].join(""); } function renderPanel(panelKey, title, meta, iconName, content) { @@ -375,71 +730,6 @@ function renderPanel(panelKey, title, meta, iconName, content) { `; } -function defaultArtifact(run) { - const preference = ["attack", "requests", "container", "browser", "baseline", "compose", "reports"]; - for (const key of preference) { - const group = (run.artifact_groups || []).find((item) => item.key === key && item.items?.length); - if (!group) continue; - const textItem = group.items.find((item) => item.kind === "text"); - return textItem || group.items[0]; - } - return null; -} - -async function openArtifact(href, label, kind) { - state.selectedArtifact = { href, label, kind }; - document.querySelectorAll(".artifact-button").forEach((button) => { - button.classList.toggle("is-active", button.dataset.href === href); - }); - - const labelNode = $("viewerLabel"); - const metaNode = $("viewerMeta"); - const openNode = $("viewerOpen"); - const viewer = $("viewerFrame"); - if (!labelNode || !metaNode || !openNode || !viewer) return; - - labelNode.textContent = label; - metaNode.textContent = href; - openNode.href = href; - - try { - if (kind === "image") { - viewer.innerHTML = `${escapeHtml(label)}`; - return; - } - if (href.endsWith(".html")) { - viewer.innerHTML = ``; - return; - } - const response = await fetch(`${href}?t=${Date.now()}`, { cache: "no-store" }); - if (!response.ok) throw new Error(`${href} -> ${response.status}`); - const text = await response.text(); - let formatted = text; - if (href.endsWith(".json")) { - try { - formatted = JSON.stringify(JSON.parse(text), null, 2); - } catch (_error) { - } - } - viewer.innerHTML = `
${escapeHtml(formatted)}
`; - } catch (error) { - viewer.innerHTML = `
加载产物失败:${escapeHtml(error.message)}
`; - } -} - -function bindPanelToggles() { - document.querySelectorAll("[data-panel-toggle]").forEach((button) => { - button.addEventListener("click", () => { - const key = button.dataset.panelToggle; - state.panels[key] = !(state.panels[key] !== false); - const panel = document.querySelector(`[data-panel="${key}"]`); - if (panel) { - panel.classList.toggle("is-collapsed", state.panels[key] === false); - } - }); - }); -} - function renderArchitectureFields(fields = []) { if (!fields.length) return ""; return ` @@ -500,9 +790,7 @@ function renderArchitectureNode(node, depth = 0) { const fields = renderArchitectureFields(node.fields || []); const stats = renderArchitectureStats(node.stats || []); const links = renderArchitectureLinks(node.links || []); - const badges = (node.badges || []) - .map((badge) => `${escapeHtml(badge)}`) - .join(""); + const badges = (node.badges || []).map((badge) => `${escapeHtml(badge)}`).join(""); const hasBody = Boolean(children || fields || stats || links || node.summary || badges); const summaryBlock = `
@@ -517,19 +805,13 @@ function renderArchitectureNode(node, depth = 0) { `; if (!hasBody) { - return ` -
- ${summaryBlock} -
- `; + return `
${summaryBlock}
`; } const openAttr = node.open === false ? "" : "open"; return `
- - ${summaryBlock} - + ${summaryBlock}
${badges ? `
${badges}
` : ""} ${stats} @@ -553,9 +835,9 @@ function renderArchitecturePanel() {
${escapeHtml(architecture.summary || "当前工作台的结构化真值视图。")}
生成时间 ${escapeHtml(formatDateTime(architecture.generated_at))} - 架构 JSON - 镜像页 - 仓库入口镜像 + 架构 JSON + 镜像页 + 仓库入口镜像
@@ -565,93 +847,116 @@ function renderArchitecturePanel() { return renderPanel("architecture", "当前架构库", `${sections.length} 个分区`, "systems", content); } -function renderEmptyWorkspace() { - $("detailWorkspace").innerHTML = ` -
- ${icon("shield", "icon icon-xl")} -

选择一个运行

-

左侧队列用于切换 run。即使当前没有选中运行,你也可以直接展开下方“当前架构库”查看仓库控制面、数据层、系统分组、授权边界与本地入口。

-
- ${renderArchitecturePanel()} - `; - bindPanelToggles(); +function defaultArtifact(run) { + const preference = ["attack", "requests", "container", "browser", "baseline", "compose", "reports"]; + for (const key of preference) { + const group = (run.artifact_groups || []).find((item) => item.key === key && item.items?.length); + if (!group) continue; + const textItem = group.items.find((item) => item.kind === "text"); + return textItem || group.items[0]; + } + return null; } -function renderDetail() { - const run = state.runs.find((item) => item.run_id === state.selectedRunId); - if (!run) { - renderEmptyWorkspace(); - return; +async function openArtifact(href, label, kind) { + state.selectedArtifact = { href, label, kind }; + document.querySelectorAll(".artifact-button").forEach((button) => { + button.classList.toggle("is-active", button.dataset.href === href); + }); + + const labelNode = $("viewerLabel"); + const metaNode = $("viewerMeta"); + const openNode = $("viewerOpen"); + const viewer = $("viewerFrame"); + if (!labelNode || !metaNode || !openNode || !viewer) return; + + labelNode.textContent = label; + metaNode.textContent = href; + openNode.href = href; + + try { + if (kind === "image") { + viewer.innerHTML = `${escapeHtml(label)}`; + return; + } + if (href.endsWith(".html")) { + viewer.innerHTML = ``; + return; + } + const response = await fetch(`${href}?t=${Date.now()}`, { cache: "no-store" }); + if (!response.ok) throw new Error(`${href} -> ${response.status}`); + const text = await response.text(); + let formatted = text; + if (href.endsWith(".json")) { + try { + formatted = JSON.stringify(JSON.parse(text), null, 2); + } catch (_error) { + } + } + viewer.innerHTML = `
${escapeHtml(formatted)}
`; + } catch (error) { + viewer.innerHTML = `
加载产物失败:${escapeHtml(error.message)}
`; + } +} + +function renderRunWorkspace() { + const runs = filteredRuns(); + if (!runs.length) { + return ` +
+ ${icon("shield", "icon icon-xl")} +

当前没有匹配运行

+

请先清空筛选,或者从系统分组页进入某个系统的运行中心。

+
+ ${renderArchitecturePanel()} + `; } + if (!state.selectedRunId || !runs.some((item) => item.run_id === state.selectedRunId)) { + state.selectedRunId = runs[0].run_id; + } + + const run = runs.find((item) => item.run_id === state.selectedRunId); const advisory = run.advisory_meta || {}; const profile = run.profile_meta || {}; const screenshotItems = ((run.artifact_groups || []).find((group) => group.key === "browser")?.items || []).filter((item) => item.kind === "image"); - const segments = progressSegments(run.progress || {}); - const browserStatus = run.browser_evidence?.present ? "已采集" : (run.browser_evidence?.required ? "必需待补" : "可选"); const artifactCount = (run.artifact_groups || []).reduce((sum, group) => sum + Number(group.count || 0), 0); + const browserStatus = run.browser_evidence?.present ? "已采集" : (run.browser_evidence?.required ? "必需待补" : "可选"); + const progress = { + completed: 0, + skipped: 0, + failed: 0, + blocked: 0, + planned: 0, + other: 0 + }; + (run.timeline || []).forEach((item) => { + if (String(item.status || "").startsWith("blocked")) progress.blocked += 1; + else if (Object.prototype.hasOwnProperty.call(progress, item.status || "")) progress[item.status] += 1; + else progress.other += 1; + }); - const timelineContent = ` -
${segments.bar}
-
${segments.legend}
-
- ${(run.timeline || []) - .map((item) => ` -
- -
- ${escapeHtml(item.step || "-")} - ${escapeHtml(formatDateTime(item.at || "-"))} -
-
${escapeHtml(formatStatus(item.status || "unknown"))}
-
${escapeHtml(item.detail || "-")}
-
- `) - .join("") || `
当前运行没有记录时间线。
`} -
- `; + const timelineRows = (run.timeline || []) + .map((item) => ` +
+ +
+ ${escapeHtml(item.step || "-")} + ${escapeHtml(formatDateTime(item.at || "-"))} +
+
${escapeHtml(formatStatus(item.status || "unknown"))}
+
${escapeHtml(item.detail || "-")}
+
+ `) + .join("") || `
当前运行没有记录时间线。
`; const reasoningCards = [ - { - label: "概要", - copy: advisory.summary || "当前漏洞条目没有摘要。" - }, - { - label: "成功判据", - copy: (profile.success_criteria || []).join(" | ") || "当前 profile 没有定义成功判据。" - }, - { - label: "Seed / 攻击思路", - copy: (run.reasoning_lines || []).join("\n\n") || "当前运行没有记录思路说明。" - }, - { - label: "允许目标", - copy: (profile.allowed_target_types || []).join(", ") || "当前 profile 没有声明允许目标类型。" - } + { label: "概要", copy: advisory.summary || "当前漏洞条目没有摘要。" }, + { label: "成功判据", copy: (profile.success_criteria || []).join(" | ") || "当前复现档案没有定义成功判据。" }, + { label: "Seed / 攻击思路", copy: (run.reasoning_lines || []).join("\n\n") || "当前运行没有记录思路说明。" }, + { label: "允许目标", copy: (profile.allowed_target_types || []).join(", ") || "当前复现档案没有声明允许目标类型。" } ]; - const reasoningContent = ` - ${run.blocked_reason ? `
失败原因
${escapeHtml(run.blocked_reason)}
` : ""} -
- 漏洞家族 ${escapeHtml(profile.vuln_family || "未定义")} - 清理策略 ${escapeHtml(profile.cleanup_policy || "-")} - 破坏性风险 ${escapeHtml(profile.destructive_risk || "-")} - 制品模式 ${escapeHtml(run.artifact_mode || "-")} -
-
- ${reasoningCards - .map( - (card) => ` -
- ${escapeHtml(card.label)} -
${escapeHtml(card.copy)}
-
- ` - ) - .join("")} -
- `; - const evidenceContent = `
${(run.artifact_groups || []) @@ -674,40 +979,12 @@ function renderDetail() { ` ) - .join("") || `
当前运行没有可浏览的产物分组。
`} - - ${ - screenshotItems.length - ? `` - : "" - } -
- `; - - const logContent = ` -
-
-
-
${escapeHtml(state.selectedArtifact?.label || "选择一个产物")}
-
${escapeHtml(state.selectedArtifact?.href || "这里会显示 JSON、文本、HTML 报告、截图和其他日志的预览。")}
-
-
- ${icon("link")}打开产物 - -
-
-
选择报告、日志、截图、JSON 或 HTML 产物后,会在这里直接预览。
+ .join("")} + ${screenshotItems.length ? `` : ""}
`; @@ -718,19 +995,11 @@ function renderDetail() { ...(advisory.secondary_source_urls || []).map((url) => `${escapeHtml(url)}`) ].join(""); - const sourcesContent = ` -
- ${(advisory.aliases || []).map((alias) => `${escapeHtml(alias)}`).join("")} - ${(advisory.secure_code_topics || []).map((topic) => `${escapeHtml(topic)}`).join("")} -
- - `; - const rawRunContent = `
${escapeHtml(JSON.stringify(run, null, 2))}
`; const rawAdvisoryContent = `
${escapeHtml(JSON.stringify(advisory, null, 2))}
`; const rawProfileContent = `
${escapeHtml(JSON.stringify(profile, null, 2))}
`; - $("detailWorkspace").innerHTML = ` + return `
${escapeHtml(formatStatus(run.verification_status))} @@ -743,20 +1012,18 @@ function renderDetail() {

${escapeHtml(advisory.title || run.advisory_id)}

${escapeHtml(advisory.summary || "当前漏洞条目没有摘要。")}
- -
时间线步骤 ${escapeHtml(run.timeline?.length || 0)}
- Artifact 数 + 证据数量 ${escapeHtml(artifactCount)}
@@ -769,66 +1036,309 @@ function renderDetail() {
- - ${renderPanel("timeline", "进度时间线", `${escapeHtml(run.timeline?.length || 0)} 步`, "timeline", timelineContent)} - ${renderPanel("reasoning", "攻击方案与推理", escapeHtml(profile.vuln_family || "未定义"), "reasoning", reasoningContent)} + ${renderPanel("timeline", "进度时间线", `${escapeHtml(run.timeline?.length || 0)} 步`, "timeline", ` +
${progressSegments(progress).bar}
+
${progressSegments(progress).legend}
+
${timelineRows}
+ `)} + ${renderPanel("reasoning", "攻击方案与推理", escapeHtml(profile.vuln_family || "未定义"), "reasoning", ` + ${run.blocked_reason ? `
失败原因
${escapeHtml(run.blocked_reason)}
` : ""} +
+ 漏洞家族 ${escapeHtml(profile.vuln_family || "未定义")} + 清理策略 ${escapeHtml(profile.cleanup_policy || "-")} + 破坏性风险 ${escapeHtml(profile.destructive_risk || "-")} + 制品模式 ${escapeHtml(run.artifact_mode || "-")} +
+
+ ${reasoningCards.map((card) => ` +
+ ${escapeHtml(card.label)} +
${escapeHtml(card.copy)}
+
+ `).join("")} +
+ `)} ${renderPanel("evidence", "证据浏览器", `${escapeHtml(run.artifact_groups?.length || 0)} 组`, "evidence", evidenceContent)} - ${renderPanel("logs", "实时日志查看器", state.selectedArtifact ? "已选产物" : "等待选择", "logs", logContent)} - ${renderPanel("sources", "来源与修复主题", `${escapeHtml((advisory.secondary_source_urls || []).length + (advisory.official_source_url ? 1 : 0))} 条链接`, "sources", sourcesContent)} + ${renderPanel("logs", "实时日志查看器", state.selectedArtifact ? "已选产物" : "等待选择", "logs", ` +
+
+
+
${escapeHtml(state.selectedArtifact?.label || "选择一个产物")}
+
${escapeHtml(state.selectedArtifact?.href || "这里会显示 JSON、文本、HTML 报告、截图和其他日志的预览。")}
+
+
+ ${icon("link")}打开产物 + +
+
+
选择报告、日志、截图、JSON 或 HTML 产物后,会在这里直接预览。
+
+ `)} + ${renderPanel("sources", "来源与修复主题", `${escapeHtml((advisory.secondary_source_urls || []).length + (advisory.official_source_url ? 1 : 0))} 条链接`, "sources", ` +
+ ${(advisory.aliases || []).map((alias) => `${escapeHtml(alias)}`).join("")} + ${(advisory.secure_code_topics || []).map((topic) => `${escapeHtml(topic)}`).join("")} +
+ + `)} ${renderArchitecturePanel()} ${renderPanel("run_json", "运行 JSON", "原始数据", "json", rawRunContent)} ${renderPanel("advisory_json", "漏洞条目 JSON", "原始数据", "json", rawAdvisoryContent)} ${renderPanel("profile_json", "复现档案 JSON", "原始数据", "json", rawProfileContent)} `; +} - bindPanelToggles(); +function renderOverviewWorkspace() { + const runs = recentRuns(6); + const systems = filteredSystems().slice(0, 12); + return ` +
+
+
+ 总览 +
+ 分类路由已启用 + 长下拉已移除 + URL 按板块分类 +
+
+

按板块浏览当前工作台

+
根入口保留为概览页,同时新增运行、系统、架构、文档和数据的独立 URL。顶部菜单负责分类切换,搜索与筛选会同步到地址栏。
+
+ ${renderPanel("overview_runs", "最新运行", `${escapeHtml(runs.length)} 条`, "queue", renderRunList(runs, "暂无运行数据。"))} + ${renderPanel("overview_systems", "系统覆盖概览", `${escapeHtml(systems.length)} 个系统`, "systems", `
${renderSystemCards(systems)}
`)} + ${renderArchitecturePanel()} + ${renderPanel("overview_docs", "文档与数据入口", `${escapeHtml(DOC_HUB_ITEMS.length + DATA_HUB_ITEMS.length)} 个入口`, "docs", `${renderHubCards(DOC_HUB_ITEMS.slice(0, 4).concat(DATA_HUB_ITEMS.slice(0, 4)))}`)} +
+ `; +} - document.querySelectorAll("[data-artifact]").forEach((button) => { - button.addEventListener("click", () => openArtifact(button.dataset.href, button.dataset.label, button.dataset.kind)); - }); +function renderSystemsWorkspace() { + const items = filteredSystems(); + return ` +
+
+
+ 系统分组 +
+ 共 ${escapeHtml(items.length)} 个系统 + ${state.filters.category ? `${escapeHtml(formatCategory(state.filters.category))}` : ""} + ${state.filters.system ? `已锁定 ${escapeHtml(state.filters.system)}` : ""} +
+
+

按系统与板块查看覆盖

+
顶部只保留板块 chips,不再使用过长下拉。若要精确到单系统,可在卡片上点“锁定系统”或直接进入该系统的运行中心。
+
+ ${renderPanel("systems_grid", "系统覆盖列表", `${escapeHtml(items.length)} 个系统`, "systems", `
${renderSystemCards(items)}
`)} +
+ `; +} - $("viewerRefresh")?.addEventListener("click", () => { - if (state.selectedArtifact) { - openArtifact(state.selectedArtifact.href, state.selectedArtifact.label, state.selectedArtifact.kind); - } - }); +function renderArchitectureWorkspace() { + return ` +
+
+
+ 架构库 +
+ 控制面 + 数据层 + 授权边界 + 系统覆盖 +
+
+

完整架构树与本地入口

+
这里是当前仓库的结构化真值视图。可以折叠查看任意层级信息,并跳转到对应的本地路由、文档镜像或 JSON。
+
+ ${renderArchitecturePanel()} +
+ `; +} - const artifactExists = (run.artifact_groups || []).some((group) => group.items.some((item) => item.href === state.selectedArtifact?.href)); - const defaultItem = artifactExists ? state.selectedArtifact : defaultArtifact(run); - if (defaultItem) { - openArtifact(defaultItem.href, defaultItem.label, defaultItem.kind); +function renderDocsWorkspace() { + return ` +
+
+
+ 文档中心 +
+ 项目文档 + 设计文档 + 镜像页 +
+
+

文档入口按板块集中

+
不再把所有入口混在首页链接堆里。这里按说明、设计、真值镜像和 secure-code 索引集中展示。
+
+ ${renderPanel("docs_hub", "文档与镜像页", `${escapeHtml(DOC_HUB_ITEMS.length)} 个入口`, "docs", renderHubCards(DOC_HUB_ITEMS))} +
+ `; +} + +function renderDataWorkspace() { + return ` +
+
+
+ 数据中心 +
+ JSON 真值 + 生成产物 + 本地直链 +
+
+

数据入口按类型集中

+
summary、runs、systems、advisories、profiles、architecture 已单独归入数据中心,避免和文档、运行详情混在一个地址里。
+
+ ${renderPanel("data_hub", "JSON 与生成数据", `${escapeHtml(DATA_HUB_ITEMS.length)} 个入口`, "json", renderHubCards(DATA_HUB_ITEMS))} +
+ `; +} + +function progressSegments(progress) { + const order = [ + ["completed", "已完成", "progress-completed"], + ["blocked", "已阻塞", "progress-blocked"], + ["failed", "失败", "progress-failed"], + ["skipped", "已跳过", "progress-skipped"], + ["planned", "已规划", "progress-planned"], + ["other", "其他", "progress-other"] + ]; + const total = order.reduce((sum, [key]) => sum + Number(progress?.[key] || 0), 0); + if (!total) { + return { + bar: `
`, + legend: `暂无进度` + }; } + const bar = order + .filter(([key]) => Number(progress?.[key] || 0) > 0) + .map(([key, _label, className]) => { + const pct = Math.max((Number(progress[key] || 0) / total) * 100, 4); + return ``; + }) + .join(""); + const legend = order + .filter(([key]) => Number(progress?.[key] || 0) > 0) + .map(([key, label, className]) => `${escapeHtml(label)} ${escapeHtml(progress[key] || 0)}`) + .join(""); + return { bar, legend }; +} + +function timelineTone(status) { + if (status === "completed" || status === "verified-real" || status === "verified-synthetic") return "timeline-success"; + if (String(status || "").startsWith("blocked") || status === "failed") return "timeline-blocked"; + if (status === "planned") return "timeline-pending"; + return "timeline-neutral"; +} + +function renderWorkspace() { + const workspace = $("detailWorkspace"); + let html = ""; + if (state.routeSection === "runs") html = renderRunWorkspace(); + else if (state.routeSection === "systems") html = renderSystemsWorkspace(); + else if (state.routeSection === "architecture") html = renderArchitectureWorkspace(); + else if (state.routeSection === "docs") html = renderDocsWorkspace(); + else if (state.routeSection === "data") html = renderDataWorkspace(); + else html = renderOverviewWorkspace(); + workspace.innerHTML = html; } function renderAll() { renderMetrics(); - renderSystems(); - renderRecentFailures(); - renderRunQueue(); - renderDetail(); + renderSectionNav(); + renderTopMenus(); + renderSidebar(); + renderWorkspace(); + updateUrl(); +} + +function setFilter(key, value) { + if (!Object.prototype.hasOwnProperty.call(state.filters, key)) return; + state.filters[key] = value || ""; + if (key !== "search") { + if (state.routeSection === "runs" && state.selectedRunId && !filteredRuns().some((item) => item.run_id === state.selectedRunId)) { + state.selectedRunId = filteredRuns()[0]?.run_id || null; + } + } + renderAll(); +} + +function clearFilters() { + state.filters = { + search: "", + status: "", + category: "", + family: "", + system: "" + }; + state.selectedRunId = state.routeSection === "runs" ? filteredRuns()[0]?.run_id || null : state.selectedRunId; + renderAll(); } function attachGlobalEvents() { - $("searchInput").addEventListener("input", (event) => { - state.filters.search = String(event.target.value || "").trim().toLowerCase(); - renderRunQueue(); + document.addEventListener("click", (event) => { + const toggle = event.target.closest("[data-panel-toggle]"); + if (toggle) { + const key = toggle.dataset.panelToggle; + state.panels[key] = !(state.panels[key] !== false); + const panel = document.querySelector(`[data-panel="${key}"]`); + if (panel) panel.classList.toggle("is-collapsed", state.panels[key] === false); + return; + } + + const refreshButton = event.target.closest("#refreshDashboard"); + if (refreshButton) { + loadData(false); + return; + } + + const viewerRefresh = event.target.closest("#viewerRefresh"); + if (viewerRefresh && state.selectedArtifact) { + openArtifact(state.selectedArtifact.href, state.selectedArtifact.label, state.selectedArtifact.kind); + return; + } + + const runButton = event.target.closest("[data-run-id]"); + if (runButton) { + const runId = runButton.dataset.runId; + if (state.routeSection !== "runs") { + navigateToSection("runs", { run: runId }); + return; + } + state.selectedRunId = runId; + renderAll(); + return; + } + + const filterButton = event.target.closest("[data-filter-key]"); + if (filterButton) { + setFilter(filterButton.dataset.filterKey, filterButton.dataset.filterValue || ""); + return; + } + + const clearButton = event.target.closest("[data-clear-filters]"); + if (clearButton) { + clearFilters(); + return; + } + + const artifactButton = event.target.closest("[data-artifact]"); + if (artifactButton) { + openArtifact(artifactButton.dataset.href, artifactButton.dataset.label, artifactButton.dataset.kind); + } }); - [ - ["systemFilter", "system"], - ["statusFilter", "status"], - ["profileFilter", "profile"] - ].forEach(([id, key]) => { - $(id).addEventListener("input", (event) => { - state.filters[key] = String(event.target.value || ""); - renderRunQueue(); - }); - }); + document.addEventListener("input", (event) => { + if (event.target.id === "searchInput") { + state.filters.search = String(event.target.value || "").trim().toLowerCase(); + renderAll(); + } - $("refreshDashboard").addEventListener("click", () => loadData(false)); - $("autoRefresh").addEventListener("change", (event) => { - state.autoRefresh = Boolean(event.target.checked); - startRefreshLoop(); + if (event.target.id === "autoRefresh") { + state.autoRefresh = Boolean(event.target.checked); + startRefreshLoop(); + } }); } @@ -842,17 +1352,17 @@ function startRefreshLoop() { } async function loadData(preserveSelection = true) { - const previous = state.selectedRunId; + const previousRunId = state.selectedRunId; renderSyncState("loading", "刷新中", `本地时间 ${new Date().toLocaleTimeString("zh-CN", { hour12: false })}`); try { const [summary, runs, systems, advisories, profiles, architecture] = await Promise.all([ - fetchJson("./summary.json"), - fetchJson("./runs.json"), - fetchJson("./systems.json"), - fetchJson("./advisories.json"), - fetchJson("./profiles.json"), - fetchJson("./architecture.json") + fetchJson("/summary.json"), + fetchJson("/runs.json"), + fetchJson("/systems.json"), + fetchJson("/advisories.json"), + fetchJson("/profiles.json"), + fetchJson("/architecture.json") ]); state.summary = summary; @@ -861,30 +1371,29 @@ async function loadData(preserveSelection = true) { state.advisories = advisories; state.profiles = profiles; state.architecture = architecture; - hydrateFilters(); - const hashRun = location.hash.startsWith("#run=") ? location.hash.replace("#run=", "") : null; - const candidate = preserveSelection ? (hashRun || previous) : hashRun; - if (candidate && runs.some((item) => item.run_id === candidate)) { + const filtered = filteredRuns(); + const candidate = preserveSelection ? (state.selectedRunId || previousRunId) : state.selectedRunId; + if (candidate && filtered.some((item) => item.run_id === candidate)) { state.selectedRunId = candidate; } else { - state.selectedRunId = runs[0]?.run_id || null; + state.selectedRunId = filtered[0]?.run_id || null; } renderAll(); renderSyncState("live", "实时同步", `最近生成 ${formatDateTime(summary.generated_at || new Date().toISOString())}`); } catch (error) { - $("runQueue").innerHTML = `
工作台加载失败:${escapeHtml(error.message)}
`; + $("sidebar").innerHTML = ``; $("detailWorkspace").innerHTML = `

加载失败

${escapeHtml(error.message)}

`; renderSyncState("error", "加载失败", error.message); } } async function init() { + applyUrlState(); attachGlobalEvents(); await loadData(false); startRefreshLoop(); - window.addEventListener("hashchange", () => loadData(false)); } document.addEventListener("DOMContentLoaded", init); diff --git a/08-threat-intel/generated/dashboard/assets/styles.css b/08-threat-intel/generated/dashboard/assets/styles.css index 893eafb6..02d70725 100644 --- a/08-threat-intel/generated/dashboard/assets/styles.css +++ b/08-threat-intel/generated/dashboard/assets/styles.css @@ -289,6 +289,157 @@ select { margin-top: 22px; } +.section-nav, +.top-menus { + position: relative; + margin-top: 18px; +} + +.section-nav { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 12px; +} + +.nav-pill { + display: grid; + gap: 8px; + padding: 14px 16px; + border-radius: 18px; + border: 1px solid var(--border-color); + background: rgba(255, 255, 255, 0.04); + transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease; +} + +.nav-pill:hover, +.nav-pill.is-active { + transform: translateY(-1px); + border-color: rgba(77, 141, 255, 0.42); + background: rgba(77, 141, 255, 0.1); +} + +.nav-pill-top { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.nav-pill-top strong { + font-size: 0.95rem; +} + +.nav-pill-copy { + color: var(--text-secondary); + font-size: 0.82rem; + line-height: 1.45; +} + +.top-menus { + display: grid; + gap: 12px; +} + +.route-note, +.menu-row, +.hub-card, +.hub-card-static { + border: 1px solid var(--border-color); + border-radius: 16px; + background: rgba(255, 255, 255, 0.04); +} + +.route-note, +.menu-row { + padding: 14px 16px; +} + +.route-note { + display: flex; + justify-content: space-between; + gap: 16px; + align-items: flex-start; +} + +.route-note strong { + display: block; + font-size: 1rem; +} + +.route-note span { + display: block; + margin-top: 6px; + color: var(--text-secondary); + line-height: 1.5; +} + +.menu-row { + display: grid; + gap: 12px; +} + +.menu-row-compact { + padding-top: 12px; + padding-bottom: 12px; +} + +.menu-row-head { + display: flex; + justify-content: space-between; + gap: 12px; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 0.74rem; +} + +.chip-strip { + display: flex; + gap: 10px; + overflow-x: auto; + padding-bottom: 4px; +} + +.chip-strip::-webkit-scrollbar { + height: 6px; +} + +.chip-strip::-webkit-scrollbar-thumb { + background: rgba(148, 163, 184, 0.3); + border-radius: 999px; +} + +.menu-chip { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + min-height: 34px; + padding: 8px 14px; + border-radius: 999px; + border: 1px solid var(--border-color); + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); + cursor: pointer; + white-space: nowrap; + transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease; +} + +.menu-chip:hover, +.menu-chip.is-active, +.menu-chip-link:hover { + transform: translateY(-1px); + border-color: rgba(77, 141, 255, 0.42); + background: rgba(77, 141, 255, 0.1); +} + +.menu-chip-muted { + color: var(--text-secondary); +} + +.menu-chip-link { + text-decoration: none; +} + .metric-card { position: relative; padding: 16px 18px; @@ -502,6 +653,10 @@ select { padding-right: 4px; } +.run-list-compact { + max-height: 560px; +} + .run-card { cursor: pointer; transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease; @@ -582,6 +737,35 @@ select { min-width: 0; } +.workspace-stack, +.system-grid, +.hub-grid { + display: grid; + gap: 16px; +} + +.system-grid { + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); +} + +.hub-grid { + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); +} + +.hub-card, +.hub-card-static { + display: grid; + gap: 10px; + padding: 14px 16px; + transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease; +} + +.hub-card:hover { + transform: translateY(-1px); + border-color: rgba(77, 141, 255, 0.42); + background: rgba(77, 141, 255, 0.08); +} + .workspace-empty { display: grid; place-items: center; @@ -619,6 +803,12 @@ select { margin-top: 18px; } +.button-small { + min-height: 36px; + padding: 8px 12px; + font-size: 0.84rem; +} + .detail-stat-grid, .plan-grid, .raw-json-grid { @@ -918,6 +1108,10 @@ select { word-break: break-all; } +.system-card-compact .detail-actions { + margin-top: 10px; +} + .viewer-frame { min-height: 320px; border: 1px solid rgba(148, 163, 184, 0.16); @@ -1181,6 +1375,7 @@ select { @media (max-width: 1320px) { .hero-top, .main-container, + .section-nav, .detail-stat-grid, .plan-grid, .raw-json-grid { @@ -1211,6 +1406,7 @@ select { } .hero-links, + .route-note, .detail-actions, .tag-row, .panel-meta, diff --git a/08-threat-intel/generated/dashboard/data/index.html b/08-threat-intel/generated/dashboard/data/index.html new file mode 100644 index 00000000..be57391b --- /dev/null +++ b/08-threat-intel/generated/dashboard/data/index.html @@ -0,0 +1,104 @@ + + + + + + 授权攻防实验工作台 + + + + +
+
+ + + +
+
+
+ + 授权攻防实验工作台 +
+

本地攻防实证工作台

+

+ Lovart 设计外壳已本地化并接入真实 run bundle 数据。页面只面向授权实验资产, + 聚合漏洞条目、时间线、证据、日志、来源、原始 JSON、当前架构库与失败原因。 +

+
+ +
+ + +
+ +
+ 启动中 + 正在载入本地生成数据 +
+
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +

选择一个运行

+

从左侧队列选择 run,即可查看时间线、证据、日志、来源、原始 JSON 和当前架构库。

+
+
+
+ + +
+ + + + diff --git a/08-threat-intel/generated/dashboard/docs/architecture-library.html b/08-threat-intel/generated/dashboard/docs/architecture-library.html index 2e98b419..8a0f75cc 100644 --- a/08-threat-intel/generated/dashboard/docs/architecture-library.html +++ b/08-threat-intel/generated/dashboard/docs/architecture-library.html @@ -82,12 +82,12 @@

当前架构库镜像

工作台内置镜像页:当前架构库结构化数据镜像。
{
-  "generated_at": "2026-03-17T09:03:14+00:00",
+  "generated_at": "2026-03-17T09:27:20+00:00",
   "title": "当前架构库",
   "summary": "工作台、控制面、数据层、授权边界与系统覆盖的当前真值视图。",
   "sections": [
@@ -137,29 +137,29 @@
         },
         {
           "label": "生成时间",
-          "value": "2026-03-17T09:03:14+00:00"
+          "value": "2026-03-17T09:27:20+00:00"
         }
       ],
       "links": [
         {
-          "label": "正式工作台",
-          "href": "./index.html",
-          "description": "本地化主 UI,默认入口。"
+          "label": "总览首页",
+          "href": "/overview/index.html",
+          "description": "工作台总览、最新运行和全局摘要。"
         },
         {
-          "label": "旧版工作台",
-          "href": "./legacy/index.html",
-          "description": "保留的 legacy 回退入口。"
+          "label": "运行中心",
+          "href": "/runs/index.html",
+          "description": "运行队列、详情、证据和日志入口。"
         },
         {
-          "label": "项目功能文档",
-          "href": "./docs/project-features.html",
-          "description": "项目能力、目录结构与自动化链路总览。"
+          "label": "系统分组",
+          "href": "/systems/index.html",
+          "description": "按系统和分类浏览覆盖情况。"
         },
         {
-          "label": "前端设计文档",
-          "href": "./docs/frontend-dashboard-design.html",
-          "description": "当前本地工作台的交互与视觉规范。"
+          "label": "架构库",
+          "href": "/architecture/index.html",
+          "description": "查看控制面、数据层和授权边界。"
         }
       ]
     },
@@ -287,93 +287,118 @@
           "open": false,
           "links": [
             {
-              "label": "正式工作台",
-              "href": "./index.html",
-              "description": "本地化主 UI,默认入口。"
+              "label": "总览首页",
+              "href": "/overview/index.html",
+              "description": "工作台总览、最新运行和全局摘要。"
+            },
+            {
+              "label": "运行中心",
+              "href": "/runs/index.html",
+              "description": "运行队列、详情、证据和日志入口。"
+            },
+            {
+              "label": "系统分组",
+              "href": "/systems/index.html",
+              "description": "按系统和分类浏览覆盖情况。"
+            },
+            {
+              "label": "架构库",
+              "href": "/architecture/index.html",
+              "description": "查看控制面、数据层和授权边界。"
+            },
+            {
+              "label": "文档中心",
+              "href": "/docs/index.html",
+              "description": "集中查看项目文档、本地镜像和说明。"
+            },
+            {
+              "label": "数据中心",
+              "href": "/data/index.html",
+              "description": "查看 summary、runs、systems 等 JSON 入口。"
             },
             {
               "label": "旧版工作台",
-              "href": "./legacy/index.html",
+              "href": "/legacy/index.html",
               "description": "保留的 legacy 回退入口。"
             },
             {
               "label": "项目功能文档",
-              "href": "./docs/project-features.html",
+              "href": "/docs/project-features.html",
               "description": "项目能力、目录结构与自动化链路总览。"
             },
             {
               "label": "前端设计文档",
-              "href": "./docs/frontend-dashboard-design.html",
+              "href": "/docs/frontend-dashboard-design.html",
               "description": "当前本地工作台的交互与视觉规范。"
             },
             {
               "label": "安全编码索引",
-              "href": "./docs/secure-code-index.html",
+              "href": "/docs/secure-code-index.html",
               "description": "secure-code 修复库本地镜像。"
             },
             {
               "label": "仓库入口镜像",
-              "href": "./docs/root-readme.html",
+              "href": "/docs/root-readme.html",
               "description": "仓库根 README 的本地镜像。"
             },
             {
               "label": "授权模型",
-              "href": "./docs/authorization-model.html",
+              "href": "/docs/authorization-model.html",
               "description": "允许目标范围、全局原则与记录要求。"
             },
             {
               "label": "source-map 真值",
-              "href": "./docs/source-map.html",
+              "href": "/docs/source-map.html",
               "description": "系统覆盖、来源和输出目录真值。"
             },
             {
               "label": "repro-map 真值",
-              "href": "./docs/repro-map.html",
+              "href": "/docs/repro-map.html",
               "description": "复现族路由、浏览器要求和日志策略。"
             },
             {
               "label": "覆盖矩阵",
-              "href": "./docs/coverage-matrix.html",
+              "href": "/docs/coverage-matrix.html",
               "description": "自动生成覆盖摘要的本地镜像。"
             },
             {
               "label": "设计来源清单",
-              "href": "./docs/design-source.html",
+              "href": "/docs/design-source.html",
               "description": "Lovart 模板本地 vendor manifest。"
             },
             {
               "label": "架构库镜像",
-              "href": "./docs/architecture-library.html",
+              "href": "/docs/architecture-library.html",
               "description": "当前架构库的结构化镜像页。"
             },
             {
               "label": "summary.json",
-              "href": "./summary.json",
+              "href": "/summary.json",
               "description": "全局摘要、状态分布和最近失败。"
             },
             {
               "label": "runs.json",
-              "href": "./runs.json",
+              "href": "/runs.json",
               "description": "最近 run 的结构化详情。"
             },
             {
               "label": "systems.json",
-              "href": "./systems.json",
+              "href": "/systems.json",
               "description": "系统级覆盖与浏览器证据摘要。"
             },
             {
               "label": "advisories.json",
-              "href": "./advisories.json",
-              "description": "advisory 元数据与来源。"
+              "href": "/advisories.json",
+              "description": "漏洞条目元数据与来源。"
             },
             {
               "label": "profiles.json",
-              "href": "./profiles.json",
-              "description": "repro profile 元数据。"
+              "href": "/profiles.json",
+              "description": "复现档案元数据。"
             },
             {
               "label": "architecture.json",
-              "href": "./architecture.json",
+              "href": "/architecture.json",
               "description": "当前架构库结构化 JSON。"
             }
           ],
@@ -387,9 +412,33 @@
               "value": "06-case-studies/generated-runs/<run-id>/"
             },
             {
-              "label": "工作台入口",
+              "label": "默认入口",
               "value": "/index.html"
             },
+            {
+              "label": "总览入口",
+              "value": "/overview/index.html"
+            },
+            {
+              "label": "运行入口",
+              "value": "/runs/index.html"
+            },
+            {
+              "label": "系统入口",
+              "value": "/systems/index.html"
+            },
+            {
+              "label": "架构入口",
+              "value": "/architecture/index.html"
+            },
+            {
+              "label": "文档入口",
+              "value": "/docs/index.html"
+            },
+            {
+              "label": "数据入口",
+              "value": "/data/index.html"
+            },
             {
               "label": "旧版入口",
               "value": "/legacy/index.html"
diff --git a/08-threat-intel/generated/dashboard/docs/authorization-model.html b/08-threat-intel/generated/dashboard/docs/authorization-model.html
index 1861e538..d4733b21 100644
--- a/08-threat-intel/generated/dashboard/docs/authorization-model.html
+++ b/08-threat-intel/generated/dashboard/docs/authorization-model.html
@@ -82,7 +82,7 @@
   

授权模型镜像

工作台内置镜像页:目标范围、授权模型、最小化验证建议和记录要求。
diff --git a/08-threat-intel/generated/dashboard/docs/coverage-matrix.html b/08-threat-intel/generated/dashboard/docs/coverage-matrix.html index ea65eb36..27f15375 100644 --- a/08-threat-intel/generated/dashboard/docs/coverage-matrix.html +++ b/08-threat-intel/generated/dashboard/docs/coverage-matrix.html @@ -82,7 +82,7 @@

覆盖矩阵镜像

工作台内置镜像页:当前覆盖矩阵生成结果。
diff --git a/08-threat-intel/generated/dashboard/docs/design-source.html b/08-threat-intel/generated/dashboard/docs/design-source.html index 0b2dc878..5cf2bb34 100644 --- a/08-threat-intel/generated/dashboard/docs/design-source.html +++ b/08-threat-intel/generated/dashboard/docs/design-source.html @@ -82,7 +82,7 @@

Lovart 设计来源与本地化清单

工作台内置镜像页:Lovart 来源文件、本地 vendor 路径和本地化说明。
diff --git a/08-threat-intel/generated/dashboard/docs/frontend-dashboard-design.html b/08-threat-intel/generated/dashboard/docs/frontend-dashboard-design.html index 8206d285..850d386b 100644 --- a/08-threat-intel/generated/dashboard/docs/frontend-dashboard-design.html +++ b/08-threat-intel/generated/dashboard/docs/frontend-dashboard-design.html @@ -82,7 +82,7 @@

本地前端工作台设计文档

工作台内置镜像页:前端交互、展示结构和视觉规范。
@@ -108,7 +108,7 @@ ### 2.1 页面名称 -- 页面名称:`Authorized Lab Dashboard` +- 页面名称:`授权攻防实验工作台` - 页面语境:本地静态前端 + 本地文件 JSON 数据源 - 非目标:在线 SaaS、多用户后端、生产管理台 @@ -119,7 +119,7 @@ - 信息密度高,但必须可折叠、可筛选、可逐层展开 - 日志与原始 JSON 必须能直接预览 - 页面视觉应更生动,但不能牺牲扫描效率 -- 默认路由采用正式新 UI,同时保留 `legacy` 回退入口 +- 默认路由采用分板块 URL,同时保留 `legacy` 回退入口 - 运行期不得依赖外部 HTML、字体 CDN 或图标 CDN ## 3. 信息架构 @@ -127,7 +127,8 @@ ```mermaid flowchart LR A["Hero + Global Status"] --> B["Sidebar Filters"] - A --> C["Run Queue List"] + A --> B1["Top Section Nav"] + B1 --> C["Overview / Runs / Systems / Architecture / Docs / Data"] C --> D["Run Detail Hero"] D --> E["Progress Timeline"] D --> F["Attack Plan & Reasoning"] @@ -150,6 +151,8 @@ flowchart LR - 自动刷新开关 - 当前同步状态 - 核心 metric cards +- 顶部板块菜单 +- 顶部 chips 分类筛选 视觉要求: @@ -159,24 +162,22 @@ flowchart LR ### 4.2 左侧侧栏 -包含四块: +改为按板块变化,不再固定使用长下拉: -- Filters +- Overview - 搜索 - - system filter - - status filter - - profile filter + - 最近失败 + - 最新运行 + - 系统概览 +- Runs + - 搜索 + - 最近失败 + - 运行队列 - Systems - - 系统覆盖度 - - browser evidence 覆盖 - - latest update -- Recent Failures - - 最近 blocker - - status - - 原因摘要 -- Run Queue View - - 最近 run 卡片列表 - - 可选中并切换到 detail panel + - 搜索 + - 系统目录 +- Architecture / Docs / Data + - 对应目录、入口或结构列表 ### 4.3 右侧 Detail Workspace @@ -241,7 +242,7 @@ flowchart LR - 点击左侧 run card 后,右侧 detail panel 即时刷新 - 当前选中项要有强视觉区别 -- URL hash 应保留 `#run=<id>`,方便直接打开特定 run +- URL 应按板块进入不同入口,并通过 query 参数保留筛选与 `run=<id>` ### 5.3 Artifact 预览 @@ -387,7 +388,19 @@ flowchart LR ## 10. 路由与文档地址 - `/index.html` - - 默认正式入口,使用本地化 Lovart UI 外壳 + - 根入口别名 +- `/overview/index.html` + - 总览入口 +- `/runs/index.html` + - 运行中心 +- `/systems/index.html` + - 系统中心 +- `/architecture/index.html` + - 架构中心 +- `/docs/index.html` + - 文档中心 +- `/data/index.html` + - 数据中心 - `/legacy/index.html` - 旧版 dashboard 回退入口 - `/docs/project-features.html` @@ -415,7 +428,8 @@ flowchart LR - 能折叠与展开各信息区 - 能打开并预览 JSON / text / image / html artifact - 能看到失败原因、思路、来源、修复主题 -- 能筛选 system / status / profile +- 能通过顶部 chips 筛选状态 / 板块 / 漏洞家族 +- 能通过分板块 URL 直接打开 overview / runs / systems / architecture / docs / data - 能在自动刷新开启时重新载入 dashboard 数据 - 页面视觉比“普通表格页”更生动,但仍适合高密度阅读
diff --git a/08-threat-intel/generated/dashboard/docs/index.html b/08-threat-intel/generated/dashboard/docs/index.html new file mode 100644 index 00000000..be57391b --- /dev/null +++ b/08-threat-intel/generated/dashboard/docs/index.html @@ -0,0 +1,104 @@ + + + + + + 授权攻防实验工作台 + + + + +
+
+ + + +
+
+
+ + 授权攻防实验工作台 +
+

本地攻防实证工作台

+

+ Lovart 设计外壳已本地化并接入真实 run bundle 数据。页面只面向授权实验资产, + 聚合漏洞条目、时间线、证据、日志、来源、原始 JSON、当前架构库与失败原因。 +

+
+ +
+ + +
+ +
+ 启动中 + 正在载入本地生成数据 +
+
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +

选择一个运行

+

从左侧队列选择 run,即可查看时间线、证据、日志、来源、原始 JSON 和当前架构库。

+
+
+
+ + +
+ + + + diff --git a/08-threat-intel/generated/dashboard/docs/project-features.html b/08-threat-intel/generated/dashboard/docs/project-features.html index 927b0ca4..628151ea 100644 --- a/08-threat-intel/generated/dashboard/docs/project-features.html +++ b/08-threat-intel/generated/dashboard/docs/project-features.html @@ -82,7 +82,7 @@

项目功能与特性总览

工作台内置镜像页:仓库功能、目录和自动化链路说明。
@@ -149,7 +149,8 @@ - `report.md`, `report.html`, `timeline.mmd`, `assets/`, `logs/` - `08-threat-intel/generated/dashboard/` - 静态前端工作台 - - `/index.html` 为本地化 Lovart 正式 UI + - `/index.html` 为根入口别名 + - `/overview/index.html`, `/runs/index.html`, `/systems/index.html`, `/architecture/index.html`, `/docs/index.html`, `/data/index.html` 为分类入口 - `/legacy/index.html` 为旧版工作台回退入口 - `/docs/*.html` 为本地可访问的说明、真值配置与设计镜像页 - `architecture.json` 为当前架构库结构化真值 @@ -244,6 +245,8 @@ python3 /Users/x/websafe/scripts/lab/main.py serve-dashboard --port 8734 前端不只是“一个结果页”,而是本地实验控制台与证据阅读器。它需要: - 快速定位系统 / advisory / repro profile +- 通过顶部板块菜单切换总览、运行、系统、架构、文档和数据中心 +- 使用顶部 chips 做状态 / 板块 / 漏洞家族筛选,不再依赖过长下拉 - 折叠与展开 timeline、evidence、sources、raw JSON - 折叠与展开“当前架构库”,查看控制面、数据层、地址入口、授权边界和系统分组 - 直接查看 compose、JSON、日志、截图、报告 @@ -257,7 +260,19 @@ python3 /Users/x/websafe/scripts/lab/main.py serve-dashboard --port 8734 当前地址布局固定为: - `/index.html` - - 默认新 UI,基于本地化 Lovart 视觉壳层,绑定真实 dashboard JSON + - 根入口别名,默认进入概览 +- `/overview/index.html` + - 总览入口 +- `/runs/index.html` + - 运行中心 +- `/systems/index.html` + - 系统中心 +- `/architecture/index.html` + - 架构中心 +- `/docs/index.html` + - 文档中心 +- `/data/index.html` + - 数据中心 - `/legacy/index.html` - 旧版工作台显式保留,用于快速回退和对照 - `/docs/design-source.html` diff --git a/08-threat-intel/generated/dashboard/docs/repro-map.html b/08-threat-intel/generated/dashboard/docs/repro-map.html index 66f0a0f4..41292d8e 100644 --- a/08-threat-intel/generated/dashboard/docs/repro-map.html +++ b/08-threat-intel/generated/dashboard/docs/repro-map.html @@ -82,7 +82,7 @@

repro-map 真值镜像

工作台内置镜像页:默认漏洞家族、浏览器要求和日志策略真值。
diff --git a/08-threat-intel/generated/dashboard/docs/root-readme.html b/08-threat-intel/generated/dashboard/docs/root-readme.html index 85b3418b..861b33d0 100644 --- a/08-threat-intel/generated/dashboard/docs/root-readme.html +++ b/08-threat-intel/generated/dashboard/docs/root-readme.html @@ -82,7 +82,7 @@

仓库入口镜像

工作台内置镜像页:仓库定位、能力矩阵、入口和自动化入口。
@@ -163,7 +163,19 @@ python3 /Users/x/websafe/scripts/lab/main.py serve-dashboard --port 8734 本地 dashboard 路由: - `/index.html` - - 默认正式 UI,使用本地化 Lovart 视觉壳层 + - 根入口别名,默认进入概览视图 +- `/overview/index.html` + - 总览入口,显示全局指标、最新运行、系统摘要与架构预览 +- `/runs/index.html` + - 运行中心,显示运行队列、详情、证据、日志与原始 JSON +- `/systems/index.html` + - 系统中心,按板块查看系统覆盖并跳转到对应运行 +- `/architecture/index.html` + - 架构中心,折叠查看控制面、数据层、授权边界与本地入口 +- `/docs/index.html` + - 文档中心,集中访问功能文档、设计文档和镜像页 +- `/data/index.html` + - 数据中心,集中访问 summary、runs、systems、profiles 等 JSON - `/legacy/index.html` - 旧版工作台回退入口 - `/docs/design-source.html` diff --git a/08-threat-intel/generated/dashboard/docs/secure-code-index.html b/08-threat-intel/generated/dashboard/docs/secure-code-index.html index 71377da6..780fd924 100644 --- a/08-threat-intel/generated/dashboard/docs/secure-code-index.html +++ b/08-threat-intel/generated/dashboard/docs/secure-code-index.html @@ -82,7 +82,7 @@

安全编码修复库索引

工作台内置镜像页:secure-code 修复主题索引。
diff --git a/08-threat-intel/generated/dashboard/docs/source-map.html b/08-threat-intel/generated/dashboard/docs/source-map.html index 888c1f85..6509033a 100644 --- a/08-threat-intel/generated/dashboard/docs/source-map.html +++ b/08-threat-intel/generated/dashboard/docs/source-map.html @@ -82,7 +82,7 @@

source-map 真值镜像

工作台内置镜像页:系统覆盖、来源、输出目录和 secure-code 主题真值。
diff --git a/08-threat-intel/generated/dashboard/index.html b/08-threat-intel/generated/dashboard/index.html index 8d3c95b3..be57391b 100644 --- a/08-threat-intel/generated/dashboard/index.html +++ b/08-threat-intel/generated/dashboard/index.html @@ -4,7 +4,7 @@ 授权攻防实验工作台 - + @@ -16,7 +16,7 @@
- + 授权攻防实验工作台

本地攻防实证工作台

@@ -28,7 +28,7 @@
- +
启动中 正在载入本地生成数据
@@ -67,85 +67,16 @@
+ +
- +
- +

选择一个运行

从左侧队列选择 run,即可查看时间线、证据、日志、来源、原始 JSON 和当前架构库。

@@ -155,19 +86,19 @@
- + diff --git a/08-threat-intel/generated/dashboard/overview/index.html b/08-threat-intel/generated/dashboard/overview/index.html new file mode 100644 index 00000000..be57391b --- /dev/null +++ b/08-threat-intel/generated/dashboard/overview/index.html @@ -0,0 +1,104 @@ + + + + + + 授权攻防实验工作台 + + + + +
+
+ + + +
+
+
+ + 授权攻防实验工作台 +
+

本地攻防实证工作台

+

+ Lovart 设计外壳已本地化并接入真实 run bundle 数据。页面只面向授权实验资产, + 聚合漏洞条目、时间线、证据、日志、来源、原始 JSON、当前架构库与失败原因。 +

+
+ +
+ + +
+ +
+ 启动中 + 正在载入本地生成数据 +
+
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +

选择一个运行

+

从左侧队列选择 run,即可查看时间线、证据、日志、来源、原始 JSON 和当前架构库。

+
+
+
+ + +
+ + + + diff --git a/08-threat-intel/generated/dashboard/runs.json b/08-threat-intel/generated/dashboard/runs.json index 19f51a90..3950ec9c 100644 --- a/08-threat-intel/generated/dashboard/runs.json +++ b/08-threat-intel/generated/dashboard/runs.json @@ -94,10 +94,10 @@ "timeline": "/Users/x/websafe/06-case-studies/generated-runs/gitea-livecheck-20260316/timeline.mmd" }, "dashboard_refs": { - "report_html": "./runs/gitea-livecheck-20260316/report.html", - "report_md": "./runs/gitea-livecheck-20260316/report.md", - "timeline": "./runs/gitea-livecheck-20260316/timeline.mmd", - "bundle": "./runs/gitea-livecheck-20260316/run.json" + "report_html": "/runs/gitea-livecheck-20260316/report.html", + "report_md": "/runs/gitea-livecheck-20260316/report.md", + "timeline": "/runs/gitea-livecheck-20260316/timeline.mmd", + "bundle": "/runs/gitea-livecheck-20260316/run.json" }, "browser_links": [], "container_links": [], @@ -201,22 +201,22 @@ "count": 4, "items": [ { - "href": "./runs/gitea-livecheck-20260316/report.html", + "href": "/runs/gitea-livecheck-20260316/report.html", "label": "report.html", "kind": "text" }, { - "href": "./runs/gitea-livecheck-20260316/report.md", + "href": "/runs/gitea-livecheck-20260316/report.md", "label": "report.md", "kind": "text" }, { - "href": "./runs/gitea-livecheck-20260316/timeline.mmd", + "href": "/runs/gitea-livecheck-20260316/timeline.mmd", "label": "timeline.mmd", "kind": "text" }, { - "href": "./runs/gitea-livecheck-20260316/run.json", + "href": "/runs/gitea-livecheck-20260316/run.json", "label": "run.json", "kind": "text" } @@ -228,7 +228,7 @@ "count": 1, "items": [ { - "href": "./runs/gitea-livecheck-20260316/compose/compose.yaml", + "href": "/runs/gitea-livecheck-20260316/compose/compose.yaml", "label": "compose.yaml", "kind": "text" } @@ -267,10 +267,10 @@ "timeline": "/Users/x/websafe/06-case-studies/generated-runs/gitea-gitea--CVE-2025-68939-20260317063330/timeline.mmd" }, "dashboard_refs": { - "report_html": "./runs/gitea-gitea--CVE-2025-68939-20260317063330/report.html", - "report_md": "./runs/gitea-gitea--CVE-2025-68939-20260317063330/report.md", - "timeline": "./runs/gitea-gitea--CVE-2025-68939-20260317063330/timeline.mmd", - "bundle": "./runs/gitea-gitea--CVE-2025-68939-20260317063330/run.json" + "report_html": "/runs/gitea-gitea--CVE-2025-68939-20260317063330/report.html", + "report_md": "/runs/gitea-gitea--CVE-2025-68939-20260317063330/report.md", + "timeline": "/runs/gitea-gitea--CVE-2025-68939-20260317063330/timeline.mmd", + "bundle": "/runs/gitea-gitea--CVE-2025-68939-20260317063330/run.json" }, "browser_evidence": { "required": false, @@ -280,8 +280,8 @@ "browser_links": [], "container_links": [], "request_links": [ - "./runs/gitea-gitea--CVE-2025-68939-20260317063330/logs/attack.json", - "./runs/gitea-gitea--CVE-2025-68939-20260317063330/logs/baseline.json" + "/runs/gitea-gitea--CVE-2025-68939-20260317063330/logs/attack.json", + "/runs/gitea-gitea--CVE-2025-68939-20260317063330/logs/baseline.json" ], "advisory_meta": { "canonical_id": "gitea--CVE-2025-68939", @@ -382,22 +382,22 @@ "count": 4, "items": [ { - "href": "./runs/gitea-gitea--CVE-2025-68939-20260317063330/report.html", + "href": "/runs/gitea-gitea--CVE-2025-68939-20260317063330/report.html", "label": "report.html", "kind": "text" }, { - "href": "./runs/gitea-gitea--CVE-2025-68939-20260317063330/report.md", + "href": "/runs/gitea-gitea--CVE-2025-68939-20260317063330/report.md", "label": "report.md", "kind": "text" }, { - "href": "./runs/gitea-gitea--CVE-2025-68939-20260317063330/timeline.mmd", + "href": "/runs/gitea-gitea--CVE-2025-68939-20260317063330/timeline.mmd", "label": "timeline.mmd", "kind": "text" }, { - "href": "./runs/gitea-gitea--CVE-2025-68939-20260317063330/run.json", + "href": "/runs/gitea-gitea--CVE-2025-68939-20260317063330/run.json", "label": "run.json", "kind": "text" } @@ -409,12 +409,12 @@ "count": 2, "items": [ { - "href": "./runs/gitea-gitea--CVE-2025-68939-20260317063330/logs/attack.json", + "href": "/runs/gitea-gitea--CVE-2025-68939-20260317063330/logs/attack.json", "label": "attack.json", "kind": "text" }, { - "href": "./runs/gitea-gitea--CVE-2025-68939-20260317063330/logs/baseline.json", + "href": "/runs/gitea-gitea--CVE-2025-68939-20260317063330/logs/baseline.json", "label": "baseline.json", "kind": "text" } @@ -462,10 +462,10 @@ "timeline": "/Users/x/websafe/06-case-studies/generated-runs/nextjs-nextjs--CVE-2025-29927-20260317063047/timeline.mmd" }, "dashboard_refs": { - "report_html": "./runs/nextjs-nextjs--CVE-2025-29927-20260317063047/report.html", - "report_md": "./runs/nextjs-nextjs--CVE-2025-29927-20260317063047/report.md", - "timeline": "./runs/nextjs-nextjs--CVE-2025-29927-20260317063047/timeline.mmd", - "bundle": "./runs/nextjs-nextjs--CVE-2025-29927-20260317063047/run.json" + "report_html": "/runs/nextjs-nextjs--CVE-2025-29927-20260317063047/report.html", + "report_md": "/runs/nextjs-nextjs--CVE-2025-29927-20260317063047/report.md", + "timeline": "/runs/nextjs-nextjs--CVE-2025-29927-20260317063047/timeline.mmd", + "bundle": "/runs/nextjs-nextjs--CVE-2025-29927-20260317063047/run.json" }, "browser_evidence": { "required": false, @@ -475,8 +475,8 @@ "browser_links": [], "container_links": [], "request_links": [ - "./runs/nextjs-nextjs--CVE-2025-29927-20260317063047/logs/attack.json", - "./runs/nextjs-nextjs--CVE-2025-29927-20260317063047/logs/baseline.json" + "/runs/nextjs-nextjs--CVE-2025-29927-20260317063047/logs/attack.json", + "/runs/nextjs-nextjs--CVE-2025-29927-20260317063047/logs/baseline.json" ], "advisory_meta": { "canonical_id": "nextjs--CVE-2025-29927", @@ -580,22 +580,22 @@ "count": 4, "items": [ { - "href": "./runs/nextjs-nextjs--CVE-2025-29927-20260317063047/report.html", + "href": "/runs/nextjs-nextjs--CVE-2025-29927-20260317063047/report.html", "label": "report.html", "kind": "text" }, { - "href": "./runs/nextjs-nextjs--CVE-2025-29927-20260317063047/report.md", + "href": "/runs/nextjs-nextjs--CVE-2025-29927-20260317063047/report.md", "label": "report.md", "kind": "text" }, { - "href": "./runs/nextjs-nextjs--CVE-2025-29927-20260317063047/timeline.mmd", + "href": "/runs/nextjs-nextjs--CVE-2025-29927-20260317063047/timeline.mmd", "label": "timeline.mmd", "kind": "text" }, { - "href": "./runs/nextjs-nextjs--CVE-2025-29927-20260317063047/run.json", + "href": "/runs/nextjs-nextjs--CVE-2025-29927-20260317063047/run.json", "label": "run.json", "kind": "text" } @@ -607,7 +607,7 @@ "count": 1, "items": [ { - "href": "./runs/nextjs-nextjs--CVE-2025-29927-20260317063047/logs/baseline.json", + "href": "/runs/nextjs-nextjs--CVE-2025-29927-20260317063047/logs/baseline.json", "label": "baseline.json", "kind": "text" } @@ -619,7 +619,7 @@ "count": 1, "items": [ { - "href": "./runs/nextjs-nextjs--CVE-2025-29927-20260317063047/logs/attack.json", + "href": "/runs/nextjs-nextjs--CVE-2025-29927-20260317063047/logs/attack.json", "label": "attack.json", "kind": "text" } diff --git a/08-threat-intel/generated/dashboard/runs/gitea-gitea--CVE-2025-68939-20260317063330 b/08-threat-intel/generated/dashboard/runs/gitea-gitea--CVE-2025-68939-20260317063330 deleted file mode 120000 index d9698843..00000000 --- a/08-threat-intel/generated/dashboard/runs/gitea-gitea--CVE-2025-68939-20260317063330 +++ /dev/null @@ -1 +0,0 @@ -../../../../06-case-studies/generated-runs/gitea-gitea--CVE-2025-68939-20260317063330 \ No newline at end of file diff --git a/08-threat-intel/generated/dashboard/runs/gitea-livecheck-20260316 b/08-threat-intel/generated/dashboard/runs/gitea-livecheck-20260316 deleted file mode 120000 index 5aefbc6f..00000000 --- a/08-threat-intel/generated/dashboard/runs/gitea-livecheck-20260316 +++ /dev/null @@ -1 +0,0 @@ -../../../../06-case-studies/generated-runs/gitea-livecheck-20260316 \ No newline at end of file diff --git a/08-threat-intel/generated/dashboard/runs/index.html b/08-threat-intel/generated/dashboard/runs/index.html new file mode 100644 index 00000000..be57391b --- /dev/null +++ b/08-threat-intel/generated/dashboard/runs/index.html @@ -0,0 +1,104 @@ + + + + + + 授权攻防实验工作台 + + + + +
+
+ + + +
+
+
+ + 授权攻防实验工作台 +
+

本地攻防实证工作台

+

+ Lovart 设计外壳已本地化并接入真实 run bundle 数据。页面只面向授权实验资产, + 聚合漏洞条目、时间线、证据、日志、来源、原始 JSON、当前架构库与失败原因。 +

+
+ +
+ + +
+ +
+ 启动中 + 正在载入本地生成数据 +
+
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +

选择一个运行

+

从左侧队列选择 run,即可查看时间线、证据、日志、来源、原始 JSON 和当前架构库。

+
+
+
+ + +
+ + + + diff --git a/08-threat-intel/generated/dashboard/runs/nextjs-nextjs--CVE-2025-29927-20260317063047 b/08-threat-intel/generated/dashboard/runs/nextjs-nextjs--CVE-2025-29927-20260317063047 deleted file mode 120000 index 623f6913..00000000 --- a/08-threat-intel/generated/dashboard/runs/nextjs-nextjs--CVE-2025-29927-20260317063047 +++ /dev/null @@ -1 +0,0 @@ -../../../../06-case-studies/generated-runs/nextjs-nextjs--CVE-2025-29927-20260317063047 \ No newline at end of file diff --git a/08-threat-intel/generated/dashboard/summary.json b/08-threat-intel/generated/dashboard/summary.json index faa46858..a42ead72 100644 --- a/08-threat-intel/generated/dashboard/summary.json +++ b/08-threat-intel/generated/dashboard/summary.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-03-17T09:03:14+00:00", + "generated_at": "2026-03-17T09:27:20+00:00", "advisory_count": 89, "run_count": 3, "statuses": { @@ -40,7 +40,10 @@ "manual": 36, "browser_required": 0, "browser_present": 0, - "latest_update": "2026-03-03T04:57:57.697708Z" + "latest_update": "2026-03-03T04:57:57.697708Z", + "category": "platforms", + "tier": "rolling-24m", + "output_dir": "07-framework-security/platforms/gitea" }, { "system_id": "nextjs", @@ -52,7 +55,10 @@ "manual": 26, "browser_required": 0, "browser_present": 0, - "latest_update": "2026-03-13T22:14:13.665535Z" + "latest_update": "2026-03-13T22:14:13.665535Z", + "category": "frameworks", + "tier": "history-full", + "output_dir": "07-framework-security/frameworks/nextjs" }, { "system_id": "undici", @@ -64,7 +70,10 @@ "manual": 14, "browser_required": 0, "browser_present": 0, - "latest_update": "2026-03-14T09:19:54.772219Z" + "latest_update": "2026-03-14T09:19:54.772219Z", + "category": "frameworks", + "tier": "rolling-24m", + "output_dir": "07-framework-security/frameworks/undici" }, { "system_id": "vite", @@ -76,7 +85,10 @@ "manual": 12, "browser_required": 0, "browser_present": 0, - "latest_update": "2026-02-04T04:37:24.129476Z" + "latest_update": "2026-02-04T04:37:24.129476Z", + "category": "frameworks", + "tier": "history-full", + "output_dir": "07-framework-security/frameworks/vite" } ] } diff --git a/08-threat-intel/generated/dashboard/systems.json b/08-threat-intel/generated/dashboard/systems.json index e43f62ca..43b94eaa 100644 --- a/08-threat-intel/generated/dashboard/systems.json +++ b/08-threat-intel/generated/dashboard/systems.json @@ -9,7 +9,10 @@ "manual": 36, "browser_required": 0, "browser_present": 0, - "latest_update": "2026-03-03T04:57:57.697708Z" + "latest_update": "2026-03-03T04:57:57.697708Z", + "category": "platforms", + "tier": "rolling-24m", + "output_dir": "07-framework-security/platforms/gitea" }, { "system_id": "nextjs", @@ -21,7 +24,10 @@ "manual": 26, "browser_required": 0, "browser_present": 0, - "latest_update": "2026-03-13T22:14:13.665535Z" + "latest_update": "2026-03-13T22:14:13.665535Z", + "category": "frameworks", + "tier": "history-full", + "output_dir": "07-framework-security/frameworks/nextjs" }, { "system_id": "undici", @@ -33,7 +39,10 @@ "manual": 14, "browser_required": 0, "browser_present": 0, - "latest_update": "2026-03-14T09:19:54.772219Z" + "latest_update": "2026-03-14T09:19:54.772219Z", + "category": "frameworks", + "tier": "rolling-24m", + "output_dir": "07-framework-security/frameworks/undici" }, { "system_id": "vite", @@ -45,6 +54,9 @@ "manual": 12, "browser_required": 0, "browser_present": 0, - "latest_update": "2026-02-04T04:37:24.129476Z" + "latest_update": "2026-02-04T04:37:24.129476Z", + "category": "frameworks", + "tier": "history-full", + "output_dir": "07-framework-security/frameworks/vite" } ] diff --git a/08-threat-intel/generated/dashboard/systems/index.html b/08-threat-intel/generated/dashboard/systems/index.html new file mode 100644 index 00000000..be57391b --- /dev/null +++ b/08-threat-intel/generated/dashboard/systems/index.html @@ -0,0 +1,104 @@ + + + + + + 授权攻防实验工作台 + + + + +
+
+ + + +
+
+
+ + 授权攻防实验工作台 +
+

本地攻防实证工作台

+

+ Lovart 设计外壳已本地化并接入真实 run bundle 数据。页面只面向授权实验资产, + 聚合漏洞条目、时间线、证据、日志、来源、原始 JSON、当前架构库与失败原因。 +

+
+ +
+ + +
+ +
+ 启动中 + 正在载入本地生成数据 +
+
+ +
+
+ +
+ +
+
+ +
+ + +
+
+ +

选择一个运行

+

从左侧队列选择 run,即可查看时间线、证据、日志、来源、原始 JSON 和当前架构库。

+
+
+
+ + +
+ + + + diff --git a/08-threat-intel/generated/latest-ingest.md b/08-threat-intel/generated/latest-ingest.md index 8b8bf0e9..a23cf580 100644 --- a/08-threat-intel/generated/latest-ingest.md +++ b/08-threat-intel/generated/latest-ingest.md @@ -1,6 +1,6 @@ # 最新同步摘要 -- 渲染时间: `2026-03-17T09:03:14+00:00` +- 渲染时间: `2026-03-17T09:27:20+00:00` - 系统数量: `62` - Advisory 数量: `89` - 重点 Markdown 数量: `89` diff --git a/08-threat-intel/generated/run-summary.json b/08-threat-intel/generated/run-summary.json index 54f8c4de..229cb822 100644 --- a/08-threat-intel/generated/run-summary.json +++ b/08-threat-intel/generated/run-summary.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-03-17T09:03:14+00:00", + "generated_at": "2026-03-17T09:27:20+00:00", "system_count": 62, "advisory_count": 89, "markdown_count": 89, diff --git a/README.md b/README.md index dbaedd87..a96b0567 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,19 @@ python3 /Users/x/websafe/scripts/lab/main.py serve-dashboard --port 8734 本地 dashboard 路由: - `/index.html` - - 默认正式 UI,使用本地化 Lovart 视觉壳层 + - 根入口别名,默认进入概览视图 +- `/overview/index.html` + - 总览入口,显示全局指标、最新运行、系统摘要与架构预览 +- `/runs/index.html` + - 运行中心,显示运行队列、详情、证据、日志与原始 JSON +- `/systems/index.html` + - 系统中心,按板块查看系统覆盖并跳转到对应运行 +- `/architecture/index.html` + - 架构中心,折叠查看控制面、数据层、授权边界与本地入口 +- `/docs/index.html` + - 文档中心,集中访问功能文档、设计文档和镜像页 +- `/data/index.html` + - 数据中心,集中访问 summary、runs、systems、profiles 等 JSON - `/legacy/index.html` - 旧版工作台回退入口 - `/docs/design-source.html` diff --git a/docs/frontend-dashboard-design.md b/docs/frontend-dashboard-design.md index 9bec8d81..dd7870fa 100644 --- a/docs/frontend-dashboard-design.md +++ b/docs/frontend-dashboard-design.md @@ -20,7 +20,7 @@ ### 2.1 页面名称 -- 页面名称:`Authorized Lab Dashboard` +- 页面名称:`授权攻防实验工作台` - 页面语境:本地静态前端 + 本地文件 JSON 数据源 - 非目标:在线 SaaS、多用户后端、生产管理台 @@ -31,7 +31,7 @@ - 信息密度高,但必须可折叠、可筛选、可逐层展开 - 日志与原始 JSON 必须能直接预览 - 页面视觉应更生动,但不能牺牲扫描效率 -- 默认路由采用正式新 UI,同时保留 `legacy` 回退入口 +- 默认路由采用分板块 URL,同时保留 `legacy` 回退入口 - 运行期不得依赖外部 HTML、字体 CDN 或图标 CDN ## 3. 信息架构 @@ -39,7 +39,8 @@ ```mermaid flowchart LR A["Hero + Global Status"] --> B["Sidebar Filters"] - A --> C["Run Queue List"] + A --> B1["Top Section Nav"] + B1 --> C["Overview / Runs / Systems / Architecture / Docs / Data"] C --> D["Run Detail Hero"] D --> E["Progress Timeline"] D --> F["Attack Plan & Reasoning"] @@ -62,6 +63,8 @@ flowchart LR - 自动刷新开关 - 当前同步状态 - 核心 metric cards +- 顶部板块菜单 +- 顶部 chips 分类筛选 视觉要求: @@ -71,24 +74,22 @@ flowchart LR ### 4.2 左侧侧栏 -包含四块: +改为按板块变化,不再固定使用长下拉: -- Filters +- Overview - 搜索 - - system filter - - status filter - - profile filter + - 最近失败 + - 最新运行 + - 系统概览 +- Runs + - 搜索 + - 最近失败 + - 运行队列 - Systems - - 系统覆盖度 - - browser evidence 覆盖 - - latest update -- Recent Failures - - 最近 blocker - - status - - 原因摘要 -- Run Queue View - - 最近 run 卡片列表 - - 可选中并切换到 detail panel + - 搜索 + - 系统目录 +- Architecture / Docs / Data + - 对应目录、入口或结构列表 ### 4.3 右侧 Detail Workspace @@ -153,7 +154,7 @@ flowchart LR - 点击左侧 run card 后,右侧 detail panel 即时刷新 - 当前选中项要有强视觉区别 -- URL hash 应保留 `#run=`,方便直接打开特定 run +- URL 应按板块进入不同入口,并通过 query 参数保留筛选与 `run=` ### 5.3 Artifact 预览 @@ -299,7 +300,19 @@ flowchart LR ## 10. 路由与文档地址 - `/index.html` - - 默认正式入口,使用本地化 Lovart UI 外壳 + - 根入口别名 +- `/overview/index.html` + - 总览入口 +- `/runs/index.html` + - 运行中心 +- `/systems/index.html` + - 系统中心 +- `/architecture/index.html` + - 架构中心 +- `/docs/index.html` + - 文档中心 +- `/data/index.html` + - 数据中心 - `/legacy/index.html` - 旧版 dashboard 回退入口 - `/docs/project-features.html` @@ -327,6 +340,7 @@ flowchart LR - 能折叠与展开各信息区 - 能打开并预览 JSON / text / image / html artifact - 能看到失败原因、思路、来源、修复主题 -- 能筛选 system / status / profile +- 能通过顶部 chips 筛选状态 / 板块 / 漏洞家族 +- 能通过分板块 URL 直接打开 overview / runs / systems / architecture / docs / data - 能在自动刷新开启时重新载入 dashboard 数据 - 页面视觉比“普通表格页”更生动,但仍适合高密度阅读 diff --git a/docs/project-features.md b/docs/project-features.md index 59807e00..59479c7e 100644 --- a/docs/project-features.md +++ b/docs/project-features.md @@ -61,7 +61,8 @@ - `report.md`, `report.html`, `timeline.mmd`, `assets/`, `logs/` - `08-threat-intel/generated/dashboard/` - 静态前端工作台 - - `/index.html` 为本地化 Lovart 正式 UI + - `/index.html` 为根入口别名 + - `/overview/index.html`, `/runs/index.html`, `/systems/index.html`, `/architecture/index.html`, `/docs/index.html`, `/data/index.html` 为分类入口 - `/legacy/index.html` 为旧版工作台回退入口 - `/docs/*.html` 为本地可访问的说明、真值配置与设计镜像页 - `architecture.json` 为当前架构库结构化真值 @@ -156,6 +157,8 @@ python3 /Users/x/websafe/scripts/lab/main.py serve-dashboard --port 8734 前端不只是“一个结果页”,而是本地实验控制台与证据阅读器。它需要: - 快速定位系统 / advisory / repro profile +- 通过顶部板块菜单切换总览、运行、系统、架构、文档和数据中心 +- 使用顶部 chips 做状态 / 板块 / 漏洞家族筛选,不再依赖过长下拉 - 折叠与展开 timeline、evidence、sources、raw JSON - 折叠与展开“当前架构库”,查看控制面、数据层、地址入口、授权边界和系统分组 - 直接查看 compose、JSON、日志、截图、报告 @@ -169,7 +172,19 @@ python3 /Users/x/websafe/scripts/lab/main.py serve-dashboard --port 8734 当前地址布局固定为: - `/index.html` - - 默认新 UI,基于本地化 Lovart 视觉壳层,绑定真实 dashboard JSON + - 根入口别名,默认进入概览 +- `/overview/index.html` + - 总览入口 +- `/runs/index.html` + - 运行中心 +- `/systems/index.html` + - 系统中心 +- `/architecture/index.html` + - 架构中心 +- `/docs/index.html` + - 文档中心 +- `/data/index.html` + - 数据中心 - `/legacy/index.html` - 旧版工作台显式保留,用于快速回退和对照 - `/docs/design-source.html` diff --git a/scripts/lab/dashboard_templates/lovart/assets/app.js b/scripts/lab/dashboard_templates/lovart/assets/app.js index 87a78e5a..c676bb1f 100644 --- a/scripts/lab/dashboard_templates/lovart/assets/app.js +++ b/scripts/lab/dashboard_templates/lovart/assets/app.js @@ -1,32 +1,18 @@ -const state = { - summary: null, - runs: [], - systems: [], - advisories: {}, - profiles: {}, - architecture: null, - selectedRunId: null, - selectedArtifact: null, - refreshHandle: null, - refreshMs: 5000, - autoRefresh: true, - filters: { - search: "", - system: "", - status: "", - profile: "" - }, - panels: { - timeline: true, - reasoning: true, - evidence: true, - logs: true, - sources: true, - architecture: true, - run_json: false, - advisory_json: false, - profile_json: false - } +const SECTION_META = [ + { id: "overview", label: "总览", path: "/overview/index.html", description: "查看全局指标、最新运行、架构摘要和入口。", icon: "report" }, + { id: "runs", label: "运行", path: "/runs/index.html", description: "查看运行队列、单次运行详情、证据与日志。", icon: "queue" }, + { id: "systems", label: "系统", path: "/systems/index.html", description: "按系统与分类查看覆盖情况和跳转入口。", icon: "systems" }, + { id: "architecture", label: "架构", path: "/architecture/index.html", description: "折叠查看控制面、数据层、授权边界与路由。", icon: "reasoning" }, + { id: "docs", label: "文档", path: "/docs/index.html", description: "集中访问功能文档、设计文档和镜像说明。", icon: "docs" }, + { id: "data", label: "数据", path: "/data/index.html", description: "集中访问 summary、runs、systems 等 JSON 产物。", icon: "json" } +]; + +const CATEGORY_LABELS = { + cms: "CMS / 内容平台", + ecommerce: "电商系统", + frameworks: "Web 框架与运行时", + servers: "服务器与边界层", + platforms: "开源平台与后台系统" }; const STATUS_LABELS = { @@ -49,9 +35,66 @@ const ARTIFACT_KIND_LABELS = { link: "链接" }; +const DOC_HUB_ITEMS = [ + { title: "项目功能总览", href: "/docs/project-features.html", description: "项目定位、功能版图、自动化链路和 CLI 入口。", badge: "docs" }, + { title: "前端设计文档", href: "/docs/frontend-dashboard-design.html", description: "工作台布局、交互、折叠逻辑和视觉规范。", badge: "ui" }, + { title: "架构库镜像", href: "/docs/architecture-library.html", description: "当前架构库的结构化镜像页,可直接查看 JSON 真值。", badge: "architecture" }, + { title: "仓库入口镜像", href: "/docs/root-readme.html", description: "根 README 的本地镜像,包含能力矩阵与主入口。", badge: "readme" }, + { title: "授权模型", href: "/docs/authorization-model.html", description: "目标范围、授权模型、最小化验证建议和记录要求。", badge: "scope" }, + { title: "source-map 镜像", href: "/docs/source-map.html", description: "系统覆盖、来源、输出目录和 secure-code 主题真值。", badge: "source-map" }, + { title: "repro-map 镜像", href: "/docs/repro-map.html", description: "默认漏洞家族、浏览器要求和日志策略真值。", badge: "repro-map" }, + { title: "覆盖矩阵镜像", href: "/docs/coverage-matrix.html", description: "当前全库覆盖矩阵的本地镜像。", badge: "coverage" }, + { title: "安全编码索引", href: "/docs/secure-code-index.html", description: "secure-code 修复主题索引镜像。", badge: "secure-code" }, + { title: "设计来源", href: "/docs/design-source.html", description: "Lovart 模板来源、本地化记录和 manifest 镜像。", badge: "vendor" } +]; + +const DATA_HUB_ITEMS = [ + { title: "summary.json", href: "/summary.json", description: "全局摘要、状态分布、最近失败与系统汇总。", badge: "json" }, + { title: "runs.json", href: "/runs.json", description: "最近运行的结构化详情,可用于 UI 和调试。", badge: "json" }, + { title: "systems.json", href: "/systems.json", description: "系统级覆盖、分类、更新时间和浏览器证据统计。", badge: "json" }, + { title: "advisories.json", href: "/advisories.json", description: "漏洞条目元数据、来源和 secure-code 主题。", badge: "json" }, + { title: "profiles.json", href: "/profiles.json", description: "复现档案元数据、成功判据和 browser assertions。", badge: "json" }, + { title: "architecture.json", href: "/architecture.json", description: "当前架构库的结构化真值。", badge: "json" }, + { title: "Manifest JSON", href: "/assets/design-source.json", description: "Lovart 模板本地 vendor manifest。", badge: "assets" }, + { title: "最新同步摘要", href: "/docs/coverage-matrix.html", description: "覆盖矩阵与本地生成态入口。", badge: "generated" } +]; + +const state = { + routeSection: resolveRouteSection(), + summary: null, + runs: [], + systems: [], + advisories: {}, + profiles: {}, + architecture: null, + selectedRunId: null, + selectedArtifact: null, + refreshHandle: null, + refreshMs: 5000, + autoRefresh: true, + filters: { + search: "", + status: "", + category: "", + family: "", + system: "" + }, + panels: { + timeline: true, + reasoning: true, + evidence: true, + logs: true, + sources: true, + architecture: true, + run_json: false, + advisory_json: false, + profile_json: false + } +}; + const $ = (id) => document.getElementById(id); const icon = (name, className = "icon") => - ``; + ``; const statusClass = (status) => ({ "verified-real": "status-pill status-verified-real", @@ -65,6 +108,24 @@ const statusClass = (status) => ({ skipped: "status-pill status-triage-manual" }[status] || "status-pill status-default"); +function resolveRouteSection() { + const parts = window.location.pathname.split("/").filter(Boolean); + const first = parts[0] || ""; + if (!first || first === "index.html" || first === "overview") return "overview"; + if (SECTION_META.some((section) => section.id === first)) return first; + return "overview"; +} + +function currentOverviewAlias() { + return window.location.pathname === "/" || window.location.pathname === "/index.html"; +} + +function routePath(sectionId) { + if (sectionId === "overview" && currentOverviewAlias()) return window.location.pathname || "/index.html"; + const section = SECTION_META.find((item) => item.id === sectionId); + return section ? section.path : "/overview/index.html"; +} + function escapeHtml(value) { return String(value ?? "") .replaceAll("&", "&") @@ -77,6 +138,10 @@ function formatStatus(value) { return STATUS_LABELS[value] || String(value || "unknown").replaceAll("-", " "); } +function formatCategory(value) { + return CATEGORY_LABELS[value] || String(value || "未分类"); +} + function formatDateTime(value) { if (!value) return "-"; const date = new Date(value); @@ -107,16 +172,69 @@ function timeAgo(value) { return `${days} 天前`; } +function distinct(values) { + return [...new Set(values.filter(Boolean))].sort(); +} + async function fetchJson(url) { const response = await fetch(`${url}?t=${Date.now()}`, { cache: "no-store" }); - if (!response.ok) { - throw new Error(`${url} -> ${response.status}`); - } + if (!response.ok) throw new Error(`${url} -> ${response.status}`); return response.json(); } -function distinct(values) { - return [...new Set(values.filter(Boolean))].sort(); +function sectionMeta(sectionId = state.routeSection) { + return SECTION_META.find((item) => item.id === sectionId) || SECTION_META[0]; +} + +function applyUrlState() { + const params = new URLSearchParams(window.location.search); + state.filters.search = params.get("search") || ""; + state.filters.status = params.get("status") || ""; + state.filters.category = params.get("category") || ""; + state.filters.family = params.get("family") || ""; + state.filters.system = params.get("system") || ""; + state.selectedRunId = params.get("run") || null; +} + +function buildUrl(sectionId = state.routeSection, overrides = {}) { + const nextFilters = { ...state.filters }; + for (const [key, value] of Object.entries(overrides)) { + if (key === "run") continue; + if (value === null || value === undefined || value === "") { + delete nextFilters[key]; + nextFilters[key] = ""; + } else { + nextFilters[key] = String(value); + } + } + + const nextRunId = Object.prototype.hasOwnProperty.call(overrides, "run") + ? (overrides.run ? String(overrides.run) : "") + : (state.selectedRunId || ""); + + const params = new URLSearchParams(); + if (["overview", "runs", "systems"].includes(sectionId)) { + if (nextFilters.search) params.set("search", nextFilters.search); + if (nextFilters.status && sectionId !== "systems") params.set("status", nextFilters.status); + if (nextFilters.category) params.set("category", nextFilters.category); + if (nextFilters.family && sectionId !== "systems") params.set("family", nextFilters.family); + if (nextFilters.system) params.set("system", nextFilters.system); + } + if (sectionId === "runs" && nextRunId) params.set("run", nextRunId); + const query = params.toString(); + return `${routePath(sectionId)}${query ? `?${query}` : ""}`; +} + +function updateUrl() { + const nextUrl = buildUrl(); + const currentUrl = `${window.location.pathname}${window.location.search}`; + if (nextUrl !== currentUrl) { + window.history.replaceState({}, "", nextUrl); + } +} + +function navigateToSection(sectionId, overrides = {}) { + window.location.assign(buildUrl(sectionId, overrides)); } function sumStatuses(predicate) { @@ -125,6 +243,14 @@ function sumStatuses(predicate) { .reduce((sum, [, value]) => sum + Number(value || 0), 0); } +function categoryOptions() { + return distinct(state.systems.map((system) => system.category)); +} + +function familyOptions() { + return distinct(state.runs.map((run) => run.profile_meta?.vuln_family || "")); +} + function metricCards() { const successCount = Number(state.summary?.statuses?.["verified-real"] || 0) + Number(state.summary?.statuses?.["verified-synthetic"] || 0); const blockedCount = sumStatuses((key) => key.startsWith("blocked")); @@ -187,40 +313,118 @@ function renderSyncState(kind, title, detail) { $("syncState").dataset.kind = kind; } -function optionLabel(kind, value) { - if (kind === "status") return formatStatus(value); - return value; +function renderSectionNav() { + const active = state.routeSection; + $("sectionNav").innerHTML = SECTION_META + .map((section) => ` + + ${icon(section.icon)}${escapeHtml(section.label)} + ${escapeHtml(section.description)} + + `) + .join(""); } -function hydrateFilters() { - const controls = [ - ["systemFilter", "system", state.runs.map((item) => item.system_id), "全部系统"], - ["statusFilter", "status", state.runs.map((item) => item.verification_status), "全部状态"], - ["profileFilter", "profile", state.runs.map((item) => item.repro_profile_id), "全部复现档案"] - ]; +function renderChipRow(label, key, options, formatter) { + return ` + + `; +} - for (const [id, key, values, label] of controls) { - const control = $(id); - const current = state.filters[key]; - control.innerHTML = ``; - control.innerHTML += distinct(values) - .map((value) => ``) - .join(""); - control.value = current; +function renderTopMenus() { + const meta = sectionMeta(); + const routeLine = ` +
+
+ ${escapeHtml(meta.label)} + ${escapeHtml(meta.description)} +
+
+ 当前地址 ${escapeHtml(window.location.pathname)} + ${window.location.search ? `${escapeHtml(window.location.search)}` : ""} +
+
+ `; + + const rows = []; + if (["overview", "runs"].includes(state.routeSection)) { + rows.push(renderChipRow("状态筛选", "status", distinct(state.runs.map((item) => item.verification_status)), formatStatus)); + rows.push(renderChipRow("系统板块", "category", categoryOptions(), formatCategory)); + rows.push(renderChipRow("漏洞家族", "family", familyOptions(), (value) => value)); + } else if (state.routeSection === "systems") { + rows.push(renderChipRow("系统板块", "category", categoryOptions(), formatCategory)); } + + if (state.filters.system && ["overview", "runs", "systems"].includes(state.routeSection)) { + rows.push(` + + `); + } + + if (["overview", "runs", "systems"].includes(state.routeSection)) { + rows.push(` + + `); + } + + $("topMenus").innerHTML = `${routeLine}${rows.join("")}`; } function filteredRuns() { return state.runs.filter((item) => { if (state.filters.system && item.system_id !== state.filters.system) return false; if (state.filters.status && item.verification_status !== state.filters.status) return false; - if (state.filters.profile && item.repro_profile_id !== state.filters.profile) return false; + if (state.filters.category && item.advisory_meta?.category !== state.filters.category) return false; + if (state.filters.family && item.profile_meta?.vuln_family !== state.filters.family) return false; if (!state.filters.search) return true; const haystack = [ item.run_id, item.advisory_id, item.system_id, item.repro_profile_id, + item.profile_meta?.vuln_family || "", item.advisory_meta?.title || "", item.advisory_meta?.summary || "" ] @@ -230,37 +434,58 @@ function filteredRuns() { }); } -function renderSystems() { - $("systemStats").innerHTML = state.systems.length - ? state.systems - .map((system) => { - const total = Math.max(Number(system.total || 0), 1); - const verified = Number(system.verified_real || 0) + Number(system.verified_synthetic || 0); - const coverage = Math.round((verified / total) * 100); - return ` -
-
- ${escapeHtml(system.display_name || system.system_id)} - ${escapeHtml(system.browser_present || 0)}/${escapeHtml(system.browser_required || 0)} 浏览器证据 -
-
${escapeHtml(system.system_id)} · 最近更新 ${escapeHtml(formatDateTime(system.latest_update || "-"))}
-
- 真实 ${escapeHtml(system.verified_real || 0)} - 合成 ${escapeHtml(system.verified_synthetic || 0)} - 阻塞 ${escapeHtml(system.blocked || 0)} -
-
-
- `; - }) - .join("") - : `
暂无系统覆盖数据。
`; +function filteredSystems() { + return state.systems.filter((system) => { + if (state.filters.system && system.system_id !== state.filters.system) return false; + if (state.filters.category && system.category !== state.filters.category) return false; + if (!state.filters.search) return true; + const haystack = [ + system.system_id, + system.display_name || "", + formatCategory(system.category) + ] + .join(" ") + .toLowerCase(); + return haystack.includes(state.filters.search); + }); } -function renderRecentFailures() { - const failures = state.summary?.recent_failures || []; - $("recentFailures").innerHTML = failures.length - ? failures +function recentFailures(limit = 6) { + return (state.summary?.recent_failures || []).slice(0, limit); +} + +function recentRuns(limit = 8) { + return filteredRuns().slice(0, limit); +} + +function renderSearchSection(countLabel, placeholder) { + const tags = [ + state.filters.status ? `状态: ${formatStatus(state.filters.status)}` : "", + state.filters.category ? `板块: ${formatCategory(state.filters.category)}` : "", + state.filters.family ? `家族: ${state.filters.family}` : "", + state.filters.system ? `系统: ${state.filters.system}` : "" + ].filter(Boolean); + return ` + + `; +} + +function renderFailureCards(items) { + return items.length + ? items .map( (item) => `
@@ -277,16 +502,14 @@ function renderRecentFailures() { : `
当前没有最近失败记录。
`; } -function renderRunQueue() { - const runs = filteredRuns(); - $("runCount").textContent = `${runs.length} 条`; - $("runQueue").innerHTML = runs.length - ? runs +function renderRunList(items, emptyText) { + return items.length + ? items .map((item) => { - const active = item.run_id === state.selectedRunId ? "is-active" : ""; + const active = item.run_id === state.selectedRunId && state.routeSection === "runs" ? "is-active" : ""; const browserState = item.browser_evidence?.present ? "已采集" : (item.browser_evidence?.required ? "必需待补" : "可选"); - const lead = item.reasoning_lines?.[0] || item.blocked_reason || item.advisory_meta?.summary || ""; const artifactCount = (item.artifact_groups || []).reduce((sum, group) => sum + Number(group.count || 0), 0); + const lead = item.reasoning_lines?.[0] || item.blocked_reason || item.advisory_meta?.summary || ""; return ` + 查看运行 +
+ + `; + }) + .join("") + : `
当前筛选下没有系统数据。
`; +} + +function renderHubCards(items) { + return ` + + `; +} + +function renderSidebar() { + const sidebar = $("sidebar"); + const filteredRunItems = filteredRuns(); + const filteredSystemItems = filteredSystems(); + const architectureSections = state.architecture?.sections || []; + + if (state.routeSection === "runs") { + sidebar.innerHTML = [ + renderSearchSection(`${filteredRunItems.length} 条运行`, "搜索 run id、漏洞条目、标题、系统、家族"), + ` + + `, + ` + + ` + ].join(""); + return; } - const bar = order - .filter(([key]) => Number(progress?.[key] || 0) > 0) - .map(([key, _label, className]) => { - const pct = Math.max((Number(progress[key] || 0) / total) * 100, 4); - return ``; - }) - .join(""); - const legend = order - .filter(([key]) => Number(progress?.[key] || 0) > 0) - .map(([key, label, className]) => `${escapeHtml(label)} ${escapeHtml(progress[key] || 0)}`) - .join(""); - return { bar, legend }; -} -function timelineTone(status) { - if (status === "completed" || status === "verified-real" || status === "verified-synthetic") return "timeline-success"; - if (String(status || "").startsWith("blocked") || status === "failed") return "timeline-blocked"; - if (status === "planned") return "timeline-pending"; - return "timeline-neutral"; + if (state.routeSection === "systems") { + sidebar.innerHTML = [ + renderSearchSection(`${filteredSystemItems.length} 个系统`, "搜索系统名、display name、板块"), + ` + + ` + ].join(""); + return; + } + + if (state.routeSection === "architecture") { + sidebar.innerHTML = ` + + `; + return; + } + + if (state.routeSection === "docs") { + sidebar.innerHTML = ` + + `; + return; + } + + if (state.routeSection === "data") { + sidebar.innerHTML = ` + + `; + return; + } + + sidebar.innerHTML = [ + renderSearchSection(`${filteredRunItems.length} 条运行`, "搜索运行、漏洞条目、标题、系统、家族"), + ` + + `, + ` + + `, + ` + + ` + ].join(""); } function renderPanel(panelKey, title, meta, iconName, content) { @@ -375,71 +730,6 @@ function renderPanel(panelKey, title, meta, iconName, content) { `; } -function defaultArtifact(run) { - const preference = ["attack", "requests", "container", "browser", "baseline", "compose", "reports"]; - for (const key of preference) { - const group = (run.artifact_groups || []).find((item) => item.key === key && item.items?.length); - if (!group) continue; - const textItem = group.items.find((item) => item.kind === "text"); - return textItem || group.items[0]; - } - return null; -} - -async function openArtifact(href, label, kind) { - state.selectedArtifact = { href, label, kind }; - document.querySelectorAll(".artifact-button").forEach((button) => { - button.classList.toggle("is-active", button.dataset.href === href); - }); - - const labelNode = $("viewerLabel"); - const metaNode = $("viewerMeta"); - const openNode = $("viewerOpen"); - const viewer = $("viewerFrame"); - if (!labelNode || !metaNode || !openNode || !viewer) return; - - labelNode.textContent = label; - metaNode.textContent = href; - openNode.href = href; - - try { - if (kind === "image") { - viewer.innerHTML = `${escapeHtml(label)}`; - return; - } - if (href.endsWith(".html")) { - viewer.innerHTML = ``; - return; - } - const response = await fetch(`${href}?t=${Date.now()}`, { cache: "no-store" }); - if (!response.ok) throw new Error(`${href} -> ${response.status}`); - const text = await response.text(); - let formatted = text; - if (href.endsWith(".json")) { - try { - formatted = JSON.stringify(JSON.parse(text), null, 2); - } catch (_error) { - } - } - viewer.innerHTML = `
${escapeHtml(formatted)}
`; - } catch (error) { - viewer.innerHTML = `
加载产物失败:${escapeHtml(error.message)}
`; - } -} - -function bindPanelToggles() { - document.querySelectorAll("[data-panel-toggle]").forEach((button) => { - button.addEventListener("click", () => { - const key = button.dataset.panelToggle; - state.panels[key] = !(state.panels[key] !== false); - const panel = document.querySelector(`[data-panel="${key}"]`); - if (panel) { - panel.classList.toggle("is-collapsed", state.panels[key] === false); - } - }); - }); -} - function renderArchitectureFields(fields = []) { if (!fields.length) return ""; return ` @@ -500,9 +790,7 @@ function renderArchitectureNode(node, depth = 0) { const fields = renderArchitectureFields(node.fields || []); const stats = renderArchitectureStats(node.stats || []); const links = renderArchitectureLinks(node.links || []); - const badges = (node.badges || []) - .map((badge) => `${escapeHtml(badge)}`) - .join(""); + const badges = (node.badges || []).map((badge) => `${escapeHtml(badge)}`).join(""); const hasBody = Boolean(children || fields || stats || links || node.summary || badges); const summaryBlock = `
@@ -517,19 +805,13 @@ function renderArchitectureNode(node, depth = 0) { `; if (!hasBody) { - return ` -
- ${summaryBlock} -
- `; + return `
${summaryBlock}
`; } const openAttr = node.open === false ? "" : "open"; return `
- - ${summaryBlock} - + ${summaryBlock}
${badges ? `
${badges}
` : ""} ${stats} @@ -553,9 +835,9 @@ function renderArchitecturePanel() {
${escapeHtml(architecture.summary || "当前工作台的结构化真值视图。")}
生成时间 ${escapeHtml(formatDateTime(architecture.generated_at))} - 架构 JSON - 镜像页 - 仓库入口镜像 + 架构 JSON + 镜像页 + 仓库入口镜像
@@ -565,93 +847,116 @@ function renderArchitecturePanel() { return renderPanel("architecture", "当前架构库", `${sections.length} 个分区`, "systems", content); } -function renderEmptyWorkspace() { - $("detailWorkspace").innerHTML = ` -
- ${icon("shield", "icon icon-xl")} -

选择一个运行

-

左侧队列用于切换 run。即使当前没有选中运行,你也可以直接展开下方“当前架构库”查看仓库控制面、数据层、系统分组、授权边界与本地入口。

-
- ${renderArchitecturePanel()} - `; - bindPanelToggles(); +function defaultArtifact(run) { + const preference = ["attack", "requests", "container", "browser", "baseline", "compose", "reports"]; + for (const key of preference) { + const group = (run.artifact_groups || []).find((item) => item.key === key && item.items?.length); + if (!group) continue; + const textItem = group.items.find((item) => item.kind === "text"); + return textItem || group.items[0]; + } + return null; } -function renderDetail() { - const run = state.runs.find((item) => item.run_id === state.selectedRunId); - if (!run) { - renderEmptyWorkspace(); - return; +async function openArtifact(href, label, kind) { + state.selectedArtifact = { href, label, kind }; + document.querySelectorAll(".artifact-button").forEach((button) => { + button.classList.toggle("is-active", button.dataset.href === href); + }); + + const labelNode = $("viewerLabel"); + const metaNode = $("viewerMeta"); + const openNode = $("viewerOpen"); + const viewer = $("viewerFrame"); + if (!labelNode || !metaNode || !openNode || !viewer) return; + + labelNode.textContent = label; + metaNode.textContent = href; + openNode.href = href; + + try { + if (kind === "image") { + viewer.innerHTML = `${escapeHtml(label)}`; + return; + } + if (href.endsWith(".html")) { + viewer.innerHTML = ``; + return; + } + const response = await fetch(`${href}?t=${Date.now()}`, { cache: "no-store" }); + if (!response.ok) throw new Error(`${href} -> ${response.status}`); + const text = await response.text(); + let formatted = text; + if (href.endsWith(".json")) { + try { + formatted = JSON.stringify(JSON.parse(text), null, 2); + } catch (_error) { + } + } + viewer.innerHTML = `
${escapeHtml(formatted)}
`; + } catch (error) { + viewer.innerHTML = `
加载产物失败:${escapeHtml(error.message)}
`; + } +} + +function renderRunWorkspace() { + const runs = filteredRuns(); + if (!runs.length) { + return ` +
+ ${icon("shield", "icon icon-xl")} +

当前没有匹配运行

+

请先清空筛选,或者从系统分组页进入某个系统的运行中心。

+
+ ${renderArchitecturePanel()} + `; } + if (!state.selectedRunId || !runs.some((item) => item.run_id === state.selectedRunId)) { + state.selectedRunId = runs[0].run_id; + } + + const run = runs.find((item) => item.run_id === state.selectedRunId); const advisory = run.advisory_meta || {}; const profile = run.profile_meta || {}; const screenshotItems = ((run.artifact_groups || []).find((group) => group.key === "browser")?.items || []).filter((item) => item.kind === "image"); - const segments = progressSegments(run.progress || {}); - const browserStatus = run.browser_evidence?.present ? "已采集" : (run.browser_evidence?.required ? "必需待补" : "可选"); const artifactCount = (run.artifact_groups || []).reduce((sum, group) => sum + Number(group.count || 0), 0); + const browserStatus = run.browser_evidence?.present ? "已采集" : (run.browser_evidence?.required ? "必需待补" : "可选"); + const progress = { + completed: 0, + skipped: 0, + failed: 0, + blocked: 0, + planned: 0, + other: 0 + }; + (run.timeline || []).forEach((item) => { + if (String(item.status || "").startsWith("blocked")) progress.blocked += 1; + else if (Object.prototype.hasOwnProperty.call(progress, item.status || "")) progress[item.status] += 1; + else progress.other += 1; + }); - const timelineContent = ` -
${segments.bar}
-
${segments.legend}
-
- ${(run.timeline || []) - .map((item) => ` -
- -
- ${escapeHtml(item.step || "-")} - ${escapeHtml(formatDateTime(item.at || "-"))} -
-
${escapeHtml(formatStatus(item.status || "unknown"))}
-
${escapeHtml(item.detail || "-")}
-
- `) - .join("") || `
当前运行没有记录时间线。
`} -
- `; + const timelineRows = (run.timeline || []) + .map((item) => ` +
+ +
+ ${escapeHtml(item.step || "-")} + ${escapeHtml(formatDateTime(item.at || "-"))} +
+
${escapeHtml(formatStatus(item.status || "unknown"))}
+
${escapeHtml(item.detail || "-")}
+
+ `) + .join("") || `
当前运行没有记录时间线。
`; const reasoningCards = [ - { - label: "概要", - copy: advisory.summary || "当前漏洞条目没有摘要。" - }, - { - label: "成功判据", - copy: (profile.success_criteria || []).join(" | ") || "当前 profile 没有定义成功判据。" - }, - { - label: "Seed / 攻击思路", - copy: (run.reasoning_lines || []).join("\n\n") || "当前运行没有记录思路说明。" - }, - { - label: "允许目标", - copy: (profile.allowed_target_types || []).join(", ") || "当前 profile 没有声明允许目标类型。" - } + { label: "概要", copy: advisory.summary || "当前漏洞条目没有摘要。" }, + { label: "成功判据", copy: (profile.success_criteria || []).join(" | ") || "当前复现档案没有定义成功判据。" }, + { label: "Seed / 攻击思路", copy: (run.reasoning_lines || []).join("\n\n") || "当前运行没有记录思路说明。" }, + { label: "允许目标", copy: (profile.allowed_target_types || []).join(", ") || "当前复现档案没有声明允许目标类型。" } ]; - const reasoningContent = ` - ${run.blocked_reason ? `
失败原因
${escapeHtml(run.blocked_reason)}
` : ""} -
- 漏洞家族 ${escapeHtml(profile.vuln_family || "未定义")} - 清理策略 ${escapeHtml(profile.cleanup_policy || "-")} - 破坏性风险 ${escapeHtml(profile.destructive_risk || "-")} - 制品模式 ${escapeHtml(run.artifact_mode || "-")} -
-
- ${reasoningCards - .map( - (card) => ` -
- ${escapeHtml(card.label)} -
${escapeHtml(card.copy)}
-
- ` - ) - .join("")} -
- `; - const evidenceContent = `
${(run.artifact_groups || []) @@ -674,40 +979,12 @@ function renderDetail() { ` ) - .join("") || `
当前运行没有可浏览的产物分组。
`} - - ${ - screenshotItems.length - ? `` - : "" - } -
- `; - - const logContent = ` -
-
-
-
${escapeHtml(state.selectedArtifact?.label || "选择一个产物")}
-
${escapeHtml(state.selectedArtifact?.href || "这里会显示 JSON、文本、HTML 报告、截图和其他日志的预览。")}
-
-
- ${icon("link")}打开产物 - -
-
-
选择报告、日志、截图、JSON 或 HTML 产物后,会在这里直接预览。
+ .join("")} + ${screenshotItems.length ? `` : ""}
`; @@ -718,19 +995,11 @@ function renderDetail() { ...(advisory.secondary_source_urls || []).map((url) => `${escapeHtml(url)}`) ].join(""); - const sourcesContent = ` -
- ${(advisory.aliases || []).map((alias) => `${escapeHtml(alias)}`).join("")} - ${(advisory.secure_code_topics || []).map((topic) => `${escapeHtml(topic)}`).join("")} -
- - `; - const rawRunContent = `
${escapeHtml(JSON.stringify(run, null, 2))}
`; const rawAdvisoryContent = `
${escapeHtml(JSON.stringify(advisory, null, 2))}
`; const rawProfileContent = `
${escapeHtml(JSON.stringify(profile, null, 2))}
`; - $("detailWorkspace").innerHTML = ` + return `
${escapeHtml(formatStatus(run.verification_status))} @@ -743,20 +1012,18 @@ function renderDetail() {

${escapeHtml(advisory.title || run.advisory_id)}

${escapeHtml(advisory.summary || "当前漏洞条目没有摘要。")}
- -
时间线步骤 ${escapeHtml(run.timeline?.length || 0)}
- Artifact 数 + 证据数量 ${escapeHtml(artifactCount)}
@@ -769,66 +1036,309 @@ function renderDetail() {
- - ${renderPanel("timeline", "进度时间线", `${escapeHtml(run.timeline?.length || 0)} 步`, "timeline", timelineContent)} - ${renderPanel("reasoning", "攻击方案与推理", escapeHtml(profile.vuln_family || "未定义"), "reasoning", reasoningContent)} + ${renderPanel("timeline", "进度时间线", `${escapeHtml(run.timeline?.length || 0)} 步`, "timeline", ` +
${progressSegments(progress).bar}
+
${progressSegments(progress).legend}
+
${timelineRows}
+ `)} + ${renderPanel("reasoning", "攻击方案与推理", escapeHtml(profile.vuln_family || "未定义"), "reasoning", ` + ${run.blocked_reason ? `
失败原因
${escapeHtml(run.blocked_reason)}
` : ""} +
+ 漏洞家族 ${escapeHtml(profile.vuln_family || "未定义")} + 清理策略 ${escapeHtml(profile.cleanup_policy || "-")} + 破坏性风险 ${escapeHtml(profile.destructive_risk || "-")} + 制品模式 ${escapeHtml(run.artifact_mode || "-")} +
+
+ ${reasoningCards.map((card) => ` +
+ ${escapeHtml(card.label)} +
${escapeHtml(card.copy)}
+
+ `).join("")} +
+ `)} ${renderPanel("evidence", "证据浏览器", `${escapeHtml(run.artifact_groups?.length || 0)} 组`, "evidence", evidenceContent)} - ${renderPanel("logs", "实时日志查看器", state.selectedArtifact ? "已选产物" : "等待选择", "logs", logContent)} - ${renderPanel("sources", "来源与修复主题", `${escapeHtml((advisory.secondary_source_urls || []).length + (advisory.official_source_url ? 1 : 0))} 条链接`, "sources", sourcesContent)} + ${renderPanel("logs", "实时日志查看器", state.selectedArtifact ? "已选产物" : "等待选择", "logs", ` +
+
+
+
${escapeHtml(state.selectedArtifact?.label || "选择一个产物")}
+
${escapeHtml(state.selectedArtifact?.href || "这里会显示 JSON、文本、HTML 报告、截图和其他日志的预览。")}
+
+
+ ${icon("link")}打开产物 + +
+
+
选择报告、日志、截图、JSON 或 HTML 产物后,会在这里直接预览。
+
+ `)} + ${renderPanel("sources", "来源与修复主题", `${escapeHtml((advisory.secondary_source_urls || []).length + (advisory.official_source_url ? 1 : 0))} 条链接`, "sources", ` +
+ ${(advisory.aliases || []).map((alias) => `${escapeHtml(alias)}`).join("")} + ${(advisory.secure_code_topics || []).map((topic) => `${escapeHtml(topic)}`).join("")} +
+ + `)} ${renderArchitecturePanel()} ${renderPanel("run_json", "运行 JSON", "原始数据", "json", rawRunContent)} ${renderPanel("advisory_json", "漏洞条目 JSON", "原始数据", "json", rawAdvisoryContent)} ${renderPanel("profile_json", "复现档案 JSON", "原始数据", "json", rawProfileContent)} `; +} - bindPanelToggles(); +function renderOverviewWorkspace() { + const runs = recentRuns(6); + const systems = filteredSystems().slice(0, 12); + return ` +
+
+
+ 总览 +
+ 分类路由已启用 + 长下拉已移除 + URL 按板块分类 +
+
+

按板块浏览当前工作台

+
根入口保留为概览页,同时新增运行、系统、架构、文档和数据的独立 URL。顶部菜单负责分类切换,搜索与筛选会同步到地址栏。
+
+ ${renderPanel("overview_runs", "最新运行", `${escapeHtml(runs.length)} 条`, "queue", renderRunList(runs, "暂无运行数据。"))} + ${renderPanel("overview_systems", "系统覆盖概览", `${escapeHtml(systems.length)} 个系统`, "systems", `
${renderSystemCards(systems)}
`)} + ${renderArchitecturePanel()} + ${renderPanel("overview_docs", "文档与数据入口", `${escapeHtml(DOC_HUB_ITEMS.length + DATA_HUB_ITEMS.length)} 个入口`, "docs", `${renderHubCards(DOC_HUB_ITEMS.slice(0, 4).concat(DATA_HUB_ITEMS.slice(0, 4)))}`)} +
+ `; +} - document.querySelectorAll("[data-artifact]").forEach((button) => { - button.addEventListener("click", () => openArtifact(button.dataset.href, button.dataset.label, button.dataset.kind)); - }); +function renderSystemsWorkspace() { + const items = filteredSystems(); + return ` +
+
+
+ 系统分组 +
+ 共 ${escapeHtml(items.length)} 个系统 + ${state.filters.category ? `${escapeHtml(formatCategory(state.filters.category))}` : ""} + ${state.filters.system ? `已锁定 ${escapeHtml(state.filters.system)}` : ""} +
+
+

按系统与板块查看覆盖

+
顶部只保留板块 chips,不再使用过长下拉。若要精确到单系统,可在卡片上点“锁定系统”或直接进入该系统的运行中心。
+
+ ${renderPanel("systems_grid", "系统覆盖列表", `${escapeHtml(items.length)} 个系统`, "systems", `
${renderSystemCards(items)}
`)} +
+ `; +} - $("viewerRefresh")?.addEventListener("click", () => { - if (state.selectedArtifact) { - openArtifact(state.selectedArtifact.href, state.selectedArtifact.label, state.selectedArtifact.kind); - } - }); +function renderArchitectureWorkspace() { + return ` +
+
+
+ 架构库 +
+ 控制面 + 数据层 + 授权边界 + 系统覆盖 +
+
+

完整架构树与本地入口

+
这里是当前仓库的结构化真值视图。可以折叠查看任意层级信息,并跳转到对应的本地路由、文档镜像或 JSON。
+
+ ${renderArchitecturePanel()} +
+ `; +} - const artifactExists = (run.artifact_groups || []).some((group) => group.items.some((item) => item.href === state.selectedArtifact?.href)); - const defaultItem = artifactExists ? state.selectedArtifact : defaultArtifact(run); - if (defaultItem) { - openArtifact(defaultItem.href, defaultItem.label, defaultItem.kind); +function renderDocsWorkspace() { + return ` +
+
+
+ 文档中心 +
+ 项目文档 + 设计文档 + 镜像页 +
+
+

文档入口按板块集中

+
不再把所有入口混在首页链接堆里。这里按说明、设计、真值镜像和 secure-code 索引集中展示。
+
+ ${renderPanel("docs_hub", "文档与镜像页", `${escapeHtml(DOC_HUB_ITEMS.length)} 个入口`, "docs", renderHubCards(DOC_HUB_ITEMS))} +
+ `; +} + +function renderDataWorkspace() { + return ` +
+
+
+ 数据中心 +
+ JSON 真值 + 生成产物 + 本地直链 +
+
+

数据入口按类型集中

+
summary、runs、systems、advisories、profiles、architecture 已单独归入数据中心,避免和文档、运行详情混在一个地址里。
+
+ ${renderPanel("data_hub", "JSON 与生成数据", `${escapeHtml(DATA_HUB_ITEMS.length)} 个入口`, "json", renderHubCards(DATA_HUB_ITEMS))} +
+ `; +} + +function progressSegments(progress) { + const order = [ + ["completed", "已完成", "progress-completed"], + ["blocked", "已阻塞", "progress-blocked"], + ["failed", "失败", "progress-failed"], + ["skipped", "已跳过", "progress-skipped"], + ["planned", "已规划", "progress-planned"], + ["other", "其他", "progress-other"] + ]; + const total = order.reduce((sum, [key]) => sum + Number(progress?.[key] || 0), 0); + if (!total) { + return { + bar: `
`, + legend: `暂无进度` + }; } + const bar = order + .filter(([key]) => Number(progress?.[key] || 0) > 0) + .map(([key, _label, className]) => { + const pct = Math.max((Number(progress[key] || 0) / total) * 100, 4); + return ``; + }) + .join(""); + const legend = order + .filter(([key]) => Number(progress?.[key] || 0) > 0) + .map(([key, label, className]) => `${escapeHtml(label)} ${escapeHtml(progress[key] || 0)}`) + .join(""); + return { bar, legend }; +} + +function timelineTone(status) { + if (status === "completed" || status === "verified-real" || status === "verified-synthetic") return "timeline-success"; + if (String(status || "").startsWith("blocked") || status === "failed") return "timeline-blocked"; + if (status === "planned") return "timeline-pending"; + return "timeline-neutral"; +} + +function renderWorkspace() { + const workspace = $("detailWorkspace"); + let html = ""; + if (state.routeSection === "runs") html = renderRunWorkspace(); + else if (state.routeSection === "systems") html = renderSystemsWorkspace(); + else if (state.routeSection === "architecture") html = renderArchitectureWorkspace(); + else if (state.routeSection === "docs") html = renderDocsWorkspace(); + else if (state.routeSection === "data") html = renderDataWorkspace(); + else html = renderOverviewWorkspace(); + workspace.innerHTML = html; } function renderAll() { renderMetrics(); - renderSystems(); - renderRecentFailures(); - renderRunQueue(); - renderDetail(); + renderSectionNav(); + renderTopMenus(); + renderSidebar(); + renderWorkspace(); + updateUrl(); +} + +function setFilter(key, value) { + if (!Object.prototype.hasOwnProperty.call(state.filters, key)) return; + state.filters[key] = value || ""; + if (key !== "search") { + if (state.routeSection === "runs" && state.selectedRunId && !filteredRuns().some((item) => item.run_id === state.selectedRunId)) { + state.selectedRunId = filteredRuns()[0]?.run_id || null; + } + } + renderAll(); +} + +function clearFilters() { + state.filters = { + search: "", + status: "", + category: "", + family: "", + system: "" + }; + state.selectedRunId = state.routeSection === "runs" ? filteredRuns()[0]?.run_id || null : state.selectedRunId; + renderAll(); } function attachGlobalEvents() { - $("searchInput").addEventListener("input", (event) => { - state.filters.search = String(event.target.value || "").trim().toLowerCase(); - renderRunQueue(); + document.addEventListener("click", (event) => { + const toggle = event.target.closest("[data-panel-toggle]"); + if (toggle) { + const key = toggle.dataset.panelToggle; + state.panels[key] = !(state.panels[key] !== false); + const panel = document.querySelector(`[data-panel="${key}"]`); + if (panel) panel.classList.toggle("is-collapsed", state.panels[key] === false); + return; + } + + const refreshButton = event.target.closest("#refreshDashboard"); + if (refreshButton) { + loadData(false); + return; + } + + const viewerRefresh = event.target.closest("#viewerRefresh"); + if (viewerRefresh && state.selectedArtifact) { + openArtifact(state.selectedArtifact.href, state.selectedArtifact.label, state.selectedArtifact.kind); + return; + } + + const runButton = event.target.closest("[data-run-id]"); + if (runButton) { + const runId = runButton.dataset.runId; + if (state.routeSection !== "runs") { + navigateToSection("runs", { run: runId }); + return; + } + state.selectedRunId = runId; + renderAll(); + return; + } + + const filterButton = event.target.closest("[data-filter-key]"); + if (filterButton) { + setFilter(filterButton.dataset.filterKey, filterButton.dataset.filterValue || ""); + return; + } + + const clearButton = event.target.closest("[data-clear-filters]"); + if (clearButton) { + clearFilters(); + return; + } + + const artifactButton = event.target.closest("[data-artifact]"); + if (artifactButton) { + openArtifact(artifactButton.dataset.href, artifactButton.dataset.label, artifactButton.dataset.kind); + } }); - [ - ["systemFilter", "system"], - ["statusFilter", "status"], - ["profileFilter", "profile"] - ].forEach(([id, key]) => { - $(id).addEventListener("input", (event) => { - state.filters[key] = String(event.target.value || ""); - renderRunQueue(); - }); - }); + document.addEventListener("input", (event) => { + if (event.target.id === "searchInput") { + state.filters.search = String(event.target.value || "").trim().toLowerCase(); + renderAll(); + } - $("refreshDashboard").addEventListener("click", () => loadData(false)); - $("autoRefresh").addEventListener("change", (event) => { - state.autoRefresh = Boolean(event.target.checked); - startRefreshLoop(); + if (event.target.id === "autoRefresh") { + state.autoRefresh = Boolean(event.target.checked); + startRefreshLoop(); + } }); } @@ -842,17 +1352,17 @@ function startRefreshLoop() { } async function loadData(preserveSelection = true) { - const previous = state.selectedRunId; + const previousRunId = state.selectedRunId; renderSyncState("loading", "刷新中", `本地时间 ${new Date().toLocaleTimeString("zh-CN", { hour12: false })}`); try { const [summary, runs, systems, advisories, profiles, architecture] = await Promise.all([ - fetchJson("./summary.json"), - fetchJson("./runs.json"), - fetchJson("./systems.json"), - fetchJson("./advisories.json"), - fetchJson("./profiles.json"), - fetchJson("./architecture.json") + fetchJson("/summary.json"), + fetchJson("/runs.json"), + fetchJson("/systems.json"), + fetchJson("/advisories.json"), + fetchJson("/profiles.json"), + fetchJson("/architecture.json") ]); state.summary = summary; @@ -861,30 +1371,29 @@ async function loadData(preserveSelection = true) { state.advisories = advisories; state.profiles = profiles; state.architecture = architecture; - hydrateFilters(); - const hashRun = location.hash.startsWith("#run=") ? location.hash.replace("#run=", "") : null; - const candidate = preserveSelection ? (hashRun || previous) : hashRun; - if (candidate && runs.some((item) => item.run_id === candidate)) { + const filtered = filteredRuns(); + const candidate = preserveSelection ? (state.selectedRunId || previousRunId) : state.selectedRunId; + if (candidate && filtered.some((item) => item.run_id === candidate)) { state.selectedRunId = candidate; } else { - state.selectedRunId = runs[0]?.run_id || null; + state.selectedRunId = filtered[0]?.run_id || null; } renderAll(); renderSyncState("live", "实时同步", `最近生成 ${formatDateTime(summary.generated_at || new Date().toISOString())}`); } catch (error) { - $("runQueue").innerHTML = `
工作台加载失败:${escapeHtml(error.message)}
`; + $("sidebar").innerHTML = ``; $("detailWorkspace").innerHTML = `

加载失败

${escapeHtml(error.message)}

`; renderSyncState("error", "加载失败", error.message); } } async function init() { + applyUrlState(); attachGlobalEvents(); await loadData(false); startRefreshLoop(); - window.addEventListener("hashchange", () => loadData(false)); } document.addEventListener("DOMContentLoaded", init); diff --git a/scripts/lab/dashboard_templates/lovart/assets/styles.css b/scripts/lab/dashboard_templates/lovart/assets/styles.css index 893eafb6..02d70725 100644 --- a/scripts/lab/dashboard_templates/lovart/assets/styles.css +++ b/scripts/lab/dashboard_templates/lovart/assets/styles.css @@ -289,6 +289,157 @@ select { margin-top: 22px; } +.section-nav, +.top-menus { + position: relative; + margin-top: 18px; +} + +.section-nav { + display: grid; + grid-template-columns: repeat(6, minmax(0, 1fr)); + gap: 12px; +} + +.nav-pill { + display: grid; + gap: 8px; + padding: 14px 16px; + border-radius: 18px; + border: 1px solid var(--border-color); + background: rgba(255, 255, 255, 0.04); + transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease; +} + +.nav-pill:hover, +.nav-pill.is-active { + transform: translateY(-1px); + border-color: rgba(77, 141, 255, 0.42); + background: rgba(77, 141, 255, 0.1); +} + +.nav-pill-top { + display: inline-flex; + align-items: center; + gap: 8px; +} + +.nav-pill-top strong { + font-size: 0.95rem; +} + +.nav-pill-copy { + color: var(--text-secondary); + font-size: 0.82rem; + line-height: 1.45; +} + +.top-menus { + display: grid; + gap: 12px; +} + +.route-note, +.menu-row, +.hub-card, +.hub-card-static { + border: 1px solid var(--border-color); + border-radius: 16px; + background: rgba(255, 255, 255, 0.04); +} + +.route-note, +.menu-row { + padding: 14px 16px; +} + +.route-note { + display: flex; + justify-content: space-between; + gap: 16px; + align-items: flex-start; +} + +.route-note strong { + display: block; + font-size: 1rem; +} + +.route-note span { + display: block; + margin-top: 6px; + color: var(--text-secondary); + line-height: 1.5; +} + +.menu-row { + display: grid; + gap: 12px; +} + +.menu-row-compact { + padding-top: 12px; + padding-bottom: 12px; +} + +.menu-row-head { + display: flex; + justify-content: space-between; + gap: 12px; + color: var(--text-secondary); + text-transform: uppercase; + letter-spacing: 0.08em; + font-size: 0.74rem; +} + +.chip-strip { + display: flex; + gap: 10px; + overflow-x: auto; + padding-bottom: 4px; +} + +.chip-strip::-webkit-scrollbar { + height: 6px; +} + +.chip-strip::-webkit-scrollbar-thumb { + background: rgba(148, 163, 184, 0.3); + border-radius: 999px; +} + +.menu-chip { + display: inline-flex; + align-items: center; + justify-content: center; + gap: 8px; + min-height: 34px; + padding: 8px 14px; + border-radius: 999px; + border: 1px solid var(--border-color); + background: rgba(255, 255, 255, 0.04); + color: var(--text-primary); + cursor: pointer; + white-space: nowrap; + transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease; +} + +.menu-chip:hover, +.menu-chip.is-active, +.menu-chip-link:hover { + transform: translateY(-1px); + border-color: rgba(77, 141, 255, 0.42); + background: rgba(77, 141, 255, 0.1); +} + +.menu-chip-muted { + color: var(--text-secondary); +} + +.menu-chip-link { + text-decoration: none; +} + .metric-card { position: relative; padding: 16px 18px; @@ -502,6 +653,10 @@ select { padding-right: 4px; } +.run-list-compact { + max-height: 560px; +} + .run-card { cursor: pointer; transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease; @@ -582,6 +737,35 @@ select { min-width: 0; } +.workspace-stack, +.system-grid, +.hub-grid { + display: grid; + gap: 16px; +} + +.system-grid { + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); +} + +.hub-grid { + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); +} + +.hub-card, +.hub-card-static { + display: grid; + gap: 10px; + padding: 14px 16px; + transition: transform 0.18s ease, border-color 0.18s ease, background 0.18s ease; +} + +.hub-card:hover { + transform: translateY(-1px); + border-color: rgba(77, 141, 255, 0.42); + background: rgba(77, 141, 255, 0.08); +} + .workspace-empty { display: grid; place-items: center; @@ -619,6 +803,12 @@ select { margin-top: 18px; } +.button-small { + min-height: 36px; + padding: 8px 12px; + font-size: 0.84rem; +} + .detail-stat-grid, .plan-grid, .raw-json-grid { @@ -918,6 +1108,10 @@ select { word-break: break-all; } +.system-card-compact .detail-actions { + margin-top: 10px; +} + .viewer-frame { min-height: 320px; border: 1px solid rgba(148, 163, 184, 0.16); @@ -1181,6 +1375,7 @@ select { @media (max-width: 1320px) { .hero-top, .main-container, + .section-nav, .detail-stat-grid, .plan-grid, .raw-json-grid { @@ -1211,6 +1406,7 @@ select { } .hero-links, + .route-note, .detail-actions, .tag-row, .panel-meta, diff --git a/scripts/lab/dashboard_templates/lovart/index.html b/scripts/lab/dashboard_templates/lovart/index.html index 8d3c95b3..be57391b 100644 --- a/scripts/lab/dashboard_templates/lovart/index.html +++ b/scripts/lab/dashboard_templates/lovart/index.html @@ -4,7 +4,7 @@ 授权攻防实验工作台 - + @@ -16,7 +16,7 @@
- + 授权攻防实验工作台

本地攻防实证工作台

@@ -28,7 +28,7 @@
- +
启动中 正在载入本地生成数据
@@ -67,85 +67,16 @@
+ +
- +
- +

选择一个运行

从左侧队列选择 run,即可查看时间线、证据、日志、来源、原始 JSON 和当前架构库。

@@ -155,19 +86,19 @@
- + diff --git a/scripts/lab/render.py b/scripts/lab/render.py index 05f33cb8..6dce5e03 100644 --- a/scripts/lab/render.py +++ b/scripts/lab/render.py @@ -779,6 +779,14 @@ def _render_section_dashboard_shells() -> None: source_index = LOVART_TEMPLATE_DIR / "index.html" for section in SECTION_ROUTE_DIRS: section_dir = DASHBOARD_DIR / section + if section == "runs": + # Preserve existing /runs// bundles; only refresh the section shell. + ensure_dir(section_dir) + index_path = section_dir / "index.html" + if index_path.exists(): + index_path.unlink() + shutil.copy2(source_index, index_path) + continue _remove_path(section_dir) ensure_dir(section_dir) shutil.copy2(source_index, section_dir / "index.html")