Commit 23412965 authored by IanShaw027's avatar IanShaw027
Browse files

feat(frontend): 优化弹窗组件架构和用户体验

- 使用 BaseDialog 替代旧版 Modal 组件
- 添加平滑过渡动画和更好的可访问性支持
- 新增 ExportProgressDialog 导出进度弹窗
- 优化所有账号管理和使用记录相关弹窗
- 更新国际化文案,改进用户交互体验
- 精简依赖,减少 package.json 体积
parent c01db6b1
...@@ -186,7 +186,7 @@ func (r *accountRepository) BatchUpdateLastUsed(ctx context.Context, updates map ...@@ -186,7 +186,7 @@ func (r *accountRepository) BatchUpdateLastUsed(ctx context.Context, updates map
ids = append(ids, id) ids = append(ids, id)
} }
caseSql += " END WHERE id IN ?" caseSql += " END WHERE id IN ? AND deleted_at IS NULL"
args = append(args, ids) args = append(args, ids)
return r.db.WithContext(ctx).Exec(caseSql, args...).Error return r.db.WithContext(ctx).Exec(caseSql, args...).Error
......
...@@ -119,6 +119,7 @@ func (r *proxyRepository) CountAccountsByProxyID(ctx context.Context, proxyID in ...@@ -119,6 +119,7 @@ func (r *proxyRepository) CountAccountsByProxyID(ctx context.Context, proxyID in
var count int64 var count int64
err := r.db.WithContext(ctx).Table("accounts"). err := r.db.WithContext(ctx).Table("accounts").
Where("proxy_id = ?", proxyID). Where("proxy_id = ?", proxyID).
Where("deleted_at IS NULL").
Count(&count).Error Count(&count).Error
return count, err return count, err
} }
...@@ -134,6 +135,7 @@ func (r *proxyRepository) GetAccountCountsForProxies(ctx context.Context) (map[i ...@@ -134,6 +135,7 @@ func (r *proxyRepository) GetAccountCountsForProxies(ctx context.Context) (map[i
Table("accounts"). Table("accounts").
Select("proxy_id, COUNT(*) as count"). Select("proxy_id, COUNT(*) as count").
Where("proxy_id IS NOT NULL"). Where("proxy_id IS NOT NULL").
Where("deleted_at IS NULL").
Group("proxy_id"). Group("proxy_id").
Scan(&results).Error Scan(&results).Error
if err != nil { if err != nil {
......
...@@ -182,6 +182,7 @@ func (r *usageLogRepository) GetDashboardStats(ctx context.Context) (*DashboardS ...@@ -182,6 +182,7 @@ func (r *usageLogRepository) GetDashboardStats(ctx context.Context) (*DashboardS
COUNT(CASE WHEN rate_limited_at IS NOT NULL AND rate_limit_reset_at > ? THEN 1 END) as ratelimit_accounts, COUNT(CASE WHEN rate_limited_at IS NOT NULL AND rate_limit_reset_at > ? THEN 1 END) as ratelimit_accounts,
COUNT(CASE WHEN overload_until IS NOT NULL AND overload_until > ? THEN 1 END) as overload_accounts COUNT(CASE WHEN overload_until IS NOT NULL AND overload_until > ? THEN 1 END) as overload_accounts
FROM accounts FROM accounts
WHERE deleted_at IS NULL
`, service.StatusActive, service.StatusError, now, now).Scan(&accountStats).Error; err != nil { `, service.StatusActive, service.StatusError, now, now).Scan(&accountStats).Error; err != nil {
return nil, err return nil, err
} }
......
...@@ -11,13 +11,16 @@ ...@@ -11,13 +11,16 @@
"@vueuse/core": "^10.7.0", "@vueuse/core": "^10.7.0",
"axios": "^1.6.2", "axios": "^1.6.2",
"chart.js": "^4.4.1", "chart.js": "^4.4.1",
"file-saver": "^2.0.5",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"vue": "^3.4.0", "vue": "^3.4.0",
"vue-chartjs": "^5.3.0", "vue-chartjs": "^5.3.0",
"vue-i18n": "^9.14.5", "vue-i18n": "^9.14.5",
"vue-router": "^4.2.5" "vue-router": "^4.2.5",
"xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@types/file-saver": "^2.0.7",
"@types/node": "^20.10.5", "@types/node": "^20.10.5",
"@vitejs/plugin-vue": "^5.2.3", "@vitejs/plugin-vue": "^5.2.3",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
...@@ -494,180 +497,6 @@ ...@@ -494,180 +497,6 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
"integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
},
"peerDependencies": {
"eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
}
},
"node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint-community/regexpp": {
"version": "4.12.2",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
"integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
},
"node_modules/@eslint/eslintrc": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
"integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
"espree": "^9.6.0",
"globals": "^13.19.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"optional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/@eslint/js": {
"version": "8.57.1",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
"integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
"integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
"deprecated": "Use @eslint/config-array instead",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.3",
"debug": "^4.3.1",
"minimatch": "^3.0.5"
},
"engines": {
"node": ">=10.10.0"
}
},
"node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"optional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/@humanwhocodes/module-importer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": ">=12.22"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@humanwhocodes/object-schema": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
"integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
"deprecated": "Use @eslint/object-schema instead",
"dev": true,
"license": "BSD-3-Clause",
"optional": true
},
"node_modules/@intlify/core-base": { "node_modules/@intlify/core-base": {
"version": "9.14.5", "version": "9.14.5",
"resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.14.5.tgz", "resolved": "https://registry.npmmirror.com/@intlify/core-base/-/core-base-9.14.5.tgz",
...@@ -1109,6 +938,13 @@ ...@@ -1109,6 +938,13 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/file-saver": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/@types/file-saver/-/file-saver-2.0.7.tgz",
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "20.19.27", "version": "20.19.27",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.27.tgz", "resolved": "https://registry.npmmirror.com/@types/node/-/node-20.19.27.tgz",
...@@ -1126,14 +962,6 @@ ...@@ -1126,14 +962,6 @@
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==", "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/@ungap/structured-clone": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"dev": true,
"license": "ISC",
"optional": true
},
"node_modules/@vitejs/plugin-vue": { "node_modules/@vitejs/plugin-vue": {
"version": "5.2.4", "version": "5.2.4",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
...@@ -1355,33 +1183,13 @@ ...@@ -1355,33 +1183,13 @@
"url": "https://github.com/sponsors/antfu" "url": "https://github.com/sponsors/antfu"
} }
}, },
"node_modules/acorn-jsx": { "node_modules/adler-32": {
"version": "5.3.2", "version": "1.3.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", "resolved": "https://registry.npmmirror.com/adler-32/-/adler-32-1.3.1.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
"dev": true, "license": "Apache-2.0",
"license": "MIT", "engines": {
"optional": true, "node": ">=0.8"
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/epoberezkin"
} }
}, },
"node_modules/alien-signals": { "node_modules/alien-signals": {
...@@ -1404,23 +1212,6 @@ ...@@ -1404,23 +1212,6 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1" "url": "https://github.com/chalk/ansi-regex?sponsor=1"
} }
}, },
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/any-promise": { "node_modules/any-promise": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz", "resolved": "https://registry.npmmirror.com/any-promise/-/any-promise-1.3.0.tgz",
...@@ -1449,14 +1240,6 @@ ...@@ -1449,14 +1240,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"dev": true,
"license": "Python-2.0",
"optional": true
},
"node_modules/asynckit": { "node_modules/asynckit": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmmirror.com/asynckit/-/asynckit-0.4.0.tgz",
...@@ -1612,17 +1395,6 @@ ...@@ -1612,17 +1395,6 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/callsites": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=6"
}
},
"node_modules/camelcase-css": { "node_modules/camelcase-css": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/camelcase-css/-/camelcase-css-2.0.1.tgz",
...@@ -1654,22 +1426,17 @@ ...@@ -1654,22 +1426,17 @@
], ],
"license": "CC-BY-4.0" "license": "CC-BY-4.0"
}, },
"node_modules/chalk": { "node_modules/cfb": {
"version": "4.1.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "resolved": "https://registry.npmmirror.com/cfb/-/cfb-1.2.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
"dev": true, "license": "Apache-2.0",
"license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"ansi-styles": "^4.1.0", "adler-32": "~1.3.0",
"supports-color": "^7.1.0" "crc-32": "~1.2.0"
}, },
"engines": { "engines": {
"node": ">=10" "node": ">=0.8"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
} }
}, },
"node_modules/chart.js": { "node_modules/chart.js": {
...@@ -1723,28 +1490,15 @@ ...@@ -1723,28 +1490,15 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/color-convert": { "node_modules/codepage": {
"version": "2.0.1", "version": "1.15.0",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "resolved": "https://registry.npmmirror.com/codepage/-/codepage-1.15.0.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
"dev": true, "license": "Apache-2.0",
"license": "MIT",
"optional": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": { "engines": {
"node": ">=7.0.0" "node": ">=0.8"
} }
}, },
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/combined-stream": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmmirror.com/combined-stream/-/combined-stream-1.0.8.tgz",
...@@ -1767,39 +1521,16 @@ ...@@ -1767,39 +1521,16 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/concat-map": { "node_modules/crc-32": {
"version": "0.0.1", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmmirror.com/crc-32/-/crc-32-1.2.2.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
"dev": true, "license": "Apache-2.0",
"license": "MIT", "bin": {
"optional": true "crc32": "bin/crc32.njs"
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
}, },
"node_modules/cross-spawn/node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": { "engines": {
"node": ">=8" "node": ">=0.8"
} }
}, },
"node_modules/cssesc": { "node_modules/cssesc": {
...@@ -1828,33 +1559,6 @@ ...@@ -1828,33 +1559,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz", "resolved": "https://registry.npmmirror.com/delayed-stream/-/delayed-stream-1.0.0.tgz",
...@@ -1878,20 +1582,6 @@ ...@@ -1878,20 +1582,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"dependencies": {
"esutils": "^2.0.2"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/dunder-proto": { "node_modules/dunder-proto": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz", "resolved": "https://registry.npmmirror.com/dunder-proto/-/dunder-proto-1.0.1.tgz",
...@@ -2019,204 +1709,57 @@ ...@@ -2019,204 +1709,57 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/escape-string-regexp": { "node_modules/estree-walker": {
"version": "4.0.0", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"dev": true, "license": "MIT"
"license": "MIT",
"optional": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}, },
"node_modules/eslint-scope": { "node_modules/fast-glob": {
"version": "7.2.2", "version": "3.3.3",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "MIT",
"optional": true,
"dependencies": { "dependencies": {
"esrecurse": "^4.3.0", "@nodelib/fs.stat": "^2.0.2",
"estraverse": "^5.2.0" "@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.8"
}, },
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": ">=8.6.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/espree": { "node_modules/fast-glob/node_modules/glob-parent": {
"version": "9.6.1", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true, "dev": true,
"license": "BSD-2-Clause", "license": "ISC",
"optional": true,
"dependencies": { "dependencies": {
"acorn": "^8.9.0", "is-glob": "^4.0.1"
"acorn-jsx": "^5.3.2",
"eslint-visitor-keys": "^3.4.1"
}, },
"engines": { "engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "node": ">= 6"
},
"funding": {
"url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/espree/node_modules/eslint-visitor-keys": { "node_modules/fastq": {
"version": "3.4.3", "version": "1.19.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"dev": true,
"license": "Apache-2.0",
"optional": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
"funding": {
"url": "https://opencollective.com/eslint"
}
},
"node_modules/esquery": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz",
"integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==",
"dev": true,
"license": "BSD-3-Clause",
"optional": true,
"dependencies": {
"estraverse": "^5.1.0"
},
"engines": {
"node": ">=0.10"
}
},
"node_modules/esrecurse": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"dependencies": {
"estraverse": "^5.2.0"
},
"engines": {
"node": ">=4.0"
}
},
"node_modules/estraverse": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"engines": {
"node": ">=4.0"
}
},
"node_modules/estree-walker": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/esutils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/fast-glob/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
"integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/fastq": {
"version": "1.19.1",
"resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.19.1.tgz",
"integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"reusify": "^1.0.4" "reusify": "^1.0.4"
} }
}, },
"node_modules/file-entry-cache": { "node_modules/file-saver": {
"version": "6.0.1", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", "resolved": "https://registry.npmmirror.com/file-saver/-/file-saver-2.0.5.tgz",
"integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
"dev": true, "license": "MIT"
"license": "MIT",
"optional": true,
"dependencies": {
"flat-cache": "^3.0.4"
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
}
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
...@@ -2231,48 +1774,6 @@ ...@@ -2231,48 +1774,6 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/flat-cache": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
"integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"flatted": "^3.2.9",
"keyv": "^4.5.3",
"rimraf": "^3.0.2"
},
"engines": {
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/flatted": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"dev": true,
"license": "ISC",
"optional": true
},
"node_modules/follow-redirects": { "node_modules/follow-redirects": {
"version": "1.15.11", "version": "1.15.11",
"resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz", "resolved": "https://registry.npmmirror.com/follow-redirects/-/follow-redirects-1.15.11.tgz",
...@@ -2309,6 +1810,15 @@ ...@@ -2309,6 +1810,15 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/frac": {
"version": "1.1.2",
"resolved": "https://registry.npmmirror.com/frac/-/frac-1.1.2.tgz",
"integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.8"
}
},
"node_modules/fraction.js": { "node_modules/fraction.js": {
"version": "5.3.4", "version": "5.3.4",
"resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz", "resolved": "https://registry.npmmirror.com/fraction.js/-/fraction.js-5.3.4.tgz",
...@@ -2323,14 +1833,6 @@ ...@@ -2323,14 +1833,6 @@
"url": "https://github.com/sponsors/rawify" "url": "https://github.com/sponsors/rawify"
} }
}, },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"dev": true,
"license": "ISC",
"optional": true
},
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.3", "version": "2.3.3",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
...@@ -2392,29 +1894,6 @@ ...@@ -2392,29 +1894,6 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dev": true,
"license": "ISC",
"optional": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": { "node_modules/glob-parent": {
"version": "6.0.2", "version": "6.0.2",
"resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz", "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-6.0.2.tgz",
...@@ -2428,49 +1907,6 @@ ...@@ -2428,49 +1907,6 @@
"node": ">=10.13.0" "node": ">=10.13.0"
} }
}, },
"node_modules/glob/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"optional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/globals": {
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
"integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"type-fest": "^0.20.2"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gopd": { "node_modules/gopd": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz", "resolved": "https://registry.npmmirror.com/gopd/-/gopd-1.2.0.tgz",
...@@ -2483,25 +1919,6 @@ ...@@ -2483,25 +1919,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/has-symbols": { "node_modules/has-symbols": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/has-symbols/-/has-symbols-1.1.0.tgz",
...@@ -2551,67 +1968,6 @@ ...@@ -2551,67 +1968,6 @@
"he": "bin/he" "he": "bin/he"
} }
}, },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 4"
}
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.8.19"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"dev": true,
"license": "ISC",
"optional": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"dev": true,
"license": "ISC",
"optional": true
},
"node_modules/is-binary-path": { "node_modules/is-binary-path": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz", "resolved": "https://registry.npmmirror.com/is-binary-path/-/is-binary-path-2.1.0.tgz",
...@@ -2674,25 +2030,6 @@ ...@@ -2674,25 +2030,6 @@
"node": ">=0.12.0" "node": ">=0.12.0"
} }
}, },
"node_modules/is-path-inside": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true,
"license": "ISC",
"optional": true
},
"node_modules/jiti": { "node_modules/jiti": {
"version": "1.21.7", "version": "1.21.7",
"resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz", "resolved": "https://registry.npmmirror.com/jiti/-/jiti-1.21.7.tgz",
...@@ -2711,70 +2048,6 @@ ...@@ -2711,70 +2048,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
"integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"json-buffer": "3.0.1"
}
},
"node_modules/levn": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/lilconfig": { "node_modules/lilconfig": {
"version": "3.1.3", "version": "3.1.3",
"resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz", "resolved": "https://registry.npmmirror.com/lilconfig/-/lilconfig-3.1.3.tgz",
...@@ -2795,31 +2068,6 @@ ...@@ -2795,31 +2068,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"p-locate": "^5.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/magic-string": { "node_modules/magic-string": {
"version": "0.30.21", "version": "0.30.21",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
...@@ -2899,14 +2147,6 @@ ...@@ -2899,14 +2147,6 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/muggle-string": { "node_modules/muggle-string": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz", "resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz",
...@@ -2944,14 +2184,6 @@ ...@@ -2944,14 +2184,6 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
"integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.27", "version": "2.0.27",
"resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz", "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.27.tgz",
...@@ -3006,84 +2238,6 @@ ...@@ -3006,84 +2238,6 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dev": true,
"license": "ISC",
"optional": true,
"dependencies": {
"wrappy": "1"
}
},
"node_modules/optionator": {
"version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
"integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"deep-is": "^0.1.3",
"fast-levenshtein": "^2.0.6",
"levn": "^0.4.1",
"prelude-ls": "^1.2.1",
"type-check": "^0.4.0",
"word-wrap": "^1.2.5"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"p-limit": "^3.0.2"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"callsites": "^3.0.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/path-browserify": { "node_modules/path-browserify": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz", "resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
...@@ -3091,28 +2245,6 @@ ...@@ -3091,28 +2245,6 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-key": { "node_modules/path-key": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz", "resolved": "https://registry.npmmirror.com/path-key/-/path-key-4.0.0.tgz",
...@@ -3357,34 +2489,12 @@ ...@@ -3357,34 +2489,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/proxy-from-env": { "node_modules/proxy-from-env": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=6"
}
},
"node_modules/queue-microtask": { "node_modules/queue-microtask": {
"version": "1.2.3", "version": "1.2.3",
"resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz",
...@@ -3450,17 +2560,6 @@ ...@@ -3450,17 +2560,6 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=4"
}
},
"node_modules/reusify": { "node_modules/reusify": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz",
...@@ -3472,24 +2571,6 @@ ...@@ -3472,24 +2571,6 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dev": true,
"license": "ISC",
"optional": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.53.5", "version": "4.53.5",
"resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.53.5.tgz", "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.53.5.tgz",
...@@ -3556,31 +2637,6 @@ ...@@ -3556,31 +2637,6 @@
"queue-microtask": "^1.2.2" "queue-microtask": "^1.2.2"
} }
}, },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
...@@ -3590,6 +2646,18 @@ ...@@ -3590,6 +2646,18 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/ssf": {
"version": "0.11.2",
"resolved": "https://registry.npmmirror.com/ssf/-/ssf-0.11.2.tgz",
"integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
"license": "Apache-2.0",
"dependencies": {
"frac": "~1.1.2"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/strip-ansi": { "node_modules/strip-ansi": {
"version": "7.1.2", "version": "7.1.2",
"resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.2.tgz", "resolved": "https://registry.npmmirror.com/strip-ansi/-/strip-ansi-7.1.2.tgz",
...@@ -3606,20 +2674,6 @@ ...@@ -3606,20 +2674,6 @@
"url": "https://github.com/chalk/strip-ansi?sponsor=1" "url": "https://github.com/chalk/strip-ansi?sponsor=1"
} }
}, },
"node_modules/strip-json-comments": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/sucrase": { "node_modules/sucrase": {
"version": "3.35.1", "version": "3.35.1",
"resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz", "resolved": "https://registry.npmmirror.com/sucrase/-/sucrase-3.35.1.tgz",
...@@ -3643,20 +2697,6 @@ ...@@ -3643,20 +2697,6 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/supports-preserve-symlinks-flag": { "node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "resolved": "https://registry.npmmirror.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
...@@ -3708,14 +2748,6 @@ ...@@ -3708,14 +2748,6 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
"dev": true,
"license": "MIT",
"optional": true
},
"node_modules/thenify": { "node_modules/thenify": {
"version": "3.3.1", "version": "3.3.1",
"resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz",
...@@ -3815,34 +2847,6 @@ ...@@ -3815,34 +2847,6 @@
"dev": true, "dev": true,
"license": "Apache-2.0" "license": "Apache-2.0"
}, },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"prelude-ls": "^1.2.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/type-fest": {
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
"dev": true,
"license": "(MIT OR CC0-1.0)",
"optional": true,
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/typescript": { "node_modules/typescript": {
"version": "5.6.3", "version": "5.6.3",
"resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.6.3.tgz", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.6.3.tgz",
...@@ -3909,17 +2913,6 @@ ...@@ -3909,17 +2913,6 @@
"browserslist": ">= 4.21.0" "browserslist": ">= 4.21.0"
} }
}, },
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
"dev": true,
"license": "BSD-2-Clause",
"optional": true,
"dependencies": {
"punycode": "^2.1.0"
}
},
"node_modules/util-deprecate": { "node_modules/util-deprecate": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/util-deprecate/-/util-deprecate-1.0.2.tgz",
...@@ -4211,54 +3204,43 @@ ...@@ -4211,54 +3204,43 @@
"typescript": ">=5.0.0" "typescript": ">=5.0.0"
} }
}, },
"node_modules/which": { "node_modules/wmf": {
"version": "2.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmmirror.com/wmf/-/wmf-1.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
"dev": true, "license": "Apache-2.0",
"license": "ISC",
"optional": true,
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": { "engines": {
"node": ">= 8" "node": ">=0.8"
} }
}, },
"node_modules/word-wrap": { "node_modules/word": {
"version": "1.2.5", "version": "0.3.0",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "resolved": "https://registry.npmmirror.com/word/-/word-0.3.0.tgz",
"integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
"dev": true, "license": "Apache-2.0",
"license": "MIT",
"optional": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.8"
} }
}, },
"node_modules/wrappy": { "node_modules/xlsx": {
"version": "1.0.2", "version": "0.18.5",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmmirror.com/xlsx/-/xlsx-0.18.5.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
"dev": true, "license": "Apache-2.0",
"license": "ISC", "dependencies": {
"optional": true "adler-32": "~1.3.0",
"cfb": "~1.2.1",
"codepage": "~1.15.0",
"crc-32": "~1.2.1",
"ssf": "~0.11.2",
"wmf": "~1.0.1",
"word": "~0.3.0"
}, },
"node_modules/yocto-queue": { "bin": {
"version": "0.1.0", "xlsx": "bin/xlsx.njs"
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=10"
}, },
"funding": { "engines": {
"url": "https://github.com/sponsors/sindresorhus" "node": ">=0.8"
} }
} }
} }
......
...@@ -14,13 +14,16 @@ ...@@ -14,13 +14,16 @@
"@vueuse/core": "^10.7.0", "@vueuse/core": "^10.7.0",
"axios": "^1.6.2", "axios": "^1.6.2",
"chart.js": "^4.4.1", "chart.js": "^4.4.1",
"file-saver": "^2.0.5",
"pinia": "^2.1.7", "pinia": "^2.1.7",
"vue": "^3.4.0", "vue": "^3.4.0",
"vue-chartjs": "^5.3.0", "vue-chartjs": "^5.3.0",
"vue-i18n": "^9.14.5", "vue-i18n": "^9.14.5",
"vue-router": "^4.2.5" "vue-router": "^4.2.5",
"xlsx": "^0.18.5"
}, },
"devDependencies": { "devDependencies": {
"@types/file-saver": "^2.0.7",
"@types/node": "^20.10.5", "@types/node": "^20.10.5",
"@vitejs/plugin-vue": "^5.2.3", "@vitejs/plugin-vue": "^5.2.3",
"autoprefixer": "^10.4.16", "autoprefixer": "^10.4.16",
......
...@@ -362,6 +362,10 @@ const resetState = () => { ...@@ -362,6 +362,10 @@ const resetState = () => {
} }
const handleClose = () => { const handleClose = () => {
// 防止在连接测试进行中关闭对话框
if (status.value === 'connecting') {
return
}
closeEventSource() closeEventSource()
emit('close') emit('close')
} }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<BaseDialog <BaseDialog
:show="show" :show="show"
:title="t('admin.accounts.createAccount')" :title="t('admin.accounts.createAccount')"
width="wide" width="normal"
@close="handleClose" @close="handleClose"
> >
<!-- Step Indicator for OAuth accounts --> <!-- Step Indicator for OAuth accounts -->
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<BaseDialog <BaseDialog
:show="show" :show="show"
:title="t('admin.accounts.editAccount')" :title="t('admin.accounts.editAccount')"
width="wide" width="normal"
@close="handleClose" @close="handleClose"
> >
<form <form
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<BaseDialog <BaseDialog
:show="show" :show="show"
:title="t('admin.accounts.reAuthorizeAccount')" :title="t('admin.accounts.reAuthorizeAccount')"
width="wide" width="normal"
@close="handleClose" @close="handleClose"
> >
<div v-if="account" class="space-y-4"> <div v-if="account" class="space-y-4">
......
...@@ -151,6 +151,10 @@ watch( ...@@ -151,6 +151,10 @@ watch(
) )
const handleClose = () => { const handleClose = () => {
// 防止在同步进行中关闭对话框
if (syncing.value) {
return
}
emit('close') emit('close')
} }
......
<template> <template>
<Teleport to="body"> <Teleport to="body">
<Transition name="modal">
<div <div
v-if="show" v-if="show"
class="modal-overlay" class="modal-overlay"
aria-labelledby="modal-title" :aria-labelledby="dialogId"
role="dialog" role="dialog"
aria-modal="true" aria-modal="true"
@click.self="handleClose" @click.self="handleClose"
> >
<!-- Modal panel --> <!-- Modal panel -->
<div :class="['modal-content', widthClasses]" @click.stop> <div ref="dialogRef" :class="['modal-content', widthClasses]" @click.stop>
<!-- Header --> <!-- Header -->
<div class="modal-header"> <div class="modal-header">
<h3 id="modal-title" class="modal-title"> <h3 :id="dialogId" class="modal-title">
{{ title }} {{ title }}
</h3> </h3>
<button <button
...@@ -43,11 +44,20 @@ ...@@ -43,11 +44,20 @@
</div> </div>
</div> </div>
</div> </div>
</Transition>
</Teleport> </Teleport>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, watch, onMounted, onUnmounted } from 'vue' import { computed, watch, onMounted, onUnmounted, ref, nextTick } from 'vue'
// 生成唯一ID以避免多个对话框时ID冲突
let dialogIdCounter = 0
const dialogId = `modal-title-${++dialogIdCounter}`
// 焦点管理
const dialogRef = ref<HTMLElement | null>(null)
let previousActiveElement: HTMLElement | null = null
type DialogWidth = 'narrow' | 'normal' | 'wide' | 'extra-wide' | 'full' type DialogWidth = 'narrow' | 'normal' | 'wide' | 'extra-wide' | 'full'
...@@ -72,12 +82,15 @@ const props = withDefaults(defineProps<Props>(), { ...@@ -72,12 +82,15 @@ const props = withDefaults(defineProps<Props>(), {
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
const widthClasses = computed(() => { const widthClasses = computed(() => {
// Width guidance: narrow=confirm/short prompts, normal=standard forms,
// wide=multi-section forms or rich content, extra-wide=analytics/tables,
// full=full-screen or very dense layouts.
const widths: Record<DialogWidth, string> = { const widths: Record<DialogWidth, string> = {
narrow: 'max-w-md', narrow: 'max-w-md',
normal: 'max-w-lg', normal: 'max-w-lg',
wide: 'max-w-4xl', wide: 'w-full sm:max-w-2xl md:max-w-3xl lg:max-w-4xl',
'extra-wide': 'max-w-6xl', 'extra-wide': 'w-full sm:max-w-3xl md:max-w-4xl lg:max-w-5xl xl:max-w-6xl',
full: 'max-w-7xl' full: 'w-full sm:max-w-4xl md:max-w-5xl lg:max-w-6xl xl:max-w-7xl'
} }
return widths[props.width] return widths[props.width]
}) })
...@@ -94,14 +107,31 @@ const handleEscape = (event: KeyboardEvent) => { ...@@ -94,14 +107,31 @@ const handleEscape = (event: KeyboardEvent) => {
} }
} }
// Prevent body scroll when modal is open // Prevent body scroll when modal is open and manage focus
watch( watch(
() => props.show, () => props.show,
(isOpen) => { async (isOpen) => {
if (isOpen) { if (isOpen) {
document.body.style.overflow = 'hidden' // 保存当前焦点元素
previousActiveElement = document.activeElement as HTMLElement
// 使用CSS类而不是直接操作style,更易于管理多个对话框
document.body.classList.add('modal-open')
// 等待DOM更新后设置焦点到对话框
await nextTick()
if (dialogRef.value) {
const firstFocusable = dialogRef.value.querySelector<HTMLElement>(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
)
firstFocusable?.focus()
}
} else { } else {
document.body.style.overflow = '' document.body.classList.remove('modal-open')
// 恢复之前的焦点
if (previousActiveElement && typeof previousActiveElement.focus === 'function') {
previousActiveElement.focus()
}
previousActiveElement = null
} }
}, },
{ immediate: true } { immediate: true }
...@@ -113,6 +143,7 @@ onMounted(() => { ...@@ -113,6 +143,7 @@ onMounted(() => {
onUnmounted(() => { onUnmounted(() => {
document.removeEventListener('keydown', handleEscape) document.removeEventListener('keydown', handleEscape)
document.body.style.overflow = '' // 确保组件卸载时移除滚动锁定
document.body.classList.remove('modal-open')
}) })
</script> </script>
<template>
<BaseDialog :show="show" :title="t('usage.exporting')" width="narrow" @close="handleCancel">
<div class="space-y-4">
<div class="text-sm text-gray-600 dark:text-gray-400">
{{ t('usage.exportingProgress') }}
</div>
<div class="flex items-center justify-between text-sm text-gray-700 dark:text-gray-300">
<span>{{ t('usage.exportedCount', { current, total }) }}</span>
<span class="font-medium text-gray-900 dark:text-white">{{ normalizedProgress }}%</span>
</div>
<div class="h-2 w-full rounded-full bg-gray-200 dark:bg-dark-700">
<div
role="progressbar"
:aria-valuenow="normalizedProgress"
aria-valuemin="0"
aria-valuemax="100"
:aria-label="`${t('usage.exportingProgress')}: ${normalizedProgress}%`"
class="h-2 rounded-full bg-primary-600 transition-all"
:style="{ width: `${normalizedProgress}%` }"
></div>
</div>
<div v-if="estimatedTime" class="text-xs text-gray-500 dark:text-gray-400" aria-live="polite" aria-atomic="true">
{{ t('usage.estimatedTime', { time: estimatedTime }) }}
</div>
</div>
<template #footer>
<button
@click="handleCancel"
type="button"
class="rounded-md border border-gray-300 bg-white px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2 dark:border-dark-600 dark:bg-dark-700 dark:text-gray-200 dark:hover:bg-dark-600 dark:focus:ring-offset-dark-800"
>
{{ t('usage.cancelExport') }}
</button>
</template>
</BaseDialog>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import BaseDialog from './BaseDialog.vue'
interface Props {
show: boolean
progress: number
current: number
total: number
estimatedTime: string
}
interface Emits {
(e: 'cancel'): void
}
const props = defineProps<Props>()
const emit = defineEmits<Emits>()
const { t } = useI18n()
const normalizedProgress = computed(() => {
const value = Number.isFinite(props.progress) ? props.progress : 0
return Math.min(100, Math.max(0, Math.round(value)))
})
const handleCancel = () => {
emit('cancel')
}
</script>
<template>
<Teleport to="body">
<div
v-if="show"
class="modal-overlay"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true"
@click.self="handleClose"
>
<!-- Modal panel -->
<div :class="['modal-content', sizeClasses]" @click.stop>
<!-- Header -->
<div class="modal-header">
<h3 id="modal-title" class="modal-title">
{{ title }}
</h3>
<button
@click="emit('close')"
class="-mr-2 rounded-xl p-2 text-gray-400 transition-colors hover:bg-gray-100 hover:text-gray-600 dark:text-dark-500 dark:hover:bg-dark-700 dark:hover:text-dark-300"
aria-label="Close modal"
>
<svg
class="h-5 w-5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
stroke-width="1.5"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
</div>
<!-- Body -->
<div class="modal-body">
<slot></slot>
</div>
<!-- Footer -->
<div v-if="$slots.footer" class="modal-footer">
<slot name="footer"></slot>
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { computed, watch, onMounted, onUnmounted } from 'vue'
type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | '2xl' | 'full'
interface Props {
show: boolean
title: string
size?: ModalSize
closeOnEscape?: boolean
closeOnClickOutside?: boolean
}
interface Emits {
(e: 'close'): void
}
const props = withDefaults(defineProps<Props>(), {
size: 'md',
closeOnEscape: true,
closeOnClickOutside: false
})
const emit = defineEmits<Emits>()
const sizeClasses = computed(() => {
const sizes: Record<ModalSize, string> = {
sm: 'max-w-sm',
md: 'max-w-md',
lg: 'max-w-lg',
xl: 'max-w-xl',
'2xl': 'max-w-5xl',
full: 'max-w-4xl'
}
return sizes[props.size]
})
const handleClose = () => {
if (props.closeOnClickOutside) {
emit('close')
}
}
const handleEscape = (event: KeyboardEvent) => {
if (props.show && props.closeOnEscape && event.key === 'Escape') {
emit('close')
}
}
// Prevent body scroll when modal is open
watch(
() => props.show,
(isOpen) => {
console.log('[Modal] show changed to:', isOpen)
if (isOpen) {
document.body.style.overflow = 'hidden'
} else {
document.body.style.overflow = ''
}
},
{ immediate: true }
)
onMounted(() => {
document.addEventListener('keydown', handleEscape)
})
onUnmounted(() => {
document.removeEventListener('keydown', handleEscape)
document.body.style.overflow = ''
})
</script>
// Export all common components // Export all common components
export { default as DataTable } from './DataTable.vue' export { default as DataTable } from './DataTable.vue'
export { default as Pagination } from './Pagination.vue' export { default as Pagination } from './Pagination.vue'
export { default as Modal } from './Modal.vue'
export { default as BaseDialog } from './BaseDialog.vue' export { default as BaseDialog } from './BaseDialog.vue'
export { default as ConfirmDialog } from './ConfirmDialog.vue' export { default as ConfirmDialog } from './ConfirmDialog.vue'
export { default as StatCard } from './StatCard.vue' export { default as StatCard } from './StatCard.vue'
...@@ -9,6 +8,7 @@ export { default as Toast } from './Toast.vue' ...@@ -9,6 +8,7 @@ export { default as Toast } from './Toast.vue'
export { default as LoadingSpinner } from './LoadingSpinner.vue' export { default as LoadingSpinner } from './LoadingSpinner.vue'
export { default as EmptyState } from './EmptyState.vue' export { default as EmptyState } from './EmptyState.vue'
export { default as LocaleSwitcher } from './LocaleSwitcher.vue' export { default as LocaleSwitcher } from './LocaleSwitcher.vue'
export { default as ExportProgressDialog } from './ExportProgressDialog.vue'
// Export types // Export types
export type { Column } from './types' export type { Column } from './types'
...@@ -326,7 +326,8 @@ export default { ...@@ -326,7 +326,8 @@ export default {
customKeyHint: 'Only letters, numbers, underscores and hyphens allowed. Minimum 16 characters.', customKeyHint: 'Only letters, numbers, underscores and hyphens allowed. Minimum 16 characters.',
customKeyTooShort: 'Custom key must be at least 16 characters', customKeyTooShort: 'Custom key must be at least 16 characters',
customKeyInvalidChars: 'Custom key can only contain letters, numbers, underscores, and hyphens', customKeyInvalidChars: 'Custom key can only contain letters, numbers, underscores, and hyphens',
customKeyRequired: 'Please enter a custom key' customKeyRequired: 'Please enter a custom key',
ccSwitchNotInstalled: 'CC-Switch is not installed or the protocol handler is not registered. Please install CC-Switch first or manually copy the API key.'
}, },
// Usage // Usage
...@@ -345,6 +346,12 @@ export default { ...@@ -345,6 +346,12 @@ export default {
allApiKeys: 'All API Keys', allApiKeys: 'All API Keys',
timeRange: 'Time Range', timeRange: 'Time Range',
exportCsv: 'Export CSV', exportCsv: 'Export CSV',
exportExcel: 'Export Excel',
exportingProgress: 'Exporting data...',
exportedCount: 'Exported {current}/{total} records',
estimatedTime: 'Estimated time remaining: {time}',
cancelExport: 'Cancel Export',
exportCancelled: 'Export cancelled',
exporting: 'Exporting...', exporting: 'Exporting...',
preparingExport: 'Preparing export...', preparingExport: 'Preparing export...',
model: 'Model', model: 'Model',
...@@ -368,6 +375,8 @@ export default { ...@@ -368,6 +375,8 @@ export default {
noDataToExport: 'No data to export', noDataToExport: 'No data to export',
exportSuccess: 'Usage data exported successfully', exportSuccess: 'Usage data exported successfully',
exportFailed: 'Failed to export usage data', exportFailed: 'Failed to export usage data',
exportExcelSuccess: 'Usage data exported successfully (Excel format)',
exportExcelFailed: 'Failed to export usage data',
billingType: 'Billing', billingType: 'Billing',
balance: 'Balance', balance: 'Balance',
subscription: 'Subscription' subscription: 'Subscription'
...@@ -1291,6 +1300,7 @@ export default { ...@@ -1291,6 +1300,7 @@ export default {
account: 'Account', account: 'Account',
group: 'Group', group: 'Group',
requestId: 'Request ID', requestId: 'Request ID',
requestIdCopied: 'Request ID copied',
allModels: 'All Models', allModels: 'All Models',
allAccounts: 'All Accounts', allAccounts: 'All Accounts',
allGroups: 'All Groups', allGroups: 'All Groups',
...@@ -1300,6 +1310,10 @@ export default { ...@@ -1300,6 +1310,10 @@ export default {
outputCost: 'Output Cost', outputCost: 'Output Cost',
cacheCreationCost: 'Cache Creation Cost', cacheCreationCost: 'Cache Creation Cost',
cacheReadCost: 'Cache Read Cost', cacheReadCost: 'Cache Read Cost',
inputTokens: 'Input Tokens',
outputTokens: 'Output Tokens',
cacheCreationTokens: 'Cache Creation Tokens',
cacheReadTokens: 'Cache Read Tokens',
failedToLoad: 'Failed to load usage records' failedToLoad: 'Failed to load usage records'
}, },
......
...@@ -322,7 +322,8 @@ export default { ...@@ -322,7 +322,8 @@ export default {
customKeyHint: '仅允许字母、数字、下划线和连字符,最少16个字符。', customKeyHint: '仅允许字母、数字、下划线和连字符,最少16个字符。',
customKeyTooShort: '自定义密钥至少需要16个字符', customKeyTooShort: '自定义密钥至少需要16个字符',
customKeyInvalidChars: '自定义密钥只能包含字母、数字、下划线和连字符', customKeyInvalidChars: '自定义密钥只能包含字母、数字、下划线和连字符',
customKeyRequired: '请输入自定义密钥' customKeyRequired: '请输入自定义密钥',
ccSwitchNotInstalled: 'CC-Switch 未安装或协议处理程序未注册。请先安装 CC-Switch 或手动复制 API 密钥。'
}, },
// Usage // Usage
...@@ -341,6 +342,12 @@ export default { ...@@ -341,6 +342,12 @@ export default {
allApiKeys: '全部密钥', allApiKeys: '全部密钥',
timeRange: '时间范围', timeRange: '时间范围',
exportCsv: '导出 CSV', exportCsv: '导出 CSV',
exportExcel: '导出 Excel',
exportingProgress: '正在导出数据...',
exportedCount: '已导出 {current}/{total} 条',
estimatedTime: '预计剩余时间:{time}',
cancelExport: '取消导出',
exportCancelled: '导出已取消',
exporting: '导出中...', exporting: '导出中...',
preparingExport: '正在准备导出...', preparingExport: '正在准备导出...',
model: '模型', model: '模型',
...@@ -364,6 +371,8 @@ export default { ...@@ -364,6 +371,8 @@ export default {
noDataToExport: '没有可导出的数据', noDataToExport: '没有可导出的数据',
exportSuccess: '使用数据导出成功', exportSuccess: '使用数据导出成功',
exportFailed: '使用数据导出失败', exportFailed: '使用数据导出失败',
exportExcelSuccess: '使用数据导出成功(Excel格式)',
exportExcelFailed: '使用数据导出失败',
billingType: '消费类型', billingType: '消费类型',
balance: '余额', balance: '余额',
subscription: '订阅' subscription: '订阅'
...@@ -1490,6 +1499,7 @@ export default { ...@@ -1490,6 +1499,7 @@ export default {
account: '账户', account: '账户',
group: '分组', group: '分组',
requestId: '请求ID', requestId: '请求ID',
requestIdCopied: '请求ID已复制',
allModels: '全部模型', allModels: '全部模型',
allAccounts: '全部账户', allAccounts: '全部账户',
allGroups: '全部分组', allGroups: '全部分组',
...@@ -1499,6 +1509,10 @@ export default { ...@@ -1499,6 +1509,10 @@ export default {
outputCost: '输出成本', outputCost: '输出成本',
cacheCreationCost: '缓存创建成本', cacheCreationCost: '缓存创建成本',
cacheReadCost: '缓存读取成本', cacheReadCost: '缓存读取成本',
inputTokens: '输入 Token',
outputTokens: '输出 Token',
cacheCreationTokens: '缓存创建 Token',
cacheReadTokens: '缓存读取 Token',
failedToLoad: '加载使用记录失败' failedToLoad: '加载使用记录失败'
}, },
......
...@@ -79,6 +79,20 @@ ...@@ -79,6 +79,20 @@
@apply hover:from-red-600 hover:to-red-700 hover:shadow-lg hover:shadow-red-500/30; @apply hover:from-red-600 hover:to-red-700 hover:shadow-lg hover:shadow-red-500/30;
} }
.btn-success {
@apply bg-gradient-to-r from-emerald-500 to-emerald-600;
@apply text-white shadow-md shadow-emerald-500/25;
@apply hover:from-emerald-600 hover:to-emerald-700 hover:shadow-lg hover:shadow-emerald-500/30;
@apply dark:shadow-emerald-500/20;
}
.btn-warning {
@apply bg-gradient-to-r from-amber-500 to-amber-600;
@apply text-white shadow-md shadow-amber-500/25;
@apply hover:from-amber-600 hover:to-amber-700 hover:shadow-lg hover:shadow-amber-500/30;
@apply dark:shadow-amber-500/20;
}
.btn-sm { .btn-sm {
@apply rounded-lg px-3 py-1.5 text-xs; @apply rounded-lg px-3 py-1.5 text-xs;
} }
...@@ -130,6 +144,20 @@ ...@@ -130,6 +144,20 @@
-moz-appearance: textfield; -moz-appearance: textfield;
} }
/* ============ 玻璃效果 ============ */
.glass {
@apply bg-white/80 backdrop-blur-xl dark:bg-dark-800/80;
}
.glass-card {
@apply bg-white/70 dark:bg-dark-800/70;
@apply backdrop-blur-xl;
@apply rounded-2xl;
@apply border border-white/20 dark:border-dark-700/50;
@apply shadow-glass;
@apply transition-all duration-300;
}
/* ============ 卡片样式 ============ */ /* ============ 卡片样式 ============ */
.card { .card {
@apply bg-white dark:bg-dark-800/50; @apply bg-white dark:bg-dark-800/50;
...@@ -151,6 +179,20 @@ ...@@ -151,6 +179,20 @@
@apply shadow-glass; @apply shadow-glass;
} }
.card-header {
@apply border-b border-gray-100 dark:border-dark-700;
@apply px-6 py-4;
}
.card-body {
@apply p-6;
}
.card-footer {
@apply border-t border-gray-100 dark:border-dark-700;
@apply px-6 py-4;
}
/* ============ 统计卡片 ============ */ /* ============ 统计卡片 ============ */
.stat-card { .stat-card {
@apply card p-5; @apply card p-5;
...@@ -256,6 +298,10 @@ ...@@ -256,6 +298,10 @@
@apply bg-gray-100 text-gray-700 dark:bg-dark-700 dark:text-dark-300; @apply bg-gray-100 text-gray-700 dark:bg-dark-700 dark:text-dark-300;
} }
.badge-purple {
@apply bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400;
}
/* ============ 下拉菜单 ============ */ /* ============ 下拉菜单 ============ */
.dropdown { .dropdown {
@apply absolute z-50; @apply absolute z-50;
...@@ -283,15 +329,19 @@ ...@@ -283,15 +329,19 @@
} }
.modal-content { .modal-content {
@apply w-full;
@apply max-h-[95vh] sm:max-h-[90vh];
@apply bg-white dark:bg-dark-800; @apply bg-white dark:bg-dark-800;
@apply rounded-2xl shadow-2xl; @apply rounded-2xl shadow-2xl;
@apply w-full; @apply border border-gray-200 dark:border-dark-700;
@apply max-h-[90vh] overflow-y-auto; @apply flex flex-col;
} }
.modal-header { .modal-header {
@apply border-b border-gray-100 px-6 py-4 dark:border-dark-700; @apply border-b border-gray-200 px-4 py-3 dark:border-dark-700;
@apply sm:px-6 sm:py-4;
@apply flex items-center justify-between; @apply flex items-center justify-between;
@apply flex-shrink-0;
} }
.modal-title { .modal-title {
...@@ -299,12 +349,69 @@ ...@@ -299,12 +349,69 @@
} }
.modal-body { .modal-body {
@apply px-6 py-4; @apply px-4 py-3;
@apply sm:px-6 sm:py-4;
@apply flex-1 overflow-y-auto;
} }
.modal-footer { .modal-footer {
@apply border-t border-gray-100 px-6 py-4 dark:border-dark-700; @apply border-t border-gray-200 px-4 py-3 dark:border-dark-700;
@apply sm:px-6 sm:py-4;
@apply flex items-center justify-end gap-3; @apply flex items-center justify-end gap-3;
@apply flex-shrink-0;
}
/* 防止body滚动的工具类 */
body.modal-open {
overflow: hidden;
}
.modal-enter-active {
transition: opacity 250ms ease-out;
}
.modal-leave-active {
transition: opacity 200ms ease-in;
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
}
.modal-enter-active .modal-content {
transition: transform 250ms ease-out, opacity 250ms ease-out;
}
.modal-leave-active .modal-content {
transition: transform 200ms ease-in, opacity 200ms ease-in;
}
.modal-enter-from .modal-content,
.modal-leave-to .modal-content {
transform: scale(0.95);
opacity: 0;
}
.modal-enter-to .modal-content,
.modal-leave-from .modal-content {
transform: scale(1);
opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
.modal-enter-active,
.modal-leave-active,
.modal-enter-active .modal-content,
.modal-leave-active .modal-content {
transition-duration: 1ms;
transition-delay: 0ms;
}
.modal-enter-from .modal-content,
.modal-leave-to .modal-content {
transform: none;
}
} }
/* ============ Dialog ============ */ /* ============ Dialog ============ */
......
...@@ -107,7 +107,7 @@ ...@@ -107,7 +107,7 @@
<!-- Bulk Actions Bar --> <!-- Bulk Actions Bar -->
<div <div
v-if="selectedAccountIds.length > 0" v-if="selectedAccountIds.length > 0"
class="card border-primary-200 bg-primary-50 px-4 py-3 dark:border-primary-800 dark:bg-primary-900/20" class="mb-[5px] mt-[10px] px-5 py-1"
> >
<div class="flex flex-wrap items-center justify-between gap-3"> <div class="flex flex-wrap items-center justify-between gap-3">
<div class="flex flex-wrap items-center gap-2"> <div class="flex flex-wrap items-center gap-2">
......
...@@ -300,8 +300,8 @@ ...@@ -300,8 +300,8 @@
<button @click="resetFilters" class="btn btn-secondary"> <button @click="resetFilters" class="btn btn-secondary">
{{ t('common.reset') }} {{ t('common.reset') }}
</button> </button>
<button @click="exportToCSV" class="btn btn-primary"> <button @click="exportToExcel" :disabled="exporting" class="btn btn-primary">
{{ t('usage.exportCsv') }} {{ t('usage.exportExcel') }}
</button> </button>
</div> </div>
</div> </div>
...@@ -361,6 +361,7 @@ ...@@ -361,6 +361,7 @@
</template> </template>
<template #cell-tokens="{ row }"> <template #cell-tokens="{ row }">
<div class="flex items-center gap-1.5">
<div class="space-y-1.5 text-sm"> <div class="space-y-1.5 text-sm">
<!-- Input / Output Tokens --> <!-- Input / Output Tokens -->
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
...@@ -448,6 +449,29 @@ ...@@ -448,6 +449,29 @@
</div> </div>
</div> </div>
</div> </div>
<!-- Token Detail Tooltip -->
<div
class="group relative"
@mouseenter="showTokenTooltip($event, row)"
@mouseleave="hideTokenTooltip"
>
<div
class="flex h-4 w-4 cursor-help items-center justify-center rounded-full bg-gray-100 transition-colors group-hover:bg-blue-100 dark:bg-gray-700 dark:group-hover:bg-blue-900/50"
>
<svg
class="h-3 w-3 text-gray-400 group-hover:text-blue-500 dark:text-gray-500 dark:group-hover:text-blue-400"
fill="currentColor"
viewBox="0 0 20 20"
>
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
clip-rule="evenodd"
/>
</svg>
</div>
</div>
</div>
</template> </template>
<template #cell-cost="{ row }"> <template #cell-cost="{ row }">
...@@ -516,9 +540,50 @@ ...@@ -516,9 +540,50 @@
</template> </template>
<template #cell-request_id="{ row }"> <template #cell-request_id="{ row }">
<span class="font-mono text-xs text-gray-500 dark:text-gray-400">{{ <div v-if="row.request_id" class="flex items-center gap-1.5 max-w-[120px]">
row.request_id || '-' <span
}}</span> class="font-mono text-xs text-gray-500 dark:text-gray-400 truncate"
:title="row.request_id"
>
{{ row.request_id }}
</span>
<button
@click="copyRequestId(row.request_id)"
class="flex-shrink-0 rounded p-0.5 transition-colors hover:bg-gray-100 dark:hover:bg-dark-700"
:class="
copiedRequestId === row.request_id
? 'text-green-500'
: 'text-gray-400 hover:text-gray-600 dark:hover:text-gray-300'
"
:title="copiedRequestId === row.request_id ? t('keys.copied') : t('keys.copyToClipboard')"
>
<svg
v-if="copiedRequestId === row.request_id"
class="h-3.5 w-3.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
stroke-width="2"
>
<path stroke-linecap="round" stroke-linejoin="round" d="M5 13l4 4L19 7" />
</svg>
<svg
v-else
class="h-3.5 w-3.5"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
stroke-width="2"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"
/>
</svg>
</button>
</div>
<span v-else class="text-gray-400 dark:text-gray-500">-</span>
</template> </template>
<template #empty> <template #empty>
...@@ -540,6 +605,63 @@ ...@@ -540,6 +605,63 @@
</div> </div>
</AppLayout> </AppLayout>
<ExportProgressDialog
:show="exportProgress.show"
:progress="exportProgress.progress"
:current="exportProgress.current"
:total="exportProgress.total"
:estimated-time="exportProgress.estimatedTime"
@cancel="cancelExport"
/>
<!-- Token Tooltip Portal -->
<Teleport to="body">
<div
v-if="tokenTooltipVisible"
class="fixed z-[9999] pointer-events-none -translate-y-1/2"
:style="{
left: tokenTooltipPosition.x + 'px',
top: tokenTooltipPosition.y + 'px'
}"
>
<div
class="whitespace-nowrap rounded-lg border border-gray-700 bg-gray-900 px-3 py-2.5 text-xs text-white shadow-xl dark:border-gray-600 dark:bg-gray-800"
>
<div class="space-y-1.5">
<!-- Token Breakdown -->
<div class="mb-2 border-b border-gray-700 pb-1.5">
<div class="text-xs font-semibold text-gray-300 mb-1">Token 明细</div>
<div v-if="tokenTooltipData && tokenTooltipData.input_tokens > 0" class="flex items-center justify-between gap-4">
<span class="text-gray-400">{{ t('admin.usage.inputTokens') }}</span>
<span class="font-medium text-white">{{ tokenTooltipData.input_tokens.toLocaleString() }}</span>
</div>
<div v-if="tokenTooltipData && tokenTooltipData.output_tokens > 0" class="flex items-center justify-between gap-4">
<span class="text-gray-400">{{ t('admin.usage.outputTokens') }}</span>
<span class="font-medium text-white">{{ tokenTooltipData.output_tokens.toLocaleString() }}</span>
</div>
<div v-if="tokenTooltipData && tokenTooltipData.cache_creation_tokens > 0" class="flex items-center justify-between gap-4">
<span class="text-gray-400">{{ t('admin.usage.cacheCreationTokens') }}</span>
<span class="font-medium text-white">{{ tokenTooltipData.cache_creation_tokens.toLocaleString() }}</span>
</div>
<div v-if="tokenTooltipData && tokenTooltipData.cache_read_tokens > 0" class="flex items-center justify-between gap-4">
<span class="text-gray-400">{{ t('admin.usage.cacheReadTokens') }}</span>
<span class="font-medium text-white">{{ tokenTooltipData.cache_read_tokens.toLocaleString() }}</span>
</div>
</div>
<!-- Total -->
<div class="flex items-center justify-between gap-6 border-t border-gray-700 pt-1.5">
<span class="text-gray-400">{{ t('usage.totalTokens') }}</span>
<span class="font-semibold text-blue-400">{{ ((tokenTooltipData?.input_tokens || 0) + (tokenTooltipData?.output_tokens || 0) + (tokenTooltipData?.cache_creation_tokens || 0) + (tokenTooltipData?.cache_read_tokens || 0)).toLocaleString() }}</span>
</div>
</div>
<!-- Tooltip Arrow (left side) -->
<div
class="absolute right-full top-1/2 h-0 w-0 -translate-y-1/2 border-b-[6px] border-r-[6px] border-t-[6px] border-b-transparent border-r-gray-900 border-t-transparent dark:border-r-gray-800"
></div>
</div>
</div>
</Teleport>
<!-- Tooltip Portal --> <!-- Tooltip Portal -->
<Teleport to="body"> <Teleport to="body">
<div <div
...@@ -602,10 +724,14 @@ ...@@ -602,10 +724,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, computed, onMounted, onUnmounted } from 'vue' import { ref, computed, reactive, onMounted, onUnmounted } from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import * as XLSX from 'xlsx'
import { saveAs } from 'file-saver'
import { useAppStore } from '@/stores/app' import { useAppStore } from '@/stores/app'
import { useClipboard } from '@/composables/useClipboard'
import { adminAPI } from '@/api/admin' import { adminAPI } from '@/api/admin'
import { adminUsageAPI } from '@/api/admin/usage'
import AppLayout from '@/components/layout/AppLayout.vue' import AppLayout from '@/components/layout/AppLayout.vue'
import DataTable from '@/components/common/DataTable.vue' import DataTable from '@/components/common/DataTable.vue'
import Pagination from '@/components/common/Pagination.vue' import Pagination from '@/components/common/Pagination.vue'
...@@ -615,6 +741,7 @@ import Select from '@/components/common/Select.vue' ...@@ -615,6 +741,7 @@ import Select from '@/components/common/Select.vue'
import DateRangePicker from '@/components/common/DateRangePicker.vue' import DateRangePicker from '@/components/common/DateRangePicker.vue'
import ModelDistributionChart from '@/components/charts/ModelDistributionChart.vue' import ModelDistributionChart from '@/components/charts/ModelDistributionChart.vue'
import TokenUsageTrend from '@/components/charts/TokenUsageTrend.vue' import TokenUsageTrend from '@/components/charts/TokenUsageTrend.vue'
import ExportProgressDialog from '@/components/common/ExportProgressDialog.vue'
import type { UsageLog, TrendDataPoint, ModelStat } from '@/types' import type { UsageLog, TrendDataPoint, ModelStat } from '@/types'
import type { Column } from '@/components/common/types' import type { Column } from '@/components/common/types'
import type { import type {
...@@ -626,12 +753,21 @@ import type { ...@@ -626,12 +753,21 @@ import type {
const { t } = useI18n() const { t } = useI18n()
const appStore = useAppStore() const appStore = useAppStore()
const { copyToClipboard: clipboardCopy } = useClipboard()
// Tooltip state // Tooltip state
const tooltipVisible = ref(false) const tooltipVisible = ref(false)
const tooltipPosition = ref({ x: 0, y: 0 }) const tooltipPosition = ref({ x: 0, y: 0 })
const tooltipData = ref<UsageLog | null>(null) const tooltipData = ref<UsageLog | null>(null)
// Token tooltip state
const tokenTooltipVisible = ref(false)
const tokenTooltipPosition = ref({ x: 0, y: 0 })
const tokenTooltipData = ref<UsageLog | null>(null)
// Request ID copy state
const copiedRequestId = ref<string | null>(null)
// Usage stats from API // Usage stats from API
const usageStats = ref<AdminUsageStatsResponse | null>(null) const usageStats = ref<AdminUsageStatsResponse | null>(null)
...@@ -657,6 +793,7 @@ const columns = computed<Column[]>(() => [ ...@@ -657,6 +793,7 @@ const columns = computed<Column[]>(() => [
{ key: 'tokens', label: t('usage.tokens'), sortable: false }, { key: 'tokens', label: t('usage.tokens'), sortable: false },
{ key: 'cost', label: t('usage.cost'), sortable: false }, { key: 'cost', label: t('usage.cost'), sortable: false },
{ key: 'billing_type', label: t('usage.billingType'), sortable: false }, { key: 'billing_type', label: t('usage.billingType'), sortable: false },
{ key: 'first_token', label: t('usage.firstToken'), sortable: false },
{ key: 'duration', label: t('usage.duration'), sortable: false }, { key: 'duration', label: t('usage.duration'), sortable: false },
{ key: 'created_at', label: t('usage.time'), sortable: true }, { key: 'created_at', label: t('usage.time'), sortable: true },
{ key: 'request_id', label: t('admin.usage.requestId'), sortable: false } { key: 'request_id', label: t('admin.usage.requestId'), sortable: false }
...@@ -669,6 +806,15 @@ const accounts = ref<any[]>([]) ...@@ -669,6 +806,15 @@ const accounts = ref<any[]>([])
const groups = ref<any[]>([]) const groups = ref<any[]>([])
const loading = ref(false) const loading = ref(false)
let abortController: AbortController | null = null let abortController: AbortController | null = null
let exportAbortController: AbortController | null = null
const exporting = ref(false)
const exportProgress = reactive({
show: false,
progress: 0,
current: 0,
total: 0,
estimatedTime: ''
})
// User search state // User search state
const userSearchKeyword = ref('') const userSearchKeyword = ref('')
...@@ -868,6 +1014,16 @@ const formatCacheTokens = (value: number): string => { ...@@ -868,6 +1014,16 @@ const formatCacheTokens = (value: number): string => {
return value.toLocaleString() return value.toLocaleString()
} }
const copyRequestId = async (requestId: string) => {
const success = await clipboardCopy(requestId, t('admin.usage.requestIdCopied'))
if (success) {
copiedRequestId.value = requestId
setTimeout(() => {
copiedRequestId.value = null
}, 800)
}
}
const isAbortError = (error: unknown): boolean => { const isAbortError = (error: unknown): boolean => {
if (error instanceof DOMException && error.name === 'AbortError') { if (error instanceof DOMException && error.name === 'AbortError') {
return true return true
...@@ -879,6 +1035,40 @@ const isAbortError = (error: unknown): boolean => { ...@@ -879,6 +1035,40 @@ const isAbortError = (error: unknown): boolean => {
return false return false
} }
const formatExportTimestamp = (date: Date): string => {
const pad = (value: number) => String(value).padStart(2, '0')
return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}_${pad(date.getHours())}-${pad(date.getMinutes())}-${pad(date.getSeconds())}`
}
const formatRemainingTime = (ms: number): string => {
const totalSeconds = Math.max(0, Math.round(ms / 1000))
const hours = Math.floor(totalSeconds / 3600)
const minutes = Math.floor((totalSeconds % 3600) / 60)
const seconds = totalSeconds % 60
const parts = []
if (hours > 0) {
parts.push(`${hours}h`)
}
if (minutes > 0 || hours > 0) {
parts.push(`${minutes}m`)
}
parts.push(`${seconds}s`)
return parts.join(' ')
}
const updateExportProgress = (current: number, total: number, startedAt: number) => {
exportProgress.current = current
exportProgress.total = total
exportProgress.progress = total > 0 ? Math.min(100, Math.round((current / total) * 100)) : 0
if (current > 0 && total > 0) {
const elapsedMs = Date.now() - startedAt
const remainingMs = Math.max(0, Math.round((elapsedMs / current) * (total - current)))
exportProgress.estimatedTime = formatRemainingTime(remainingMs)
} else {
exportProgress.estimatedTime = ''
}
}
const loadUsageLogs = async () => { const loadUsageLogs = async () => {
if (abortController) { if (abortController) {
abortController.abort() abortController.abort()
...@@ -1051,8 +1241,72 @@ const handlePageSizeChange = (pageSize: number) => { ...@@ -1051,8 +1241,72 @@ const handlePageSizeChange = (pageSize: number) => {
loadUsageLogs() loadUsageLogs()
} }
const exportToCSV = () => { const cancelExport = () => {
if (usageLogs.value.length === 0) { if (!exporting.value) {
return
}
exportAbortController?.abort()
}
const exportToExcel = async () => {
if (pagination.value.total === 0) {
appStore.showWarning(t('usage.noDataToExport'))
return
}
if (exporting.value) {
return
}
exporting.value = true
exportProgress.show = true
exportProgress.progress = 0
exportProgress.current = 0
exportProgress.total = pagination.value.total
exportProgress.estimatedTime = ''
const startedAt = Date.now()
const controller = new AbortController()
exportAbortController = controller
try {
const allLogs: UsageLog[] = []
const pageSize = 100
let page = 1
let total = pagination.value.total
while (true) {
const params: AdminUsageQueryParams = {
page,
page_size: pageSize,
...filters.value
}
const response = await adminUsageAPI.list(params, { signal: controller.signal })
if (controller.signal.aborted) {
break
}
if (page === 1) {
total = response.total
exportProgress.total = total
}
if (response.items?.length) {
allLogs.push(...response.items)
}
updateExportProgress(allLogs.length, total, startedAt)
if (allLogs.length >= total || response.items.length < pageSize) {
break
}
page += 1
}
if (controller.signal.aborted) {
appStore.showInfo(t('usage.exportCancelled'))
return
}
if (allLogs.length === 0) {
appStore.showWarning(t('usage.noDataToExport')) appStore.showWarning(t('usage.noDataToExport'))
return return
} }
...@@ -1071,7 +1325,7 @@ const exportToCSV = () => { ...@@ -1071,7 +1325,7 @@ const exportToCSV = () => {
'Duration (ms)', 'Duration (ms)',
'Time' 'Time'
] ]
const rows = usageLogs.value.map((log) => [ const rows = allLogs.map((log) => [
log.user?.email || '', log.user?.email || '',
log.api_key?.name || '', log.api_key?.name || '',
log.model, log.model,
...@@ -1080,23 +1334,36 @@ const exportToCSV = () => { ...@@ -1080,23 +1334,36 @@ const exportToCSV = () => {
log.output_tokens, log.output_tokens,
log.cache_read_tokens, log.cache_read_tokens,
log.cache_creation_tokens, log.cache_creation_tokens,
log.total_cost.toFixed(6), Number(log.total_cost.toFixed(6)),
log.billing_type === 1 ? 'Subscription' : 'Balance', log.billing_type === 1 ? 'Subscription' : 'Balance',
log.duration_ms, log.duration_ms,
log.created_at log.created_at
]) ])
const csvContent = [headers.join(','), ...rows.map((row) => row.join(','))].join('\n') const worksheet = XLSX.utils.aoa_to_sheet([headers, ...rows])
const workbook = XLSX.utils.book_new()
const blob = new Blob([csvContent], { type: 'text/csv' }) XLSX.utils.book_append_sheet(workbook, worksheet, 'Usage')
const url = window.URL.createObjectURL(blob) const excelBuffer = XLSX.write(workbook, { bookType: 'xlsx', type: 'array' })
const link = document.createElement('a') const blob = new Blob([excelBuffer], {
link.href = url type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
link.download = `admin_usage_${new Date().toISOString().split('T')[0]}.csv` })
link.click()
window.URL.revokeObjectURL(url)
appStore.showSuccess(t('usage.exportSuccess')) saveAs(blob, `admin_usage_${formatExportTimestamp(new Date())}.xlsx`)
appStore.showSuccess(t('usage.exportExcelSuccess'))
} catch (error) {
if (controller.signal.aborted || isAbortError(error)) {
appStore.showInfo(t('usage.exportCancelled'))
return
}
appStore.showError(t('usage.exportExcelFailed'))
console.error('Excel export failed:', error)
} finally {
if (exportAbortController === controller) {
exportAbortController = null
}
exporting.value = false
exportProgress.show = false
}
} }
// Click outside to close dropdown // Click outside to close dropdown
...@@ -1123,6 +1390,22 @@ const hideTooltip = () => { ...@@ -1123,6 +1390,22 @@ const hideTooltip = () => {
tooltipData.value = null tooltipData.value = null
} }
// Token tooltip functions
const showTokenTooltip = (event: MouseEvent, row: UsageLog) => {
const target = event.currentTarget as HTMLElement
const rect = target.getBoundingClientRect()
tokenTooltipData.value = row
tokenTooltipPosition.value.x = rect.right + 8
tokenTooltipPosition.value.y = rect.top + rect.height / 2
tokenTooltipVisible.value = true
}
const hideTokenTooltip = () => {
tokenTooltipVisible.value = false
tokenTooltipData.value = null
}
onMounted(() => { onMounted(() => {
loadFilterOptions() loadFilterOptions()
loadApiKeys() loadApiKeys()
...@@ -1140,5 +1423,8 @@ onUnmounted(() => { ...@@ -1140,5 +1423,8 @@ onUnmounted(() => {
if (abortController) { if (abortController) {
abortController.abort() abortController.abort()
} }
if (exportAbortController) {
exportAbortController.abort()
}
}) })
</script> </script>
...@@ -301,7 +301,7 @@ ...@@ -301,7 +301,7 @@
<BaseDialog <BaseDialog
:show="showCreateModal || showEditModal" :show="showCreateModal || showEditModal"
:title="showEditModal ? t('keys.editKey') : t('keys.createKey')" :title="showEditModal ? t('keys.editKey') : t('keys.createKey')"
width="narrow" width="normal"
@close="closeModals" @close="closeModals"
> >
<form id="key-form" @submit.prevent="handleSubmit" class="space-y-5"> <form id="key-form" @submit.prevent="handleSubmit" class="space-y-5">
...@@ -878,7 +878,20 @@ const importToCcswitch = (apiKey: string) => { ...@@ -878,7 +878,20 @@ const importToCcswitch = (apiKey: string) => {
usageAutoInterval: '30' usageAutoInterval: '30'
}) })
const deeplink = `ccswitch://v1/import?${params.toString()}` const deeplink = `ccswitch://v1/import?${params.toString()}`
try {
window.open(deeplink, '_self') window.open(deeplink, '_self')
// Check if the protocol handler worked by detecting if we're still focused
setTimeout(() => {
if (document.hasFocus()) {
// Still focused means the protocol handler likely failed
appStore.showError(t('keys.ccSwitchNotInstalled'))
}
}, 100)
} catch (error) {
appStore.showError(t('keys.ccSwitchNotInstalled'))
}
} }
onMounted(() => { onMounted(() => {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment