deps: update webui deps
This commit is contained in:
parent
eb8f0be004
commit
b20f7cd1ad
@ -9,7 +9,8 @@
|
|||||||
"preview": "rsbuild preview"
|
"preview": "rsbuild preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@abraham/reflection": "^0.12.0",
|
"@abraham/reflection": "^0.13.0",
|
||||||
|
"@apollo/client": "^3.13.8",
|
||||||
"@codemirror/language": "6.0.0",
|
"@codemirror/language": "6.0.0",
|
||||||
"@corvu/drawer": "^0.2.3",
|
"@corvu/drawer": "^0.2.3",
|
||||||
"@corvu/otp-field": "^0.1.4",
|
"@corvu/otp-field": "^0.1.4",
|
||||||
@ -53,18 +54,19 @@
|
|||||||
"cmdk": "^1.1.1",
|
"cmdk": "^1.1.1",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"embla-carousel-react": "^8.6.0",
|
"embla-carousel-react": "^8.6.0",
|
||||||
"graphiql": "^3.8.3",
|
"graphiql": "^4.0.2",
|
||||||
|
"graphql": "^16.11.0",
|
||||||
"input-otp": "^1.4.2",
|
"input-otp": "^1.4.2",
|
||||||
"jotai": "^2.12.3",
|
"jotai": "^2.12.3",
|
||||||
"jotai-signal": "^0.9.0",
|
"jotai-signal": "^0.9.0",
|
||||||
"lucide-react": "^0.503.0",
|
"lucide-react": "^0.508.0",
|
||||||
"next-themes": "^0.4.6",
|
"next-themes": "^0.4.6",
|
||||||
"oidc-client-rx": "0.1.0-alpha.9",
|
"oidc-client-rx": "0.1.0-alpha.9",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-day-picker": "8.10.1",
|
"react-day-picker": "9.6.7",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-hook-form": "^7.56.0",
|
"react-hook-form": "^7.56.0",
|
||||||
"react-resizable-panels": "^2.1.7",
|
"react-resizable-panels": "^3.0.0",
|
||||||
"recharts": "^2.15.3",
|
"recharts": "^2.15.3",
|
||||||
"rxjs": "^7.8.2",
|
"rxjs": "^7.8.2",
|
||||||
"sonner": "^2.0.3",
|
"sonner": "^2.0.3",
|
||||||
@ -72,8 +74,7 @@
|
|||||||
"tailwindcss": "^4.0.6",
|
"tailwindcss": "^4.0.6",
|
||||||
"tw-animate-css": "^1.2.7",
|
"tw-animate-css": "^1.2.7",
|
||||||
"type-fest": "^4.40.0",
|
"type-fest": "^4.40.0",
|
||||||
"vaul": "^1.1.2",
|
"vaul": "^1.1.2"
|
||||||
"zod": "^3.24.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rsbuild/core": "^1.2.15",
|
"@rsbuild/core": "^1.2.15",
|
||||||
|
@ -1,21 +1,26 @@
|
|||||||
import { UnreachableError } from '@/infra/errors/common';
|
import { AuthService } from '@/domains/auth/auth.service';
|
||||||
import type { Injector, Provider } from '@outposts/injection-js';
|
import type { Injector, Provider } from '@outposts/injection-js';
|
||||||
import type { AnyRouter } from '@tanstack/react-router';
|
import type { AnyRouter } from '@tanstack/react-router';
|
||||||
import {
|
import {
|
||||||
CHECK_AUTH_RESULT_EVENT,
|
|
||||||
type CheckAuthResultEventType,
|
type CheckAuthResultEventType,
|
||||||
OidcSecurityService,
|
|
||||||
provideAuth as provideOidcAuth,
|
provideAuth as provideOidcAuth,
|
||||||
withCheckAuthResultEvent,
|
withCheckAuthResultEvent,
|
||||||
withDefaultFeatures,
|
withDefaultFeatures,
|
||||||
} from 'oidc-client-rx';
|
} from 'oidc-client-rx';
|
||||||
import { withTanstackRouter } from 'oidc-client-rx/adapters/@tanstack/react-router';
|
import { withTanstackRouter } from 'oidc-client-rx/adapters/@tanstack/react-router';
|
||||||
import { NEVER, type Observable, map, of } from 'rxjs';
|
import type { Observable } from 'rxjs';
|
||||||
import { AppAuthMethod, AuthMethodEnum, buildOidcConfig } from './config';
|
import {
|
||||||
|
AppAuthMethod,
|
||||||
|
AuthMethodEnum,
|
||||||
|
type AuthMethodType,
|
||||||
|
buildOidcConfig,
|
||||||
|
} from './config';
|
||||||
|
|
||||||
export function provideAuth(router: AnyRouter): Provider[] {
|
export function provideAuth(router: AnyRouter): Provider[] {
|
||||||
|
const providers: Provider[] = [AuthService];
|
||||||
if (AppAuthMethod === AuthMethodEnum.OIDC) {
|
if (AppAuthMethod === AuthMethodEnum.OIDC) {
|
||||||
return provideOidcAuth(
|
providers.push(
|
||||||
|
...provideOidcAuth(
|
||||||
{
|
{
|
||||||
config: buildOidcConfig(),
|
config: buildOidcConfig(),
|
||||||
},
|
},
|
||||||
@ -25,59 +30,32 @@ export function provideAuth(router: AnyRouter): Provider[] {
|
|||||||
}),
|
}),
|
||||||
withTanstackRouter(router),
|
withTanstackRouter(router),
|
||||||
withCheckAuthResultEvent()
|
withCheckAuthResultEvent()
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return [];
|
return providers;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setupAuthContext(injector: Injector) {
|
export function setupAuthContext(injector: Injector) {
|
||||||
if (AppAuthMethod === AuthMethodEnum.OIDC) {
|
const { authService } = authContextFromInjector(injector);
|
||||||
const oidcSecurityService = injector.get(OidcSecurityService);
|
authService.setup();
|
||||||
oidcSecurityService.checkAuth().subscribe();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
export interface OidcAuthContext {
|
|
||||||
type: typeof AuthMethodEnum.OIDC;
|
export interface AuthContext {
|
||||||
oidcSecurityService: OidcSecurityService;
|
type: AuthMethodType;
|
||||||
|
authService: AuthService;
|
||||||
isAuthenticated$: Observable<boolean>;
|
isAuthenticated$: Observable<boolean>;
|
||||||
userData$: Observable<{}>;
|
userData$: Observable<{}>;
|
||||||
checkAuthResultEvent$: Observable<CheckAuthResultEventType>;
|
checkAuthResultEvent$: Observable<CheckAuthResultEventType>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BasicAuthContext {
|
|
||||||
type: typeof AuthMethodEnum.BASIC;
|
|
||||||
isAuthenticated$: Observable<true>;
|
|
||||||
userData$: Observable<{}>;
|
|
||||||
checkAuthResultEvent$: Observable<CheckAuthResultEventType>;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthContext = OidcAuthContext | BasicAuthContext;
|
|
||||||
|
|
||||||
const BASIC_AUTH_IS_AUTHENTICATED$ = of(true) as Observable<true>;
|
|
||||||
|
|
||||||
const BASIC_AUTH_USER_DATA$ = of({});
|
|
||||||
|
|
||||||
export function authContextFromInjector(injector: Injector): AuthContext {
|
export function authContextFromInjector(injector: Injector): AuthContext {
|
||||||
if (AppAuthMethod === AuthMethodEnum.OIDC) {
|
const authService = injector.get(AuthService);
|
||||||
const oidcSecurityService = injector.get(OidcSecurityService)!;
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
type: AuthMethodEnum.OIDC,
|
type: AppAuthMethod,
|
||||||
oidcSecurityService: injector.get(OidcSecurityService),
|
authService,
|
||||||
isAuthenticated$: oidcSecurityService.isAuthenticated$.pipe(
|
isAuthenticated$: authService.isAuthenticated$,
|
||||||
map((s) => s.isAuthenticated)
|
userData$: authService.userData$,
|
||||||
),
|
checkAuthResultEvent$: authService.checkAuthResultEvent$,
|
||||||
userData$: oidcSecurityService.userData$.pipe(map((s) => s.userData)),
|
|
||||||
checkAuthResultEvent$: injector.get(CHECK_AUTH_RESULT_EVENT),
|
|
||||||
};
|
};
|
||||||
}
|
|
||||||
if (AppAuthMethod === AuthMethodEnum.BASIC) {
|
|
||||||
return {
|
|
||||||
type: AuthMethodEnum.BASIC,
|
|
||||||
isAuthenticated$: BASIC_AUTH_IS_AUTHENTICATED$,
|
|
||||||
userData$: BASIC_AUTH_USER_DATA$,
|
|
||||||
checkAuthResultEvent$: NEVER,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
throw new UnreachableError('Invalid auth method');
|
|
||||||
}
|
}
|
||||||
|
53
apps/webui/src/domains/auth/auth.service.ts
Normal file
53
apps/webui/src/domains/auth/auth.service.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { AppAuthMethod, AuthMethodEnum } from '@/app/auth/config';
|
||||||
|
import { injectInjector } from '@/infra/di/inject';
|
||||||
|
import { Injectable, type Injector } from '@outposts/injection-js';
|
||||||
|
import {
|
||||||
|
CHECK_AUTH_RESULT_EVENT,
|
||||||
|
type CheckAuthResultEventType,
|
||||||
|
OidcSecurityService,
|
||||||
|
} from 'oidc-client-rx';
|
||||||
|
import { NEVER, type Observable, map, of } from 'rxjs';
|
||||||
|
|
||||||
|
const BASIC_AUTH_IS_AUTHENTICATED$ = of(true) as Observable<true>;
|
||||||
|
|
||||||
|
const BASIC_AUTH_USER_DATA$ = of({});
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthService {
|
||||||
|
private injector: Injector = injectInjector();
|
||||||
|
oidcSecurityService: OidcSecurityService | undefined;
|
||||||
|
checkAuthResultEvent$: Observable<CheckAuthResultEventType>;
|
||||||
|
constructor() {
|
||||||
|
if (AppAuthMethod === 'oidc') {
|
||||||
|
this.oidcSecurityService = this.injector.get(OidcSecurityService);
|
||||||
|
this.checkAuthResultEvent$ = this.injector.get(CHECK_AUTH_RESULT_EVENT);
|
||||||
|
} else {
|
||||||
|
this.checkAuthResultEvent$ = NEVER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setup() {
|
||||||
|
if (AppAuthMethod === AuthMethodEnum.OIDC) {
|
||||||
|
this.oidcSecurityService!.checkAuth().subscribe();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
get isAuthenticated$() {
|
||||||
|
return (
|
||||||
|
this.oidcSecurityService?.isAuthenticated$.pipe(
|
||||||
|
map((s) => s.isAuthenticated)
|
||||||
|
) ?? BASIC_AUTH_IS_AUTHENTICATED$
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get userData$() {
|
||||||
|
return (
|
||||||
|
this.oidcSecurityService?.userData$?.pipe(map((s) => s.userData)) ??
|
||||||
|
BASIC_AUTH_USER_DATA$
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAccessToken(): Observable<string | undefined> {
|
||||||
|
return this.oidcSecurityService?.getAccessToken() ?? of(undefined);
|
||||||
|
}
|
||||||
|
}
|
5
apps/webui/src/infra/di/inject.ts
Normal file
5
apps/webui/src/infra/di/inject.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { type InjectionToken, Injector, inject } from '@outposts/injection-js';
|
||||||
|
|
||||||
|
export function injectInjector(): Injector {
|
||||||
|
return inject(Injector as any as InjectionToken<Injector>);
|
||||||
|
}
|
@ -1,19 +1,21 @@
|
|||||||
import * as React from "react"
|
import React from 'react';
|
||||||
|
|
||||||
const MOBILE_BREAKPOINT = 768
|
const MOBILE_BREAKPOINT = 768;
|
||||||
|
|
||||||
export function useIsMobile() {
|
export function useIsMobile() {
|
||||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
|
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
|
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
||||||
const onChange = () => {
|
const onChange = () => {
|
||||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||||
}
|
};
|
||||||
mql.addEventListener("change", onChange)
|
mql.addEventListener('change', onChange);
|
||||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
|
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||||
return () => mql.removeEventListener("change", onChange)
|
return () => mql.removeEventListener('change', onChange);
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
return !!isMobile
|
return !!isMobile;
|
||||||
}
|
}
|
||||||
|
7
apps/webui/src/infra/http/http.service.ts
Normal file
7
apps/webui/src/infra/http/http.service.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { Injectable, inject } from '@outposts/injection-js';
|
||||||
|
import { OidcSecurityService } from 'oidc-client-rx';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class HttpService {
|
||||||
|
authService = inject(OidcSecurityService);
|
||||||
|
}
|
@ -48,16 +48,16 @@ const rootElement = document.getElementById('root');
|
|||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
return (
|
return (
|
||||||
<Suspense>
|
|
||||||
<InjectorProvider injector={injector}>
|
<InjectorProvider injector={injector}>
|
||||||
|
<Suspense>
|
||||||
<RouterProvider
|
<RouterProvider
|
||||||
router={router}
|
router={router}
|
||||||
context={{
|
context={{
|
||||||
injector,
|
injector,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</InjectorProvider>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
|
</InjectorProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import { useAuth } from '@/app/auth/hooks';
|
import { useAuth } from '@/app/auth/hooks';
|
||||||
import { type Fetcher, createGraphiQLFetcher } from '@graphiql/toolkit';
|
import { type Fetcher, createGraphiQLFetcher } from '@graphiql/toolkit';
|
||||||
import { createLazyFileRoute } from '@tanstack/react-router';
|
import { createLazyFileRoute } from '@tanstack/react-router';
|
||||||
import GraphiQL from 'graphiql';
|
import { GraphiQL } from 'graphiql';
|
||||||
import { useCallback } from 'react';
|
import { useCallback } from 'react';
|
||||||
import 'graphiql/graphiql.css';
|
import 'graphiql/graphiql.css';
|
||||||
import { AuthMethodEnum } from '@/app/auth/config';
|
|
||||||
import { firstValueFrom } from 'rxjs';
|
import { firstValueFrom } from 'rxjs';
|
||||||
|
|
||||||
export const Route = createLazyFileRoute('/_app/playground/graphql-api')({
|
export const Route = createLazyFileRoute('/_app/playground/graphql-api')({
|
||||||
@ -16,12 +15,9 @@ function PlaygroundGraphQLApiRouteComponent() {
|
|||||||
|
|
||||||
const fetcher: Fetcher = useCallback(
|
const fetcher: Fetcher = useCallback(
|
||||||
async (props) => {
|
async (props) => {
|
||||||
const accessToken =
|
const accessToken = await firstValueFrom(
|
||||||
authContext.type === AuthMethodEnum.OIDC
|
authContext.authService.getAccessToken()
|
||||||
? await firstValueFrom(
|
);
|
||||||
authContext.oidcSecurityService.getAccessToken()
|
|
||||||
)
|
|
||||||
: undefined;
|
|
||||||
return createGraphiQLFetcher({
|
return createGraphiQLFetcher({
|
||||||
url: '/api/graphql',
|
url: '/api/graphql',
|
||||||
headers: accessToken
|
headers: accessToken
|
||||||
@ -31,11 +27,7 @@ function PlaygroundGraphQLApiRouteComponent() {
|
|||||||
: undefined,
|
: undefined,
|
||||||
})(props);
|
})(props);
|
||||||
},
|
},
|
||||||
[
|
[authContext.authService.getAccessToken]
|
||||||
authContext.type,
|
|
||||||
// @ts-ignore
|
|
||||||
authContext?.oidcSecurityService?.getAccessToken,
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"linter": {
|
"linter": {
|
||||||
"rules": {
|
"rules": {
|
||||||
"style": {
|
"style": {
|
||||||
|
"noParameterProperties": "off",
|
||||||
"noNonNullAssertion": "off"
|
"noNonNullAssertion": "off"
|
||||||
},
|
},
|
||||||
"security": {
|
"security": {
|
||||||
|
24
package.json
24
package.json
@ -14,7 +14,7 @@
|
|||||||
"bump-deps": "npx --yes npm-check-updates --deep -u -x react-day-picker && pnpm install",
|
"bump-deps": "npx --yes npm-check-updates --deep -u -x react-day-picker && pnpm install",
|
||||||
"clean": "git clean -xdf node_modules"
|
"clean": "git clean -xdf node_modules"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.6.1",
|
"packageManager": "pnpm@10.10.0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=22"
|
"node": ">=22"
|
||||||
},
|
},
|
||||||
@ -22,23 +22,9 @@
|
|||||||
"@auto-it/all-contributors": "^11.3.0",
|
"@auto-it/all-contributors": "^11.3.0",
|
||||||
"@auto-it/first-time-contributor": "^11.3.0",
|
"@auto-it/first-time-contributor": "^11.3.0",
|
||||||
"@biomejs/biome": "1.9.4",
|
"@biomejs/biome": "1.9.4",
|
||||||
"@types/node": "^22.13.8",
|
"@types/node": "^22.15.16",
|
||||||
"tsx": "^4.19.2",
|
"tsx": "^4.19.4",
|
||||||
"typescript": "^5.8.2",
|
"typescript": "^5.8.3",
|
||||||
"ultracite": "^4.1.15"
|
"ultracite": "^4.2.4"
|
||||||
},
|
|
||||||
"overrides": {
|
|
||||||
"@headlessui/react": "^2.2.0",
|
|
||||||
"react": "^19.1.0",
|
|
||||||
"react-dom": "^19.1.0",
|
|
||||||
"date-fns": "^4.1.0"
|
|
||||||
},
|
|
||||||
"pnpm": {
|
|
||||||
"overrides": {
|
|
||||||
"@headlessui/react": "^2.2.0",
|
|
||||||
"react": "^19.1.0",
|
|
||||||
"react-dom": "^19.1.0",
|
|
||||||
"date-fns": "^4.1.0"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4658
pnpm-lock.yaml
generated
4658
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user