feat: интеграция с VPS Tracker
Build, Test, and Push CFDM Docker Image / test (push) Failing after 3m36s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been skipped
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
Build, Test, and Push CFDM Docker Image / test (push) Failing after 3m36s
Build, Test, and Push CFDM Docker Image / build-and-push (push) Has been skipped
Build, Test, and Push CFDM Docker Image / update-wiki (push) Has been skipped
Build, Test, and Push CFDM Docker Image / create-release (push) Has been skipped
Исходящий sync bindings после updateConfig, настройки в app_settings, страница Интеграции, приём событий vps_down для DNS failover. Co-authored-by: Cursor <[email protected]>
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/_auth/settings/')({
|
||||
beforeLoad: () => {
|
||||
throw redirect({ to: '/settings/integrations' })
|
||||
},
|
||||
})
|
||||
@@ -0,0 +1,74 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
|
||||
import { toast } from 'sonner'
|
||||
import type { AppSettingsPatch } from '@cfdm/shared'
|
||||
|
||||
import { api } from '@/lib/api-client'
|
||||
import { QueryState } from '@/components/query-state'
|
||||
import { AppSwitcherEditor } from '@/components/integrations/app-switcher-editor'
|
||||
import {
|
||||
VpsTrackerIntegrationCard,
|
||||
type AppSettingsView,
|
||||
} from '@/components/integrations/vps-tracker-integration-card'
|
||||
import { DEFAULT_APP_SWITCHER_CONFIG } from '@/lib/app-switcher-config'
|
||||
import { appSwitcherQueryKey } from '@/queries/app-switcher'
|
||||
|
||||
type SettingsResponse = AppSettingsView & {
|
||||
id: string
|
||||
appSwitcher: typeof DEFAULT_APP_SWITCHER_CONFIG
|
||||
}
|
||||
|
||||
export const Route = createFileRoute('/_auth/settings/integrations')({
|
||||
component: SettingsIntegrationsPage,
|
||||
})
|
||||
|
||||
function SettingsIntegrationsPage() {
|
||||
const queryClient = useQueryClient()
|
||||
const { data, isLoading, isError, error, refetch } = useQuery({
|
||||
queryKey: ['app-settings'],
|
||||
queryFn: () => api.get<SettingsResponse>('/api/v1/settings'),
|
||||
})
|
||||
|
||||
const saveMut = useMutation({
|
||||
mutationFn: (patch: AppSettingsPatch) =>
|
||||
api.patch<SettingsResponse>('/api/v1/settings', patch),
|
||||
onSuccess: async () => {
|
||||
await queryClient.invalidateQueries({ queryKey: ['app-settings'] })
|
||||
await queryClient.invalidateQueries({ queryKey: appSwitcherQueryKey })
|
||||
toast.success('Настройки сохранены')
|
||||
},
|
||||
onError: () => toast.error('Не удалось сохранить'),
|
||||
})
|
||||
|
||||
return (
|
||||
<QueryState
|
||||
isLoading={isLoading}
|
||||
isError={isError}
|
||||
error={error}
|
||||
onRetry={() => refetch()}
|
||||
>
|
||||
{data ? (
|
||||
<div className="flex flex-col gap-4">
|
||||
<AppSwitcherEditor
|
||||
defaultValues={data.appSwitcher ?? DEFAULT_APP_SWITCHER_CONFIG}
|
||||
isSaving={saveMut.isPending}
|
||||
onSave={(appSwitcher) => saveMut.mutate({ appSwitcher })}
|
||||
/>
|
||||
<VpsTrackerIntegrationCard
|
||||
settings={data}
|
||||
isSaving={saveMut.isPending}
|
||||
onSave={(values) => {
|
||||
const patch: AppSettingsPatch = {
|
||||
vpsTrackerUrl: values.vpsTrackerUrl,
|
||||
vpsTrackerSyncEnabled: values.vpsTrackerSyncEnabled,
|
||||
}
|
||||
const token = values.vpsTrackerIntegrationToken?.trim()
|
||||
if (token) patch.vpsTrackerIntegrationToken = token
|
||||
saveMut.mutate(patch)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</QueryState>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import { createFileRoute, Link, Outlet, useRouterState } from '@tanstack/react-router'
|
||||
import { cn } from '@cfdm/ui/lib/utils'
|
||||
import { PageShell } from '@/components/page-shell'
|
||||
import { PageHeader } from '@/components/page-header'
|
||||
|
||||
export const Route = createFileRoute('/_auth/settings')({
|
||||
component: SettingsLayout,
|
||||
})
|
||||
|
||||
const TABS = [{ to: '/settings/integrations', label: 'Интеграции' }] as const
|
||||
|
||||
function SettingsLayout() {
|
||||
const pathname = useRouterState({ select: (s) => s.location.pathname })
|
||||
|
||||
return (
|
||||
<PageShell>
|
||||
<PageHeader title="Настройки" description="Интеграции с другими приложениями" />
|
||||
<nav className="flex gap-1 border-b pb-0">
|
||||
{TABS.map((tab) => {
|
||||
const active = pathname.startsWith(tab.to)
|
||||
return (
|
||||
<Link
|
||||
key={tab.to}
|
||||
to={tab.to}
|
||||
className={cn(
|
||||
'rounded-t-md px-4 py-2 text-sm font-medium transition-colors',
|
||||
active
|
||||
? 'border border-b-0 bg-background text-foreground'
|
||||
: 'text-muted-foreground hover:text-foreground',
|
||||
)}
|
||||
>
|
||||
{tab.label}
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
<Outlet />
|
||||
</PageShell>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user