@aiquants/auth-react-router 0.14.2 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/admin.d.mts +37 -4
- package/dist/admin.d.ts +37 -4
- package/dist/admin.js +1 -1
- package/dist/admin.js.map +1 -1
- package/dist/admin.mjs +1 -1
- package/dist/admin.mjs.map +1 -1
- package/dist/styles/auth-react-router.standalone.css +1 -1
- package/package.json +4 -4
- package/src/admin/labels.ts +4 -0
- package/src/admin/server.ts +40 -4
- package/src/admin/ui.tsx +62 -2
- package/src/admin/views.tsx +34 -3
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.0 | MIT License | https://tailwindcss.com */
|
|
2
2
|
/*! @aiquants/auth-react-router standalone CSS — 非 Tailwind ホスト専用。ホストの Tailwind ビルドと混在させないこと */
|
|
3
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--color-blue-300:oklch(80.9% .105 251.813);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-white:#fff;--spacing:.25rem;--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-bold:700;--tracking-wide:.025em;--radius-2xl:1rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}}@layer base,components;@layer utilities{.collapse{visibility:collapse}.absolute{position:absolute}.fixed{position:fixed}.static{position:static}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.my-4{margin-block:calc(var(--spacing) * 4)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.w-full{width:100%}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.items-center{align-items:center}.justify-center{justify-content:center}.gap-3{gap:calc(var(--spacing) * 3)}.rounded-2xl{border-radius:var(--radius-2xl)}.border{border-style:var(--tw-border-style);border-width:1px}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-blue-500{--tw-gradient-from:var(--color-blue-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-indigo-500{--tw-gradient-to:var(--color-indigo-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-3{padding-block:calc(var(--spacing) * 3)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.text-white{color:var(--color-white)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.drop-shadow{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#0000001a)) drop-shadow(0 1px 1px var(--tw-drop-shadow-color,#0000000f));--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a) drop-shadow(0 1px 1px #0000000f);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}@media (hover:hover){.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:from-blue-600:hover{--tw-gradient-from:var(--color-blue-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.hover\:to-indigo-600:hover{--tw-gradient-to:var(--color-indigo-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-blue-300:focus{--tw-ring-color:var(--color-blue-300)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}
|
|
3
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-border-style:solid;--tw-gradient-position:initial;--tw-gradient-from:#0000;--tw-gradient-via:#0000;--tw-gradient-to:#0000;--tw-gradient-stops:initial;--tw-gradient-via-stops:initial;--tw-gradient-from-position:0%;--tw-gradient-via-position:50%;--tw-gradient-to-position:100%;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-scale-x:1;--tw-scale-y:1;--tw-scale-z:1}}}@layer theme{:root,:host{--color-blue-300:oklch(80.9% .105 251.813);--color-blue-500:oklch(62.3% .214 259.815);--color-blue-600:oklch(54.6% .245 262.881);--color-indigo-500:oklch(58.5% .233 277.117);--color-indigo-600:oklch(51.1% .262 276.966);--color-white:#fff;--spacing:.25rem;--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--font-weight-bold:700;--tracking-wide:.025em;--radius-2xl:1rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1)}}@layer base,components;@layer utilities{.collapse{visibility:collapse}.invisible{visibility:hidden}.absolute{position:absolute}.fixed{position:fixed}.static{position:static}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.my-4{margin-block:calc(var(--spacing) * 4)}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.w-full{width:100%}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.items-center{align-items:center}.justify-center{justify-content:center}.gap-3{gap:calc(var(--spacing) * 3)}.rounded-2xl{border-radius:var(--radius-2xl)}.border{border-style:var(--tw-border-style);border-width:1px}.bg-gradient-to-r{--tw-gradient-position:to right in oklab;background-image:linear-gradient(var(--tw-gradient-stops))}.from-blue-500{--tw-gradient-from:var(--color-blue-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.to-indigo-500{--tw-gradient-to:var(--color-indigo-500);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-3{padding-block:calc(var(--spacing) * 3)}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.text-white{color:var(--color-white)}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.drop-shadow{--tw-drop-shadow-size:drop-shadow(0 1px 2px var(--tw-drop-shadow-color,#0000001a)) drop-shadow(0 1px 1px var(--tw-drop-shadow-color,#0000000f));--tw-drop-shadow:drop-shadow(0 1px 2px #0000001a) drop-shadow(0 1px 1px #0000000f);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-all{transition-property:all;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}@media (hover:hover){.hover\:scale-105:hover{--tw-scale-x:105%;--tw-scale-y:105%;--tw-scale-z:105%;scale:var(--tw-scale-x) var(--tw-scale-y)}.hover\:from-blue-600:hover{--tw-gradient-from:var(--color-blue-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}.hover\:to-indigo-600:hover{--tw-gradient-to:var(--color-indigo-600);--tw-gradient-stops:var(--tw-gradient-via-stops,var(--tw-gradient-position), var(--tw-gradient-from) var(--tw-gradient-from-position), var(--tw-gradient-to) var(--tw-gradient-to-position))}}.focus\:ring-2:focus{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus\:ring-blue-300:focus{--tw-ring-color:var(--color-blue-300)}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-gradient-position{syntax:"*";inherits:false}@property --tw-gradient-from{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-via{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-to{syntax:"<color>";inherits:false;initial-value:#0000}@property --tw-gradient-stops{syntax:"*";inherits:false}@property --tw-gradient-via-stops{syntax:"*";inherits:false}@property --tw-gradient-from-position{syntax:"<length-percentage>";inherits:false;initial-value:0%}@property --tw-gradient-via-position{syntax:"<length-percentage>";inherits:false;initial-value:50%}@property --tw-gradient-to-position{syntax:"<length-percentage>";inherits:false;initial-value:100%}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-scale-x{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-y{syntax:"*";inherits:false;initial-value:1}@property --tw-scale-z{syntax:"*";inherits:false;initial-value:1}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiquants/auth-react-router",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "React Router v7 / v8 auth adapter for @aiquants/auth-core: Google OAuth strategy factory (remix-auth), cookie session storage with DI test backdoor, a loader guard that authenticates at most once per request (token refresh + photo cache), auth route handlers, and a <GoogleForm> login button.",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css"
|
|
@@ -69,9 +69,9 @@
|
|
|
69
69
|
"tsup": "^8.5.1",
|
|
70
70
|
"typescript": "^5.9.3",
|
|
71
71
|
"vitest": "^4.1.8",
|
|
72
|
-
"@aiquants/auth-core": "0.6.0",
|
|
73
72
|
"@aiquants/fuzzy-search": "2.2.0",
|
|
74
73
|
"@aiquants/select-box": "0.9.0",
|
|
74
|
+
"@aiquants/auth-core": "0.6.0",
|
|
75
75
|
"@aiquants/virtualscroll": "3.4.0"
|
|
76
76
|
},
|
|
77
77
|
"peerDependencies": {
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"remix-auth-oauth2": "^3.0.0",
|
|
84
84
|
"@aiquants/auth-core": "^0.6.0",
|
|
85
85
|
"@aiquants/fuzzy-search": "^2.2.0",
|
|
86
|
-
"@aiquants/
|
|
87
|
-
"@aiquants/
|
|
86
|
+
"@aiquants/virtualscroll": "^3.4.0",
|
|
87
|
+
"@aiquants/select-box": "^0.9.0"
|
|
88
88
|
},
|
|
89
89
|
"engines": {
|
|
90
90
|
"node": ">=18.0.0",
|
package/src/admin/labels.ts
CHANGED
|
@@ -277,6 +277,8 @@ export type UserAdminLabels = {
|
|
|
277
277
|
syncQueued: string
|
|
278
278
|
/** 同じ範囲の要求が既に待っていて、積まれなかったことの説明。 */
|
|
279
279
|
syncAlreadyPending: string
|
|
280
|
+
/** 連携は作れたが、その直後の同期要求を積めなかったときの告知。 */
|
|
281
|
+
linkSyncNotQueued: string
|
|
280
282
|
/** 同期が滞っていることの警告。停止はセキュリティ事象である。 */
|
|
281
283
|
staleWarning: string
|
|
282
284
|
disabledWarning: string
|
|
@@ -526,6 +528,7 @@ export const defaultUserAdminLabels: UserAdminLabels = {
|
|
|
526
528
|
syncNow: "Sync now",
|
|
527
529
|
syncQueued: "Queued",
|
|
528
530
|
syncAlreadyPending: "A synchronization request for this scope is already waiting, so nothing was queued. If it never completes, the synchronization job may not be running.",
|
|
531
|
+
linkSyncNotQueued: 'The link was created, but a synchronization request could not be queued. Use "Sync now" on the Directory tab.',
|
|
529
532
|
staleWarning: "Synchronization is overdue. Until it runs, people removed upstream keep their access.",
|
|
530
533
|
disabledWarning: "Directory synchronization is not wired for this deployment. Existing memberships remain in force and are no longer being refreshed.",
|
|
531
534
|
pausedTemplate: "{paused} of {total} links are paused",
|
|
@@ -751,6 +754,7 @@ export const jaUserAdminLabels: UserAdminLabels = {
|
|
|
751
754
|
syncNow: "今すぐ同期",
|
|
752
755
|
syncQueued: "同期を要求しました",
|
|
753
756
|
syncAlreadyPending: "この範囲の同期要求は既に待機中のため、新しくは積みませんでした。いつまでも完了しない場合、同期ジョブが動いていない可能性があります。",
|
|
757
|
+
linkSyncNotQueued: "連携は作成しましたが、同期要求を積めませんでした。ディレクトリ連携タブの「今すぐ同期」で要求してください。",
|
|
754
758
|
staleWarning: "同期が滞っています。実行されるまで、上流から外れた人のアクセスは残り続けます。",
|
|
755
759
|
disabledWarning: "この配備ではディレクトリ同期が結線されていません。既存の所属はそのまま有効で、更新だけが止まっています。",
|
|
756
760
|
pausedTemplate: "連携 {total} 件のうち {paused} 件が停止中",
|
package/src/admin/server.ts
CHANGED
|
@@ -551,6 +551,16 @@ export type UserAdminCatalogSnapshot = AuthDirectoryCatalogSnapshot
|
|
|
551
551
|
*/
|
|
552
552
|
export type UserAdminSyncRequestOutcome = "queued" | "already-pending"
|
|
553
553
|
|
|
554
|
+
/**
|
|
555
|
+
* What the sync request made right after a link was created did.
|
|
556
|
+
* 連携の直後に積む同期要求が行ったこと。
|
|
557
|
+
*
|
|
558
|
+
* `not-queued` は **連携は成功したが要求は積めなかった** ことを表す。要求の失敗を連携の失敗として
|
|
559
|
+
* 投げると、運用者には「連携に失敗した」と伝わる一方で上流所有のグループは既に増えている。
|
|
560
|
+
* 連携の結果と要求の結果は別の事実なので、別の値で運ぶ。
|
|
561
|
+
*/
|
|
562
|
+
export type UserAdminLinkSyncOutcome = UserAdminSyncRequestOutcome | "not-queued"
|
|
563
|
+
|
|
554
564
|
/**
|
|
555
565
|
* Why the upstream catalog could not be listed, classified for display.
|
|
556
566
|
* 上流の候補一覧を引けなかった理由を、表示のために分類したもの。
|
|
@@ -728,6 +738,31 @@ export function createUserAdminApp(config: UserAdminAppConfig) {
|
|
|
728
738
|
}
|
|
729
739
|
}
|
|
730
740
|
|
|
741
|
+
/**
|
|
742
|
+
* Queues a sync for a group that was linked a moment ago, without letting the queue fail the link.
|
|
743
|
+
* たった今連携したグループの同期を積む処理 (要求の失敗で連携の結果を覆さない)。
|
|
744
|
+
*
|
|
745
|
+
* ⚠️ 連携を作った時点で、そのグループの所属は次の全件走査 (既定 10 分) まで空のままである。
|
|
746
|
+
* 運用者が別途「今すぐ同期」を押さなければならないことは、画面のどこにも書かれていない。
|
|
747
|
+
* ここで積めば、常駐しているジョブが数秒で拾う。
|
|
748
|
+
*
|
|
749
|
+
* ⚠️ 例外を伝播させない。連携は **既に成功している** ので、ここで投げると `{ ok: false }` が
|
|
750
|
+
* 返り「連携に失敗した」と伝わる一方で、上流所有のグループは増えている (作成対話が守った
|
|
751
|
+
* 不可分性が、結果の報告で崩れる)。積めなかった事実は `not-queued` として画面に言わせる。
|
|
752
|
+
*
|
|
753
|
+
* @param groupId The group that was just linked. 連携したばかりのグループ。
|
|
754
|
+
* @param request The incoming request, for the audit actor. 監査用の主体を解決するリクエスト。
|
|
755
|
+
* @returns What the request did. 要求が行ったこと。
|
|
756
|
+
*/
|
|
757
|
+
async function requestSyncAfterLink(groupId: number, request: Request): Promise<UserAdminLinkSyncOutcome> {
|
|
758
|
+
try {
|
|
759
|
+
return await config.store.requestDirectorySync(groupId, await actorOf(request))
|
|
760
|
+
} catch (error) {
|
|
761
|
+
console.error(`[@aiquants/auth-react-router] the link for group ${groupId} was created, but its sync request could not be queued:`, error)
|
|
762
|
+
return "not-queued"
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
|
|
731
766
|
/**
|
|
732
767
|
* Decides whether this request may list the upstream catalog. 候補一覧を引いてよいかを決める処理。
|
|
733
768
|
*
|
|
@@ -1050,7 +1085,7 @@ export function createUserAdminApp(config: UserAdminAppConfig) {
|
|
|
1050
1085
|
// 候補一覧が在るなら、選択肢に無いキーは受け付けない。手入力の綴り違いは「10 分後に
|
|
1051
1086
|
// 赤いバナー」ではなく、その場で分かるべきである
|
|
1052
1087
|
assertKnownUpstream(externalId, externalKey, resolved)
|
|
1053
|
-
await config.store.createLinkedGroup({
|
|
1088
|
+
const group = await config.store.createLinkedGroup({
|
|
1054
1089
|
groupKey: requireText(formData, "groupKey"),
|
|
1055
1090
|
name: requireText(formData, "name"),
|
|
1056
1091
|
description: optionalText(formData, "description"),
|
|
@@ -1059,7 +1094,7 @@ export function createUserAdminApp(config: UserAdminAppConfig) {
|
|
|
1059
1094
|
externalKey,
|
|
1060
1095
|
membershipMode: requireMembershipMode(formData),
|
|
1061
1096
|
})
|
|
1062
|
-
return { ok: true }
|
|
1097
|
+
return { ok: true, syncRequest: await requestSyncAfterLink(group.id, request) }
|
|
1063
1098
|
}
|
|
1064
1099
|
if (intent === "linkDirectoryGroup") {
|
|
1065
1100
|
const externalKey = requireText(formData, "externalKey")
|
|
@@ -1068,13 +1103,14 @@ export function createUserAdminApp(config: UserAdminAppConfig) {
|
|
|
1068
1103
|
const externalId = resolveExternalId(formData, externalKey, resolved)
|
|
1069
1104
|
await assertNotAlreadyLinked(externalId, externalKey)
|
|
1070
1105
|
assertKnownUpstream(externalId, externalKey, resolved)
|
|
1071
|
-
|
|
1106
|
+
const groupId = requireId(formData, "groupId")
|
|
1107
|
+
await config.store.linkDirectoryGroup(groupId, {
|
|
1072
1108
|
provider,
|
|
1073
1109
|
externalId,
|
|
1074
1110
|
externalKey,
|
|
1075
1111
|
membershipMode: requireMembershipMode(formData),
|
|
1076
1112
|
})
|
|
1077
|
-
return { ok: true }
|
|
1113
|
+
return { ok: true, syncRequest: await requestSyncAfterLink(groupId, request) }
|
|
1078
1114
|
}
|
|
1079
1115
|
if (intent === "unlinkDirectoryGroup") {
|
|
1080
1116
|
await config.store.unlinkDirectoryGroup(requireId(formData, "groupId"))
|
package/src/admin/ui.tsx
CHANGED
|
@@ -5,14 +5,72 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import type { AuthDirectoryStatus, AuthGroup } from "@aiquants/auth-core"
|
|
7
7
|
import type React from "react"
|
|
8
|
-
import {
|
|
8
|
+
import { useEffect, useRef } from "react"
|
|
9
|
+
import { Link, useLoaderData, useRevalidator } from "react-router"
|
|
9
10
|
import { fill, formatDateTime } from "./format"
|
|
10
11
|
import { defaultUserAdminLabels, type UserAdminLabels } from "./labels"
|
|
11
12
|
import type { UserAdminPermission } from "./server"
|
|
12
13
|
|
|
14
|
+
/**
|
|
15
|
+
* How the console keeps itself current while "sync now" requests are waiting for the job.
|
|
16
|
+
* 「今すぐ同期」の要求がジョブを待っている間、画面が自分を最新に保つ間隔と上限。
|
|
17
|
+
*
|
|
18
|
+
* The loader only re-runs after the console's own submissions, so a request the job finishes a few
|
|
19
|
+
* seconds later would stay invisible until a reload. While at least one request is pending the
|
|
20
|
+
* console re-validates every `intervalMs`, and gives up after `maxMs` so a deployment with no job
|
|
21
|
+
* consuming the queue does not poll forever from every open tab.
|
|
22
|
+
* ローダーは自分の送信のあとにしか再実行されないため、ジョブが数秒後に終えた要求はリロードまで
|
|
23
|
+
* 見えない。未処理の要求が 1 件でもある間は `intervalMs` ごとに再検証し、`maxMs` で諦める
|
|
24
|
+
* (キューを消化するジョブが居ない配備で、開いている全タブが永久に叩き続けないため)。
|
|
25
|
+
*/
|
|
26
|
+
export type PendingSyncPolling = {
|
|
27
|
+
/** Milliseconds between re-validations. 再検証の間隔 (ミリ秒)。 */
|
|
28
|
+
intervalMs: number
|
|
29
|
+
/** Milliseconds after which polling stops for an unchanged pending count. 件数が変わらないまま諦めるまでの時間 (ミリ秒)。 */
|
|
30
|
+
maxMs: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Defaults: every 3 seconds, for up to 5 minutes. 既定値 (3 秒ごと、最長 5 分)。 */
|
|
34
|
+
export const DEFAULT_PENDING_SYNC_POLLING: PendingSyncPolling = { intervalMs: 3_000, maxMs: 300_000 }
|
|
35
|
+
|
|
13
36
|
export type UserAdminShellProps = {
|
|
14
37
|
children?: React.ReactNode
|
|
15
38
|
renderHeader?: (props: { title: string; annotation: React.ReactNode }) => React.ReactNode
|
|
39
|
+
/** Polling while sync requests are pending; defaults to {@link DEFAULT_PENDING_SYNC_POLLING}. 未処理中のポーリング設定。 */
|
|
40
|
+
pendingSyncPolling?: PendingSyncPolling
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Re-validates the route data periodically while sync requests are waiting for the job.
|
|
45
|
+
* 同期要求がジョブを待っている間、ルートのデータを定期的に再検証する処理。
|
|
46
|
+
*
|
|
47
|
+
* @param pendingRequestCount Requests not yet finished, from the loader. ローダーが返した未処理件数。
|
|
48
|
+
* @param polling Interval and cap. 間隔と上限。
|
|
49
|
+
* @returns Nothing. 戻り値なし。
|
|
50
|
+
*/
|
|
51
|
+
function usePendingSyncRevalidation(pendingRequestCount: number, polling: PendingSyncPolling): void {
|
|
52
|
+
const revalidator = useRevalidator()
|
|
53
|
+
// 進行中の再検証に重ねない。重ねると応答順が入れ替わり、古い結果が新しい結果を上書きし得る。
|
|
54
|
+
// 効果のクロージャは古い state を掴むので、毎描画で更新する参照から読む
|
|
55
|
+
const stateRef = useRef(revalidator.state)
|
|
56
|
+
stateRef.current = revalidator.state
|
|
57
|
+
const { revalidate } = revalidator
|
|
58
|
+
const { intervalMs, maxMs } = polling
|
|
59
|
+
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
if (pendingRequestCount <= 0) return
|
|
62
|
+
// 件数が変わるたびに効果が張り直され、上限も測り直される。上限が効くのは件数が
|
|
63
|
+
// 変わらないまま (誰にも拾われないまま) 時間だけが過ぎる場合である
|
|
64
|
+
const startedAt = Date.now()
|
|
65
|
+
const timer = setInterval(() => {
|
|
66
|
+
if (Date.now() - startedAt >= maxMs) {
|
|
67
|
+
clearInterval(timer)
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
if (stateRef.current === "idle") void revalidate()
|
|
71
|
+
}, intervalMs)
|
|
72
|
+
return () => clearInterval(timer)
|
|
73
|
+
}, [pendingRequestCount, intervalMs, maxMs, revalidate])
|
|
16
74
|
}
|
|
17
75
|
|
|
18
76
|
/**
|
|
@@ -97,10 +155,12 @@ function DirectorySyncBanner({ status, labels, groups }: { status?: AuthDirector
|
|
|
97
155
|
* Shell layout for user administration pages with header annotation delegation.
|
|
98
156
|
* ヘッダー注釈委譲を備えたユーザー管理ページのシェルレイアウト。
|
|
99
157
|
*/
|
|
100
|
-
export function UserAdminShell({ children, renderHeader }: UserAdminShellProps) {
|
|
158
|
+
export function UserAdminShell({ children, renderHeader, pendingSyncPolling }: UserAdminShellProps) {
|
|
101
159
|
// ⚠️ ここは `UserAdminLoaderData` と違い **すべて任意** で受ける。シェルは loader を持たない
|
|
102
160
|
// 呼び出し (エラー境界の外側、テスト、部分描画) からも描かれるため、欠落を落ちる理由にしない
|
|
103
161
|
const data = useLoaderData() as { segment?: string; labels?: UserAdminLabels; myPermissions?: Record<string, UserAdminPermission | undefined> | null; resourceLabels?: Record<string, string>; directoryStatus?: AuthDirectoryStatus; groups?: AuthGroup[] } | undefined
|
|
162
|
+
// 同期状態が無い呼び出し (ディレクトリ未結線・部分描画) に未処理の要求は存在しない
|
|
163
|
+
usePendingSyncRevalidation(data?.directoryStatus?.pendingRequestCount ?? 0, pendingSyncPolling ?? DEFAULT_PENDING_SYNC_POLLING)
|
|
104
164
|
const segment = data?.segment ?? "users"
|
|
105
165
|
const labels = data?.labels ?? defaultUserAdminLabels
|
|
106
166
|
// null = 未結線または取得失敗。空 {} も「分からない」扱いで、いずれも許可として描画しない
|
package/src/admin/views.tsx
CHANGED
|
@@ -274,6 +274,26 @@ const matchStyle: Record<"exact" | "fuzzy", React.CSSProperties> = {
|
|
|
274
274
|
fuzzy: { backgroundColor: "#fef3c7", color: "#78350f", borderRadius: "2px", fontWeight: 600 },
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Turns the sync-request part of an action result into a notice, or nothing when there is nothing to say.
|
|
279
|
+
* 操作結果のうち同期要求の部分を告知文へ写す処理 (言うことが無ければ `null`)。
|
|
280
|
+
*
|
|
281
|
+
* ⚠️ `queued` は告げない。積めたことの結果は、数秒後の再検証で `最終同期` が進むことそのもの
|
|
282
|
+
* である。告げるのは **押しても何も積まれなかった** 2 つの場合だけで、黙ると押しても反応しない
|
|
283
|
+
* 画面になる。
|
|
284
|
+
*
|
|
285
|
+
* @param actionResult The fetcher's last result. 直前の操作結果。
|
|
286
|
+
* @param labels Active labels. 使用中のラベル。
|
|
287
|
+
* @returns The notice text, or `null`. 告知文、または `null`。
|
|
288
|
+
*/
|
|
289
|
+
function syncRequestNoticeOf(actionResult: unknown, labels: UserAdminLabels): string | null {
|
|
290
|
+
if (typeof actionResult !== "object" || actionResult === null) return null
|
|
291
|
+
const outcome = (actionResult as { syncRequest?: unknown }).syncRequest
|
|
292
|
+
if (outcome === "already-pending") return labels.directoryView.syncAlreadyPending
|
|
293
|
+
if (outcome === "not-queued") return labels.directoryView.linkSyncNotQueued
|
|
294
|
+
return null
|
|
295
|
+
}
|
|
296
|
+
|
|
277
297
|
/**
|
|
278
298
|
* Indexes linked groups by both upstream keys. 連携済みグループを 2 つの上流キーで索引する処理。
|
|
279
299
|
*
|
|
@@ -698,6 +718,7 @@ export function GroupsView() {
|
|
|
698
718
|
const fetcher = useFetcher()
|
|
699
719
|
const { submit: submitThen, resetResult, result: actionResult } = useSubmitThen(fetcher)
|
|
700
720
|
const labels = data.labels ?? defaultUserAdminLabels
|
|
721
|
+
const syncRequestNotice = syncRequestNoticeOf(actionResult, labels)
|
|
701
722
|
|
|
702
723
|
const [searchQuery, setSearchQuery] = useState("")
|
|
703
724
|
const [modal, setModal] = useState<{ kind: "create" } | { kind: "edit"; group: AuthGroup } | null>(null)
|
|
@@ -848,6 +869,16 @@ export function GroupsView() {
|
|
|
848
869
|
<section data-testid="groups-view-section" style={{ display: "flex", flexDirection: "column", gap: "1rem" }}>
|
|
849
870
|
<h2 style={heading}>{labels.groupsView.title}</h2>
|
|
850
871
|
{!modal && <ActionError result={actionResult} />}
|
|
872
|
+
{/* ⚠️ ライブリージョンは **常に置いておき、中身だけ入れ替える** (連携面と同じ理由)。
|
|
873
|
+
上流から作った直後に要求が積めなかったことを、閉じた対話の外で伝える唯一の場所 */}
|
|
874
|
+
<p
|
|
875
|
+
data-testid="groups-sync-request-notice"
|
|
876
|
+
role="status"
|
|
877
|
+
aria-live="polite"
|
|
878
|
+
hidden={syncRequestNotice === null}
|
|
879
|
+
style={syncRequestNotice === null ? undefined : { margin: 0, padding: "0.5rem 0.7rem", borderRadius: "6px", backgroundColor: "#fffbeb", border: "1px solid #fde68a", color: "#92400e", fontSize: "0.8rem" }}>
|
|
880
|
+
{syncRequestNotice}
|
|
881
|
+
</p>
|
|
851
882
|
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", backgroundColor: "#ffffff", padding: "1rem", borderRadius: "8px", border: "1px solid #e2e8f0" }}>
|
|
852
883
|
<input
|
|
853
884
|
type="text"
|
|
@@ -1682,7 +1713,7 @@ export function DirectoryView() {
|
|
|
1682
1713
|
* とき積み増さないのは正しいが、黙って成功に見せると、押しても何も起きない画面になる —
|
|
1683
1714
|
* しかもその待っている行が誰にも拾われなければ、以後の押下すべてが同じように消える
|
|
1684
1715
|
*/
|
|
1685
|
-
const syncRequestNotice =
|
|
1716
|
+
const syncRequestNotice = syncRequestNoticeOf(actionResult, labels)
|
|
1686
1717
|
/**
|
|
1687
1718
|
* 「要求しました」を出し続けてよいグループ。
|
|
1688
1719
|
*
|
|
@@ -2075,14 +2106,14 @@ export type AuthUserAdminAppViewProps = UserAdminShellProps
|
|
|
2075
2106
|
* Top-level application view component for the user administration UI (`/users/*`).
|
|
2076
2107
|
* ユーザー管理 UI 全体を描画するトップレベルコンポーネント。
|
|
2077
2108
|
*/
|
|
2078
|
-
export function AuthUserAdminAppView({ renderHeader }: AuthUserAdminAppViewProps = {}) {
|
|
2109
|
+
export function AuthUserAdminAppView({ renderHeader, pendingSyncPolling }: AuthUserAdminAppViewProps = {}) {
|
|
2079
2110
|
const data = useLoaderData() as UserAdminLoaderData | undefined
|
|
2080
2111
|
const segment = data?.segment ?? "users"
|
|
2081
2112
|
// 素のオブジェクト参照だと `segment="constructor"` が真値を返し、画面が描けなくなる
|
|
2082
2113
|
const View = Object.hasOwn(SEGMENT_VIEWS, segment) ? SEGMENT_VIEWS[segment] : UsersView
|
|
2083
2114
|
|
|
2084
2115
|
return (
|
|
2085
|
-
<UserAdminShell renderHeader={renderHeader}>
|
|
2116
|
+
<UserAdminShell renderHeader={renderHeader} pendingSyncPolling={pendingSyncPolling}>
|
|
2086
2117
|
<View />
|
|
2087
2118
|
</UserAdminShell>
|
|
2088
2119
|
)
|