Modernize admin UI and harden provider contracts
Adopt Web Awesome for consistent accessible controls while aligning production ASR, App Attest, and runtime dependency safeguards.
This commit is contained in:
Generated
+448
@@ -7,6 +7,9 @@
|
||||
"": {
|
||||
"name": "osg-admin-web",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"@awesome.me/webawesome": "^3.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^26.2.0",
|
||||
"jsdom": "^27.0.1",
|
||||
@@ -50,6 +53,120 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@awesome.me/webawesome": {
|
||||
"version": "3.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@awesome.me/webawesome/-/webawesome-3.11.0.tgz",
|
||||
"integrity": "sha512-WM8kyP+AdAT3/zwNpc/YzN3Nu5pRD6ksREFZAct/DIn6KMnQs8lPVfiPbJrOpKMk8OaSnQX19iiwFGjoswXVXw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "4.1.0",
|
||||
"@floating-ui/dom": "^1.6.13",
|
||||
"@konnorr/qr-creator": "^1.0.1",
|
||||
"@lit-labs/ssr": "^4.1.0",
|
||||
"@lit-labs/ssr-client": "^1.1.8",
|
||||
"@lit/context": "^1.1.6",
|
||||
"@lit/react": "^1.0.8",
|
||||
"@shoelace-style/animations": "^1.2.0",
|
||||
"@shoelace-style/localize": "^3.2.3",
|
||||
"composed-offset-position": "^0.0.6",
|
||||
"lit": "^3.2.1",
|
||||
"marked": "^11.2.0",
|
||||
"nanoid": "^5.1.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@awesome.me/webawesome/node_modules/@lit-labs/ssr": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/ssr/-/ssr-4.1.0.tgz",
|
||||
"integrity": "sha512-m0zymVVlHB1ddJQ1lastsV8ROW3whFOiHJhVPQWd04MnGTkTlUUVLQctux1QlyD9BtLXNN6iASxv388vhgKMFg==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-client": "^1.1.7",
|
||||
"@lit-labs/ssr-dom-shim": "^1.6.0",
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"@parse5/tools": "^0.3.0",
|
||||
"enhanced-resolve": "^5.10.0",
|
||||
"lit": "^3.1.2",
|
||||
"lit-element": "^4.0.4",
|
||||
"lit-html": "^3.1.2",
|
||||
"node-fetch": "^3.2.8",
|
||||
"parse5": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=13.9.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">=20.0.0 <25.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/node": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@awesome.me/webawesome/node_modules/@types/node": {
|
||||
"version": "24.13.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz",
|
||||
"integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.18.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@awesome.me/webawesome/node_modules/entities": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@awesome.me/webawesome/node_modules/nanoid": {
|
||||
"version": "5.1.16",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.16.tgz",
|
||||
"integrity": "sha512-kVrnsrJqMR8+oLJnGEmSWw9BivK5mt7H3FZatVRjrc5wGqFYuBxX1yG7+A7Gi5AefkX6t/oCkizcQgpu0cY1dQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18 || >=20"
|
||||
}
|
||||
},
|
||||
"node_modules/@awesome.me/webawesome/node_modules/parse5": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@awesome.me/webawesome/node_modules/undici-types": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
|
||||
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/@csstools/color-helpers": {
|
||||
"version": "6.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.1.tgz",
|
||||
@@ -190,6 +307,15 @@
|
||||
"node": ">=20.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@ctrl/tinycolor": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.1.0.tgz",
|
||||
"integrity": "sha512-WyOx8cJQ+FQus4Mm4uPIZA64gbk3Wxh0so5Lcii0aJifqwoVOlfFtorjLE0Hen4OYyHZMXDWqMmaQemBhgxFRQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.28.2",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz",
|
||||
@@ -632,6 +758,31 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/core": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz",
|
||||
"integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/utils": "^0.2.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/dom": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz",
|
||||
"integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@floating-ui/core": "^1.8.0",
|
||||
"@floating-ui/utils": "^0.2.12"
|
||||
}
|
||||
},
|
||||
"node_modules/@floating-ui/utils": {
|
||||
"version": "0.2.12",
|
||||
"resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz",
|
||||
"integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@jridgewell/sourcemap-codec": {
|
||||
"version": "1.5.5",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
|
||||
@@ -639,6 +790,56 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@konnorr/qr-creator": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@konnorr/qr-creator/-/qr-creator-1.1.0.tgz",
|
||||
"integrity": "sha512-6Ae3AfG45MOqF9Qde+1YxIxKf/GUsRmcriV7iCb/HaR+Mk1YDFYP3RINDRMI8z8YMWW3BnSthOujX9RoOex/Kw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@lit-labs/ssr-client": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-client/-/ssr-client-1.1.8.tgz",
|
||||
"integrity": "sha512-PjGh81oKsoI64m3IDjTqqjhC7dr2uC/o0jrllUb5gRAyp/RlAHxapgJrjq9kWz97faCHLQ8jUlTi6tGm+8fgyA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@lit/reactive-element": "^2.0.4",
|
||||
"lit": "^3.1.2",
|
||||
"lit-html": "^3.1.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit-labs/ssr-dom-shim": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.6.0.tgz",
|
||||
"integrity": "sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@lit/context": {
|
||||
"version": "1.1.6",
|
||||
"resolved": "https://registry.npmjs.org/@lit/context/-/context-1.1.6.tgz",
|
||||
"integrity": "sha512-M26qDE6UkQbZA2mQ3RjJ3Gzd8TxP+/0obMgE5HfkfLhEEyYE3Bui4A5XHiGPjy0MUGAyxB3QgVuw2ciS0kHn6A==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@lit/reactive-element": "^1.6.2 || ^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit/react": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@lit/react/-/react-1.0.8.tgz",
|
||||
"integrity": "sha512-p2+YcF+JE67SRX3mMlJ1TKCSTsgyOVdAwd/nxp3NuV1+Cb6MWALbN6nT7Ld4tpmYofcE5kcaSY1YBB9erY+6fw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"peerDependencies": {
|
||||
"@types/react": "17 || 18 || 19"
|
||||
}
|
||||
},
|
||||
"node_modules/@lit/reactive-element": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz",
|
||||
"integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-dom-shim": "^1.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/lzma-linux-x64-gnu": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
|
||||
@@ -656,6 +857,39 @@
|
||||
"node": "^22.20 || ^24.12 || >=25"
|
||||
}
|
||||
},
|
||||
"node_modules/@parse5/tools": {
|
||||
"version": "0.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@parse5/tools/-/tools-0.3.0.tgz",
|
||||
"integrity": "sha512-zxRyTHkqb7WQMV8kTNBKWb1BeOFUKXBXTBWuxg9H9hfvQB3IwP6Iw2U75Ia5eyRxPNltmY7E8YAlz6zWwUnjKg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"parse5": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@parse5/tools/node_modules/entities": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
|
||||
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
|
||||
"license": "BSD-2-Clause",
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@parse5/tools/node_modules/parse5": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
|
||||
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"entities": "^6.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/inikulin/parse5?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.62.4",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz",
|
||||
@@ -1006,6 +1240,22 @@
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@shoelace-style/animations": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@shoelace-style/animations/-/animations-1.2.0.tgz",
|
||||
"integrity": "sha512-avvo1xxkLbv2dgtabdewBbqcJfV0e0zCwFqkPMnHFGbJbBHorRFfMAHh1NG9ymmXn0jW95ibUVH03E1NYXD6Gw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://github.com/sponsors/claviska"
|
||||
}
|
||||
},
|
||||
"node_modules/@shoelace-style/localize": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@shoelace-style/localize/-/localize-3.2.3.tgz",
|
||||
"integrity": "sha512-pWoZywizMilenLfQcpE9bjaqi7WKiQXJB42cxvmSPwYS76U776wJ7B5QQ0P28yVit1vpHuFZzq1CtfVFv32jaQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/chai": {
|
||||
"version": "5.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
|
||||
@@ -1041,6 +1291,22 @@
|
||||
"undici-types": "~8.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react": {
|
||||
"version": "19.2.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.18.tgz",
|
||||
"integrity": "sha512-AnzbBERsrLKtk2XSfTbYRLjQPdy116Sty4q+T+Bp3IC4l6jNBvreVPAHmpq9qhXQM7CXZPjLVmGMw9sy+hxQ3w==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.2.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/trusted-types": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
|
||||
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@vitest/expect": {
|
||||
"version": "3.2.7",
|
||||
"resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz",
|
||||
@@ -1223,6 +1489,15 @@
|
||||
"node": ">= 16"
|
||||
}
|
||||
},
|
||||
"node_modules/composed-offset-position": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/composed-offset-position/-/composed-offset-position-0.0.6.tgz",
|
||||
"integrity": "sha512-Q7dLompI6lUwd7LWyIcP66r4WcS9u7AL2h8HaeipiRfCRPLMWqRx8fYsjb4OHi6UQFifO7XtNC2IlEJ1ozIFxw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"@floating-ui/utils": "^0.2.5"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
|
||||
@@ -1253,6 +1528,22 @@
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
|
||||
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/data-uri-to-buffer": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
|
||||
"integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
},
|
||||
"node_modules/data-urls": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.1.tgz",
|
||||
@@ -1312,6 +1603,19 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/enhanced-resolve": {
|
||||
"version": "5.24.5",
|
||||
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz",
|
||||
"integrity": "sha512-L1l8TNvomm6UVW5B253AGxQagSQr+vGwhMlrrfRS2qmhx46AMpMVJKQYLvWYbysTMY8VoicOvzHzoHMbyzB+4A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"graceful-fs": "^4.2.4",
|
||||
"tapable": "^2.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/entities": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
|
||||
@@ -1412,6 +1716,41 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fetch-blob": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
|
||||
"integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "paypal",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-domexception": "^1.0.0",
|
||||
"web-streams-polyfill": "^3.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20 || >= 14.13"
|
||||
}
|
||||
},
|
||||
"node_modules/formdata-polyfill": {
|
||||
"version": "4.0.10",
|
||||
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
|
||||
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fetch-blob": "^3.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.20.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||
@@ -1427,6 +1766,12 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/graceful-fs": {
|
||||
"version": "4.2.11",
|
||||
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
|
||||
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/html-encoding-sniffer": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
|
||||
@@ -1535,6 +1880,37 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/lit": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/lit/-/lit-3.3.3.tgz",
|
||||
"integrity": "sha512-fycuvZg/hkpozL00lm1pEJH5nN/lr9ZXd6mJI2HSN4+Bzc+LDNdEApJ6HFbPkdFNHLvOplIIuJvxkS4XUxqirw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@lit/reactive-element": "^2.1.0",
|
||||
"lit-element": "^4.2.0",
|
||||
"lit-html": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lit-element": {
|
||||
"version": "4.2.2",
|
||||
"resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz",
|
||||
"integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@lit-labs/ssr-dom-shim": "^1.5.0",
|
||||
"@lit/reactive-element": "^2.1.0",
|
||||
"lit-html": "^3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/lit-html": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.3.tgz",
|
||||
"integrity": "sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@types/trusted-types": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/loupe": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
|
||||
@@ -1562,6 +1938,18 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.5.5"
|
||||
}
|
||||
},
|
||||
"node_modules/marked": {
|
||||
"version": "11.2.0",
|
||||
"resolved": "https://registry.npmjs.org/marked/-/marked-11.2.0.tgz",
|
||||
"integrity": "sha512-HR0m3bvu0jAPYiIvLUUQtdg1g6D247//lvcekpHO1WMvbwDlwSkZAX9Lw4F4YHE1T0HaaNve0tuAWuV1UJ6vtw==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"marked": "bin/marked.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.27.1",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
|
||||
@@ -1595,6 +1983,44 @@
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/node-domexception": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
|
||||
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
|
||||
"deprecated": "Use your platform's native DOMException instead",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/jimmywarting"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://paypal.me/jimmywarting"
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
|
||||
"integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"data-uri-to-buffer": "^4.0.0",
|
||||
"fetch-blob": "^3.1.4",
|
||||
"formdata-polyfill": "^4.0.10"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/node-fetch"
|
||||
}
|
||||
},
|
||||
"node_modules/parse5": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
|
||||
@@ -1818,6 +2244,19 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tapable": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz",
|
||||
"integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/webpack"
|
||||
}
|
||||
},
|
||||
"node_modules/tinybench": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
|
||||
@@ -2130,6 +2569,15 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/web-streams-polyfill": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
|
||||
"integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
|
||||
|
||||
@@ -14,5 +14,8 @@
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.1.2",
|
||||
"vitest": "^3.2.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@awesome.me/webawesome": "^3.11.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,17 +160,17 @@ export class AdminApp {
|
||||
<span class="operator-avatar" aria-hidden="true">管</span>
|
||||
<div><strong>${escapeHtml(this.auth.operatorName)}</strong><span>${roleLabel(this.auth.role)}</span></div>
|
||||
</div>
|
||||
<button class="logout-button" data-logout title="安全退出">
|
||||
<wa-button class="logout-button" appearance="plain" data-logout title="安全退出">
|
||||
<svg viewBox="0 0 22 22" aria-hidden="true"><path d="M9 4H4v14h5M13 7l4 4-4 4M17 11H8" /></svg>
|
||||
<span>退出</span>
|
||||
</button>
|
||||
</wa-button>
|
||||
</div>
|
||||
</aside>
|
||||
<div class="main-column">
|
||||
<header class="mobile-header">
|
||||
<button class="icon-button" data-menu aria-label="打开导航" aria-expanded="false" aria-controls="primary-navigation">
|
||||
<wa-button class="icon-button" appearance="plain" data-menu aria-label="打开导航" aria-expanded="false" aria-controls="primary-navigation">
|
||||
<svg viewBox="0 0 22 22" aria-hidden="true"><path d="M3 6h16M3 11h16M3 16h16" /></svg>
|
||||
</button>
|
||||
</wa-button>
|
||||
<strong data-mobile-title>运营总览</strong>
|
||||
</header>
|
||||
<main class="page-content" id="main-content" data-content tabindex="-1"></main>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import type WaButton from "@awesome.me/webawesome/dist/components/button/button.js";
|
||||
import { ApiError } from "../api/client";
|
||||
import { escapeHtml } from "../lib/format";
|
||||
|
||||
export function renderLoading(container: HTMLElement, label = "正在加载"): void {
|
||||
container.innerHTML = `
|
||||
<div class="state-card" role="status" aria-live="polite">
|
||||
<span class="spinner" aria-hidden="true"></span>
|
||||
<wa-spinner class="state-spinner" aria-hidden="true"></wa-spinner>
|
||||
<p>${escapeHtml(label)}…</p>
|
||||
</div>
|
||||
`;
|
||||
@@ -18,14 +19,14 @@ export function renderError(
|
||||
const message =
|
||||
error instanceof ApiError ? error.message : "出现未知错误,请稍后重试";
|
||||
container.innerHTML = `
|
||||
<div class="state-card state-card--error" role="alert">
|
||||
<wa-callout class="state-card state-card--error" variant="danger" appearance="outlined" role="alert">
|
||||
<span class="state-icon" aria-hidden="true">!</span>
|
||||
<h2>无法加载数据</h2>
|
||||
<p>${escapeHtml(message)}</p>
|
||||
${retry ? '<button class="button button--secondary" data-retry>重试</button>' : ""}
|
||||
</div>
|
||||
${retry ? '<wa-button variant="neutral" appearance="outlined" data-retry>重试</wa-button>' : ""}
|
||||
</wa-callout>
|
||||
`;
|
||||
container.querySelector<HTMLButtonElement>("[data-retry]")?.addEventListener(
|
||||
container.querySelector<HTMLElement>("[data-retry]")?.addEventListener(
|
||||
"click",
|
||||
() => retry?.(),
|
||||
);
|
||||
@@ -36,8 +37,10 @@ export function renderEmpty(message: string): string {
|
||||
}
|
||||
|
||||
export function showToast(message: string, tone: "success" | "error"): void {
|
||||
const toast = document.createElement("div");
|
||||
const toast = document.createElement("wa-callout");
|
||||
toast.className = `toast toast--${tone}`;
|
||||
toast.setAttribute("variant", tone === "error" ? "danger" : "success");
|
||||
toast.setAttribute("appearance", "filled-outlined");
|
||||
toast.setAttribute("role", tone === "error" ? "alert" : "status");
|
||||
toast.textContent = message;
|
||||
document.body.append(toast);
|
||||
@@ -49,10 +52,13 @@ export function showToast(message: string, tone: "success" | "error"): void {
|
||||
}
|
||||
|
||||
export function setButtonBusy(
|
||||
button: HTMLButtonElement,
|
||||
button: HTMLButtonElement | WaButton,
|
||||
busy: boolean,
|
||||
busyLabel = "处理中…",
|
||||
): void {
|
||||
if (button.tagName === "WA-BUTTON") {
|
||||
(button as WaButton).loading = busy;
|
||||
}
|
||||
if (busy) {
|
||||
button.dataset.label = button.textContent ?? "";
|
||||
button.textContent = busyLabel;
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import "@awesome.me/webawesome/dist/styles/webawesome.css";
|
||||
import "@awesome.me/webawesome/dist/components/button/button.js";
|
||||
import "@awesome.me/webawesome/dist/components/callout/callout.js";
|
||||
import "@awesome.me/webawesome/dist/components/input/input.js";
|
||||
import "@awesome.me/webawesome/dist/components/spinner/spinner.js";
|
||||
|
||||
const darkMode = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
|
||||
function syncColorScheme(event: MediaQueryList | MediaQueryListEvent): void {
|
||||
document.documentElement.classList.toggle("wa-dark", event.matches);
|
||||
document.documentElement.classList.toggle("wa-light", !event.matches);
|
||||
}
|
||||
|
||||
syncColorScheme(darkMode);
|
||||
darkMode.addEventListener("change", syncColorScheme);
|
||||
@@ -1,3 +1,4 @@
|
||||
import "./components/webawesome";
|
||||
import "./styles.css";
|
||||
import { AdminApp } from "./app";
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ export async function renderAudit(container: HTMLElement): Promise<void> {
|
||||
</div>
|
||||
${
|
||||
page.nextCursor
|
||||
? '<div class="pagination-actions"><button class="button button--secondary" data-audit-more>加载更早记录</button></div>'
|
||||
? '<div class="pagination-actions"><wa-button variant="neutral" appearance="outlined" data-audit-more>加载更早记录</wa-button></div>'
|
||||
: ""
|
||||
}
|
||||
`
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import type WaButton from "@awesome.me/webawesome/dist/components/button/button.js";
|
||||
import { adminApi, ApiError, setCsrfToken } from "../api/client";
|
||||
import type { AuthState } from "../api/types";
|
||||
import { setButtonBusy } from "../components/ui";
|
||||
@@ -27,27 +28,18 @@ export function renderPasswordLogin(
|
||||
<h1>管理员登录</h1>
|
||||
<p class="muted">使用管理员凭据和认证器动态验证码登录。凭据不会保存到浏览器。</p>
|
||||
<form class="form-stack" data-login-form novalidate>
|
||||
<label>
|
||||
<span>管理员用户名</span>
|
||||
<input name="username" type="text" autocomplete="username" minlength="3" maxlength="64" required autofocus />
|
||||
</label>
|
||||
<label>
|
||||
<span>管理员密码</span>
|
||||
<input name="password" type="password" autocomplete="current-password" minlength="12" required />
|
||||
</label>
|
||||
<label>
|
||||
<span>动态验证码</span>
|
||||
<input class="totp-input" name="totpCode" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]{6}" maxlength="6" required />
|
||||
</label>
|
||||
<wa-input name="username" label="管理员用户名" type="text" autocomplete="username" minlength="3" maxlength="64" appearance="outlined" required autofocus></wa-input>
|
||||
<wa-input name="password" label="管理员密码" type="password" autocomplete="current-password" minlength="12" appearance="outlined" password-toggle required></wa-input>
|
||||
<wa-input class="totp-input" name="totpCode" label="动态验证码" type="text" inputmode="numeric" autocomplete="one-time-code" pattern="[0-9]{6}" maxlength="6" appearance="outlined" required></wa-input>
|
||||
<p class="form-error" data-error role="alert"></p>
|
||||
<button class="button button--primary button--wide" type="submit">安全登录</button>
|
||||
<wa-button class="button--wide" variant="brand" appearance="accent" type="submit">安全登录</wa-button>
|
||||
</form>
|
||||
`);
|
||||
|
||||
const form = root.querySelector<HTMLFormElement>("[data-login-form]");
|
||||
form?.addEventListener("submit", async (event) => {
|
||||
event.preventDefault();
|
||||
const button = form.querySelector<HTMLButtonElement>("button");
|
||||
const button = form.querySelector<WaButton>('wa-button[type="submit"]');
|
||||
const errorNode = form.querySelector<HTMLElement>("[data-error]");
|
||||
const username = new FormData(form).get("username")?.toString().trim() ?? "";
|
||||
const password = new FormData(form).get("password")?.toString() ?? "";
|
||||
|
||||
@@ -24,7 +24,7 @@ export function renderCredits(container: HTMLElement): void {
|
||||
<span aria-hidden="true">⌕</span>
|
||||
<input name="userId" type="search" placeholder="输入完整用户 ID" autocomplete="off" maxlength="36" required />
|
||||
</label>
|
||||
<button class="button button--primary" type="submit">查询流水</button>
|
||||
<wa-button variant="brand" appearance="accent" type="submit">查询流水</wa-button>
|
||||
</form>
|
||||
<div data-ledger-results>${renderEmpty("输入内部用户 ID 开始查询")}</div>
|
||||
</section>
|
||||
|
||||
@@ -57,7 +57,7 @@ function securityTemplate(
|
||||
<h1>安全中心</h1>
|
||||
<p>管理运营人员、角色、登录锁定、会话与双重认证。</p>
|
||||
</div>
|
||||
<button class="button button--primary" data-create-operator>添加管理员</button>
|
||||
<wa-button variant="brand" appearance="accent" data-create-operator>添加管理员</wa-button>
|
||||
</div>
|
||||
|
||||
<aside class="security-notice" aria-label="安全说明">
|
||||
@@ -88,7 +88,7 @@ function securityTemplate(
|
||||
</div>
|
||||
${
|
||||
nextCursor
|
||||
? '<div class="pagination-actions"><button class="button button--secondary" data-operator-more aria-describedby="operator-pagination-status">加载更多管理员</button></div>'
|
||||
? '<div class="pagination-actions"><wa-button variant="neutral" appearance="outlined" data-operator-more aria-describedby="operator-pagination-status">加载更多管理员</wa-button></div>'
|
||||
: ""
|
||||
}
|
||||
<p class="sr-only" id="operator-pagination-status" data-operator-status role="status" aria-live="polite"></p>
|
||||
@@ -133,15 +133,15 @@ function operatorRow(
|
||||
<div class="row-actions" data-operator-id="${escapeHtml(operator.operatorId)}">
|
||||
${
|
||||
locked
|
||||
? '<button class="button button--small button--secondary" data-operator-action="unlock">解锁</button>'
|
||||
? '<wa-button variant="neutral" appearance="outlined" size="s" data-operator-action="unlock">解锁</wa-button>'
|
||||
: ""
|
||||
}
|
||||
<button class="button button--small button--secondary" data-operator-action="sessions">撤销会话</button>
|
||||
<button class="button button--small button--secondary" data-operator-action="reset">重置凭据</button>
|
||||
<wa-button variant="neutral" appearance="outlined" size="s" data-operator-action="sessions">撤销会话</wa-button>
|
||||
<wa-button variant="neutral" appearance="outlined" size="s" data-operator-action="reset">重置凭据</wa-button>
|
||||
${
|
||||
operator.enabled
|
||||
? `<button class="button button--small button--danger" data-operator-action="disable" ${current ? "disabled" : ""}>停用</button>`
|
||||
: '<button class="button button--small button--secondary" data-operator-action="enable">启用</button>'
|
||||
? `<wa-button variant="danger" appearance="filled" size="s" data-operator-action="disable" ${current ? "disabled" : ""}>停用</wa-button>`
|
||||
: '<wa-button variant="neutral" appearance="outlined" size="s" data-operator-action="enable">启用</wa-button>'
|
||||
}
|
||||
</div>
|
||||
</td>
|
||||
|
||||
@@ -39,7 +39,7 @@ export function renderUsers(container: HTMLElement, role: AdminRole): void {
|
||||
<span aria-hidden="true">⌕</span>
|
||||
<input name="query" type="search" placeholder="输入完整内部用户 ID" autocomplete="off" maxlength="36" required />
|
||||
</label>
|
||||
<button class="button button--primary" type="submit">搜索</button>
|
||||
<wa-button variant="brand" appearance="accent" type="submit">搜索</wa-button>
|
||||
</form>
|
||||
<div data-results>${renderEmpty("输入查询条件开始搜索")}</div>
|
||||
</section>
|
||||
@@ -102,7 +102,7 @@ function userRow(user: UserSummary): string {
|
||||
<td><span class="badge badge--${escapeHtml(user.status)}">${statusLabel(user.status)}</span></td>
|
||||
<td>${formatNumber(user.creditBalance)}</td>
|
||||
<td>${formatDateTime(user.createdAt)}</td>
|
||||
<td><button class="button button--small button--secondary" data-user-id="${escapeHtml(user.userId)}" aria-label="查看 ${escapeHtml(user.displayName || user.userId)}">查看</button></td>
|
||||
<td><wa-button variant="neutral" appearance="outlined" size="s" data-user-id="${escapeHtml(user.userId)}" aria-label="查看 ${escapeHtml(user.displayName || user.userId)}">查看</wa-button></td>
|
||||
</tr>
|
||||
`;
|
||||
}
|
||||
@@ -157,14 +157,14 @@ function detailTemplate(
|
||||
return `
|
||||
<div class="page-heading page-heading--detail">
|
||||
<div>
|
||||
<button class="back-link" data-back>← 返回用户查询</button>
|
||||
<wa-button class="back-link" appearance="plain" data-back>← 返回用户查询</wa-button>
|
||||
<div class="eyebrow">用户详情</div>
|
||||
<h1>${escapeHtml(user.displayName || "未命名用户")}</h1>
|
||||
<p class="mono">${escapeHtml(user.userId)}</p>
|
||||
</div>
|
||||
${
|
||||
role === "SUPER_ADMIN"
|
||||
? '<button class="button button--primary" data-open-grant>人工赠送积分</button>'
|
||||
? '<wa-button variant="brand" appearance="accent" data-open-grant>人工赠送积分</wa-button>'
|
||||
: ""
|
||||
}
|
||||
</div>
|
||||
@@ -208,7 +208,7 @@ function detailTemplate(
|
||||
</div>
|
||||
${
|
||||
ledger.nextCursor
|
||||
? '<div class="pagination-actions"><button class="button button--secondary" data-ledger-more aria-describedby="ledger-pagination-status">加载更多流水</button></div>'
|
||||
? '<div class="pagination-actions"><wa-button variant="neutral" appearance="outlined" data-ledger-more aria-describedby="ledger-pagination-status">加载更多流水</wa-button></div>'
|
||||
: ""
|
||||
}
|
||||
<p class="sr-only" id="ledger-pagination-status" data-ledger-status role="status" aria-live="polite"></p>
|
||||
|
||||
+66
-23
@@ -22,6 +22,18 @@
|
||||
--focus-ring: rgb(0 113 227 / 28%);
|
||||
--danger: #d70015;
|
||||
--success: #248a3d;
|
||||
--wa-font-family-body:
|
||||
-apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",
|
||||
"Helvetica Neue", "PingFang SC", system-ui, sans-serif;
|
||||
--wa-font-family-heading: var(--wa-font-family-body);
|
||||
--wa-color-brand-fill-loud: var(--primary);
|
||||
--wa-color-brand-border-loud: var(--primary);
|
||||
--wa-color-brand-on-loud: #fff;
|
||||
--wa-color-focus: var(--primary);
|
||||
--wa-form-control-border-radius: 12px;
|
||||
--wa-panel-border-radius: 18px;
|
||||
--wa-focus-ring-width: 3px;
|
||||
--wa-focus-ring-offset: 2px;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -177,6 +189,10 @@ h2 {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
wa-button.button--wide {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.button--small {
|
||||
min-height: 34px;
|
||||
padding: 0 12px;
|
||||
@@ -200,6 +216,12 @@ h2 {
|
||||
background: var(--hover-fill);
|
||||
}
|
||||
|
||||
wa-button.icon-button::part(button),
|
||||
wa-button.logout-button::part(button) {
|
||||
min-height: 44px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.icon-button svg,
|
||||
.logout-button svg {
|
||||
width: 21px;
|
||||
@@ -246,17 +268,6 @@ h2 {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.auth-brand::after {
|
||||
display: block;
|
||||
width: min(390px, 80vw);
|
||||
height: 200px;
|
||||
margin-top: 40px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 30px;
|
||||
background: var(--surface-muted);
|
||||
content: "";
|
||||
}
|
||||
|
||||
.auth-brand p {
|
||||
margin-top: 24px;
|
||||
color: var(--muted);
|
||||
@@ -277,6 +288,20 @@ h2 {
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
wa-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
wa-input::part(form-control-label) {
|
||||
color: var(--navy);
|
||||
font-size: 0.88rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
wa-input::part(input) {
|
||||
min-height: 44px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: grid;
|
||||
color: var(--navy);
|
||||
@@ -329,6 +354,18 @@ input:disabled {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
wa-input.totp-input {
|
||||
font-size: inherit;
|
||||
letter-spacing: normal;
|
||||
text-align: initial;
|
||||
}
|
||||
|
||||
wa-input.totp-input::part(input) {
|
||||
font-size: 1.45rem;
|
||||
letter-spacing: 0.4em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.form-error {
|
||||
min-height: 20px;
|
||||
margin: -4px 0 0;
|
||||
@@ -481,6 +518,10 @@ input:disabled {
|
||||
background: var(--hover-fill);
|
||||
}
|
||||
|
||||
wa-button.logout-button::part(button) {
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.logout-button span {
|
||||
font-size: 0.76rem;
|
||||
font-weight: 650;
|
||||
@@ -934,6 +975,13 @@ td strong {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
wa-callout.state-card::part(message) {
|
||||
display: grid;
|
||||
text-align: center;
|
||||
align-content: center;
|
||||
justify-items: center;
|
||||
}
|
||||
|
||||
.state-icon,
|
||||
.confirm-icon {
|
||||
display: grid;
|
||||
@@ -957,6 +1005,13 @@ td strong {
|
||||
animation: spin 700ms linear infinite;
|
||||
}
|
||||
|
||||
.state-spinner {
|
||||
margin-bottom: 12px;
|
||||
font-size: 28px;
|
||||
--indicator-color: var(--primary);
|
||||
--track-color: color-mix(in srgb, var(--primary) 18%, transparent);
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
@@ -1148,10 +1203,6 @@ td strong {
|
||||
right: 22px;
|
||||
bottom: 22px;
|
||||
max-width: min(390px, calc(100vw - 44px));
|
||||
padding: 14px 18px;
|
||||
color: #fff;
|
||||
border-radius: 11px;
|
||||
background: #344054;
|
||||
box-shadow: 0 15px 40px rgb(16 24 40 / 25%);
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
@@ -1165,14 +1216,6 @@ td strong {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.toast--success {
|
||||
background: var(--success);
|
||||
}
|
||||
|
||||
.toast--error {
|
||||
background: var(--danger);
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 3px 6px;
|
||||
color: #364fc7;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import type WaButton from "@awesome.me/webawesome/dist/components/button/button.js";
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import { renderError, renderLoading, setButtonBusy } from "../components/ui";
|
||||
|
||||
describe("Web Awesome UI states", () => {
|
||||
it("renders accessible loading and retry components", () => {
|
||||
const container = document.createElement("div");
|
||||
const retry = vi.fn();
|
||||
|
||||
renderLoading(container, "读取用户");
|
||||
expect(container.querySelector("wa-spinner")).not.toBeNull();
|
||||
expect(container.textContent).toContain("读取用户");
|
||||
|
||||
renderError(container, new Error("failed"), retry);
|
||||
const retryButton = container.querySelector<HTMLElement>("wa-button[data-retry]");
|
||||
expect(container.querySelector('wa-callout[variant="danger"]')).not.toBeNull();
|
||||
retryButton?.click();
|
||||
expect(retry).toHaveBeenCalledOnce();
|
||||
});
|
||||
|
||||
it("uses the Web Awesome loading state and restores the label", () => {
|
||||
const button = document.createElement("wa-button") as WaButton;
|
||||
button.textContent = "提交";
|
||||
|
||||
setButtonBusy(button, true, "提交中…");
|
||||
expect(button.loading).toBe(true);
|
||||
expect(button.disabled).toBe(true);
|
||||
expect(button.textContent).toBe("提交中…");
|
||||
|
||||
setButtonBusy(button, false);
|
||||
expect(button.loading).toBe(false);
|
||||
expect(button.disabled).toBe(false);
|
||||
expect(button.textContent).toBe("提交");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user