feat(assets): 资产页新增「应用」Tab + app 字段可搜

- 资产页 TABS 补 app 分类,新增应用列表(应用名/Bundle ID/分类/ICP 备案)+勾选/删除
- DSL 搜索白名单补 bundle_id/category/app_icp(原仅 app_name),前端搜索建议同步
- list/count 本就按 type 通用,app 自动适配;补齐后 app 资产可看/可搜/可删

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Autumn-27andClaude Opus 4.8 committed 2026-08-08 06:19:11 -04:00
1 parent 6d8fa5bc48
commit e01b659097
2 files changed
+54 -7

No files matched your search

+10 -7
View File
@@ -37,6 +37,9 @@ var knownStringFields = map[string]string{
"icp": "icp",
"service_name": "service_name",
"app_name": "app_name",
"bundle_id": "bundle_id",
"category": "category",
"app_icp": "app_icp",
"method": "method",
"service_type": "service_type",
"record_type": "record_type",
@@ -67,13 +70,13 @@ func isKnownField(f string) bool {
// ── tokeniser ────────────────────────────────────────────────────────────────
const (
tkField = "FIELD"
tkBare = "BARE"
tkAnd = "AND"
tkOr = "OR"
tkLP = "LPAREN"
tkRP = "RPAREN"
tkEOF = "EOF"
tkField = "FIELD"
tkBare = "BARE"
tkAnd = "AND"
tkOr = "OR"
tkLP = "LPAREN"
tkRP = "RPAREN"
tkEOF = "EOF"
)
type tok struct {
@@ -7,6 +7,7 @@ import {
NetworkIcon,
LayoutTemplateIcon,
LinkIcon,
SmartphoneIcon,
SearchIcon,
KeyRoundIcon,
ChevronLeftIcon,
@@ -76,6 +77,9 @@ const DSL_FIELDS: { name: string; desc: string; ops: { op: string; desc: string
{ name: "icp", desc: "ICP 备案号", ops: [{ op: "=", desc: "模糊匹配" }, { op: "==", desc: "精确匹配" }, { op: "!=", desc: "排除" }] },
{ name: "service_name", desc: "服务名称(非 HTTP 服务)", ops: [{ op: "=", desc: "模糊匹配" }, { op: "==", desc: "精确匹配" }, { op: "!=", desc: "排除" }] },
{ name: "app_name", desc: "应用名称", ops: [{ op: "=", desc: "模糊匹配" }, { op: "==", desc: "精确匹配" }, { op: "!=", desc: "排除" }] },
{ name: "bundle_id", desc: "应用 Bundle ID", ops: [{ op: "=", desc: "模糊匹配" }, { op: "==", desc: "精确匹配" }, { op: "!=", desc: "排除" }] },
{ name: "category", desc: "应用分类", ops: [{ op: "=", desc: "模糊匹配" }, { op: "==", desc: "精确匹配" }, { op: "!=", desc: "排除" }] },
{ name: "app_icp", desc: "应用 ICP 备案", ops: [{ op: "=", desc: "模糊匹配" }, { op: "==", desc: "精确匹配" }, { op: "!=", desc: "排除" }] },
{ name: "method", desc: "HTTP 方法 GET/POST/PUT/…", ops: [{ op: "==", desc: "精确匹配" }, { op: "!=", desc: "排除" }] },
{ name: "service_type", desc: "服务类型:http | other", ops: [{ op: "==", desc: "精确匹配" }, { op: "!=", desc: "排除" }] },
{ name: "record_type", desc: "DNS 解析类型 A/CNAME/MX/…", ops: [{ op: "==", desc: "精确匹配" }, { op: "!=", desc: "排除" }] },
@@ -216,6 +220,7 @@ const TABS: { key: string; label: string; icon: LucideIcon }[] = [
{ key: "root_domain", label: "根域名", icon: GlobeIcon },
{ key: "ip", label: "IP", icon: NetworkIcon },
{ key: "subdomain", label: "子域名", icon: GlobeIcon },
{ key: "app", label: "应用", icon: SmartphoneIcon },
{ key: "service", label: "服务", icon: LayoutTemplateIcon },
{ key: "endpoint", label: "接口", icon: LinkIcon },
];
@@ -609,6 +614,45 @@ export default function AssetsPage() {
</AssetCard>
</TabsContent>
{/* 应用 */}
<TabsContent value="app" className="mt-0 flex min-h-0 flex-1 flex-col gap-2">
{searchBox}
<AssetCard
cols={["", "应用名", "Bundle ID", "分类", "ICP 备案", ""]}
loaded={loaded}
total={total}
page={page}
size={size}
onSize={setSize}
onPage={setPage}
rows={rows}
selected={selected}
onToggleAll={(ids) => toggleSelectAll(ids)}
>
{slice(tabData("app")).map((a) => (
<TableRow key={a.id} className={selected.has(a.id) ? "bg-muted/40" : undefined}>
<TableCell className="w-8 pr-0">
<Checkbox checked={selected.has(a.id)} onCheckedChange={() => toggleSelect(a.id)} />
</TableCell>
<TableCell className="text-xs font-medium">{a.app_name || "—"}</TableCell>
<TableCell className="font-mono text-xs">{a.bundle_id || "—"}</TableCell>
<TableCell className="text-xs">{a.category || "—"}</TableCell>
<TableCell className="text-xs">{a.app_icp || "—"}</TableCell>
<TableCell className="w-8 pl-0">
<Button
variant="ghost"
size="icon"
className="size-7 text-muted-foreground hover:text-destructive"
onClick={() => openDelete([a.id])}
>
<Trash2Icon className="size-3.5" />
</Button>
</TableCell>
</TableRow>
))}
</AssetCard>
</TabsContent>
{/* 服务 */}
<TabsContent value="service" className="mt-0 flex min-h-0 flex-1 flex-col gap-2">
{searchBox}