refactor(repo): consolidate openflare-server to root and move subprojects to internal/apps

- Merge all files inside openflare-server to the repository root directory.
- Relocate agent, relay, and flared subprojects from internal/ to internal/apps/.
- Combine docker-compose files and update build context paths to root.
- Update GitHub workflows and Dockerfiles to refer to new directories and package names.
- Rewrite Go package imports across all files.
- Resolve database renew test race condition and clean up docs.
This commit is contained in:
ryan
2026-06-19 14:23:29 +08:00
parent 19d476ed7f
commit 63cd906cfc
1064 changed files with 366 additions and 1397 deletions
+27
View File
@@ -0,0 +1,27 @@
"use client"
import {useEffect} from "react"
import {usePathname, useRouter} from "next/navigation"
import {useUser} from "@/contexts/user-context"
/** Redirects unauthenticated users to login while allowing the shell to render first. */
export function useAuthRedirect() {
const router = useRouter()
const pathname = usePathname()
const {user, loading} = useUser()
useEffect(() => {
if (loading || user) {
return
}
const queryString = window.location.search
const callbackUrl = queryString ? `${pathname}${queryString}` : pathname
const loginUrl = new URL("/login", window.location.origin)
loginUrl.searchParams.set("callbackUrl", callbackUrl)
sessionStorage.setItem("redirect_after_login", callbackUrl)
router.replace(loginUrl.toString())
}, [loading, pathname, router, user])
}
+42
View File
@@ -0,0 +1,42 @@
import * as React from 'react';
interface CommonControlledStateProps<T> {
value?: T;
defaultValue?: T;
}
/**
* 受控状态 Hook
* 用于处理受控和非受控组件的状态管理
*
* @example
* ```tsx
* const [value, setValue] = useControlledState({ value: propValue, onChange: console.log })
* ```
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function useControlledState<T, Rest extends any[] = []>(
props: CommonControlledStateProps<T> & {
onChange?: (value: T, ...args: Rest) => void;
},
): readonly [T, (next: T, ...args: Rest) => void] {
const { value, defaultValue, onChange } = props;
const [state, setInternalState] = React.useState<T>(
value !== undefined ? value : (defaultValue as T),
);
React.useEffect(() => {
if (value !== undefined) setInternalState(value);
}, [value]);
const setState = React.useCallback(
(next: T, ...args: Rest) => {
setInternalState(next);
onChange?.(next, ...args);
},
[onChange],
);
return [state, setState] as const;
}
+33
View File
@@ -0,0 +1,33 @@
import * as React from 'react';
import {useInView, type UseInViewOptions} from 'motion/react';
interface UseIsInViewOptions {
inView?: boolean;
inViewOnce?: boolean;
inViewMargin?: UseInViewOptions['margin'];
}
/**
* 检测元素是否在视图中
*
* @example
* ```tsx
* const { ref, isInView } = useIsInView({ inViewOnce: true })
* ```
*/
function useIsInView<T extends HTMLElement = HTMLElement>(
ref: React.Ref<T>,
options: UseIsInViewOptions = {},
) {
const { inView, inViewOnce = false, inViewMargin = '0px' } = options;
const localRef = React.useRef<T>(null);
React.useImperativeHandle(ref, () => localRef.current as T);
const inViewResult = useInView(localRef, {
once: inViewOnce,
margin: inViewMargin,
});
const isInView = !inView || inViewResult;
return { ref: localRef, isInView };
}
export { useIsInView, type UseIsInViewOptions };
+19
View File
@@ -0,0 +1,19 @@
import * as React from "react"
const MOBILE_BREAKPOINT = 768
export function useIsMobile() {
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)
React.useEffect(() => {
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
const onChange = () => {
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
}
mql.addEventListener("change", onChange)
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
return () => mql.removeEventListener("change", onChange)
}, [])
return !!isMobile
}
+20
View File
@@ -0,0 +1,20 @@
import {useQuery} from '@tanstack/react-query'
import services from '@/lib/services'
/**
* 获取公共配置(使用 React Query 进行统一缓存与自动同步)
*
* @example
* ```tsx
* const { config, loading, error } = usePublicConfig()
* ```
*/
export function usePublicConfig() {
const { data: config, isLoading: loading, error } = useQuery({
queryKey: ['public-config'],
queryFn: () => services.config.getPublicConfig(),
staleTime: 5 * 60 * 1000, // 5 分钟缓存
})
return { config: config || null, loading, error }
}