chore: update API configuration and enhance authentication handling

- Updated Vite configuration to use the production API endpoint.
- Modified TypeScript build info to include new config file.
- Refactored API client to support authenticated URLs.
- Updated various components to utilize the new authenticated API URL for media fetching.
- Removed obsolete CSS and JS files from the distribution directory.
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-06-24 18:12:41 +03:00
parent da0a5aa187
commit 27351e83d2
17 changed files with 216 additions and 191 deletions
+1
View File
@@ -0,0 +1 @@
VITE_API_URL=https://api.azaion.com
+1
View File
@@ -0,0 +1 @@
VITE_API_URL=https://api.azaion.com
-164
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+152
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+13 -13
View File
@@ -1,13 +1,13 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>AZAION</title> <title>AZAION</title>
<script type="module" crossorigin src="/assets/index-B-KLvAXK.js"></script> <script type="module" crossorigin src="/assets/index-Da0DwgCt.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Du68yxJU.css"> <link rel="stylesheet" crossorigin href="/assets/index-CUCmanMv.css">
</head> </head>
<body class="bg-[#1e1e1e] text-[#adb5bd]"> <body class="bg-[#1e1e1e] text-[#adb5bd]">
<div id="root"></div> <div id="root"></div>
</body>
</body> </html>
+12 -3
View File
@@ -1,3 +1,5 @@
import { apiUrl } from '../config'
let accessToken: string | null = null let accessToken: string | null = null
export function setToken(token: string | null) { export function setToken(token: string | null) {
@@ -8,6 +10,13 @@ export function getToken() {
return accessToken return accessToken
} }
export function authenticatedApiUrl(path: string): string {
const url = apiUrl(path)
if (!accessToken) return url
const separator = url.includes('?') ? '&' : '?'
return `${url}${separator}access_token=${encodeURIComponent(accessToken)}`
}
async function handleResponse<T>(res: Response): Promise<T> { async function handleResponse<T>(res: Response): Promise<T> {
if (res.status === 204) return undefined as T if (res.status === 204) return undefined as T
if (!res.ok) { if (!res.ok) {
@@ -22,13 +31,13 @@ async function request<T>(url: string, options: RequestInit = {}): Promise<T> {
if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`) if (accessToken) headers.set('Authorization', `Bearer ${accessToken}`)
if (options.body && typeof options.body === 'string') headers.set('Content-Type', 'application/json') if (options.body && typeof options.body === 'string') headers.set('Content-Type', 'application/json')
let res = await fetch(url, { ...options, headers }) let res = await fetch(apiUrl(url), { ...options, headers, credentials: 'include' })
if (res.status === 401 && accessToken) { if (res.status === 401 && accessToken) {
const refreshed = await refreshToken() const refreshed = await refreshToken()
if (refreshed) { if (refreshed) {
headers.set('Authorization', `Bearer ${accessToken}`) headers.set('Authorization', `Bearer ${accessToken}`)
res = await fetch(url, { ...options, headers }) res = await fetch(apiUrl(url), { ...options, headers, credentials: 'include' })
} else { } else {
setToken(null) setToken(null)
window.location.href = '/login' window.location.href = '/login'
@@ -41,7 +50,7 @@ async function request<T>(url: string, options: RequestInit = {}): Promise<T> {
async function refreshToken(): Promise<boolean> { async function refreshToken(): Promise<boolean> {
try { try {
const res = await fetch('/api/admin/auth/refresh', { method: 'POST', credentials: 'include' }) const res = await fetch(apiUrl('/api/admin/auth/refresh'), { method: 'POST', credentials: 'include' })
if (!res.ok) return false if (!res.ok) return false
const data = await res.json() const data = await res.json()
setToken(data.token) setToken(data.token)
+5 -1
View File
@@ -1,3 +1,4 @@
import { apiUrl } from '../config'
import { getToken } from './client' import { getToken } from './client'
export function createSSE<T>( export function createSSE<T>(
@@ -5,8 +6,11 @@ export function createSSE<T>(
onMessage: (data: T) => void, onMessage: (data: T) => void,
onError?: (err: Event) => void, onError?: (err: Event) => void,
): () => void { ): () => void {
const resolved = apiUrl(url)
const token = getToken() const token = getToken()
const fullUrl = token ? `${url}${url.includes('?') ? '&' : '?'}access_token=${token}` : url const fullUrl = token
? `${resolved}${resolved.includes('?') ? '&' : '?'}access_token=${encodeURIComponent(token)}`
: resolved
const source = new EventSource(fullUrl) const source = new EventSource(fullUrl)
+11
View File
@@ -0,0 +1,11 @@
const raw = import.meta.env.VITE_API_URL
export const API_BASE_URL =
raw === '' ? '' : (raw ?? 'https://api.azaion.com')
export function apiUrl(path: string): string {
if (path.startsWith('http')) return path
if (!API_BASE_URL) return path
const base = API_BASE_URL.replace(/\/$/, '')
const normalized = path.startsWith('/') ? path : `/${path}`
return `${base}${normalized}`
}
+2 -2
View File
@@ -1,6 +1,6 @@
import { useState, useCallback, useEffect, useRef } from 'react' import { useState, useCallback, useEffect, useRef } from 'react'
import { useResizablePanel } from '../../hooks/useResizablePanel' import { useResizablePanel } from '../../hooks/useResizablePanel'
import { api } from '../../api/client' import { api, authenticatedApiUrl } from '../../api/client'
import MediaList from './MediaList' import MediaList from './MediaList'
import VideoPlayer, { type VideoPlayerHandle } from './VideoPlayer' import VideoPlayer, { type VideoPlayerHandle } from './VideoPlayer'
import CanvasEditor, { type CanvasEditorHandle } from './CanvasEditor' import CanvasEditor, { type CanvasEditorHandle } from './CanvasEditor'
@@ -96,7 +96,7 @@ export default function AnnotationsPage() {
img.crossOrigin = 'anonymous' img.crossOrigin = 'anonymous'
img.src = selectedMedia.path.startsWith('blob:') img.src = selectedMedia.path.startsWith('blob:')
? selectedMedia.path ? selectedMedia.path
: `/api/annotations/media/${selectedMedia.id}/file` : authenticatedApiUrl(`/api/annotations/media/${selectedMedia.id}/file`)
await new Promise(res => { img.onload = res; img.onerror = res }) await new Promise(res => { img.onload = res; img.onerror = res })
w = img.naturalWidth w = img.naturalWidth
h = img.naturalHeight h = img.naturalHeight
+3 -2
View File
@@ -1,6 +1,7 @@
import { useRef, useEffect, useState, useCallback, forwardRef, useImperativeHandle } from 'react' import { useRef, useEffect, useState, useCallback, forwardRef, useImperativeHandle } from 'react'
import { MediaType } from '../../types' import { MediaType } from '../../types'
import type { Media, AnnotationListItem, Detection, Affiliation, CombatReadiness } from '../../types' import type { Media, AnnotationListItem, Detection, Affiliation, CombatReadiness } from '../../types'
import { authenticatedApiUrl } from '../../api/client'
import { getClassColor, getPhotoModeSuffix, getClassNameFallback } from './classColors' import { getClassColor, getPhotoModeSuffix, getClassNameFallback } from './classColors'
interface Props { interface Props {
@@ -76,11 +77,11 @@ const CanvasEditor = forwardRef<CanvasEditorHandle, Props>(function CanvasEditor
const img = new Image() const img = new Image()
img.crossOrigin = 'anonymous' img.crossOrigin = 'anonymous'
if (annotation && !media.path.startsWith('blob:')) { if (annotation && !media.path.startsWith('blob:')) {
img.src = `/api/annotations/annotations/${annotation.id}/image` img.src = authenticatedApiUrl(`/api/annotations/annotations/${annotation.id}/image`)
} else if (media.path.startsWith('blob:')) { } else if (media.path.startsWith('blob:')) {
img.src = media.path img.src = media.path
} else { } else {
img.src = `/api/annotations/media/${media.id}/file` img.src = authenticatedApiUrl(`/api/annotations/media/${media.id}/file`)
} }
img.onload = () => { img.onload = () => {
imgRef.current = img imgRef.current = img
+2 -1
View File
@@ -1,5 +1,6 @@
import { useRef, useState, useCallback, useEffect, forwardRef, useImperativeHandle } from 'react' import { useRef, useState, useCallback, useEffect, forwardRef, useImperativeHandle } from 'react'
import { FaPlay, FaPause, FaStop, FaStepBackward, FaStepForward, FaVolumeMute, FaVolumeUp } from 'react-icons/fa' import { FaPlay, FaPause, FaStop, FaStepBackward, FaStepForward, FaVolumeMute, FaVolumeUp } from 'react-icons/fa'
import { authenticatedApiUrl } from '../../api/client'
import type { Media } from '../../types' import type { Media } from '../../types'
interface Props { interface Props {
@@ -38,7 +39,7 @@ const VideoPlayer = forwardRef<VideoPlayerHandle, Props>(function VideoPlayer({
const videoUrl = media.path.startsWith('blob:') const videoUrl = media.path.startsWith('blob:')
? media.path ? media.path
: `/api/annotations/media/${media.id}/file` : authenticatedApiUrl(`/api/annotations/media/${media.id}/file`)
const stepFrames = useCallback((count: number) => { const stepFrames = useCallback((count: number) => {
const video = videoRef.current const video = videoRef.current
+2 -2
View File
@@ -1,6 +1,6 @@
import { useState, useEffect, useCallback } from 'react' import { useState, useEffect, useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import { api } from '../../api/client' import { api, authenticatedApiUrl } from '../../api/client'
import { useDebounce } from '../../hooks/useDebounce' import { useDebounce } from '../../hooks/useDebounce'
import { useResizablePanel } from '../../hooks/useResizablePanel' import { useResizablePanel } from '../../hooks/useResizablePanel'
import { useFlight } from '../../components/FlightContext' import { useFlight } from '../../components/FlightContext'
@@ -183,7 +183,7 @@ export default function DatasetPage() {
} ${item.isSeed ? 'ring-2 ring-az-red' : ''}`} } ${item.isSeed ? 'ring-2 ring-az-red' : ''}`}
> >
<img <img
src={`/api/annotations/annotations/${item.annotationId}/thumbnail`} src={authenticatedApiUrl(`/api/annotations/annotations/${item.annotationId}/thumbnail`)}
alt={item.imageName} alt={item.imageName}
className="w-full h-32 object-cover bg-az-bg" className="w-full h-32 object-cover bg-az-bg"
loading="lazy" loading="lazy"
+8
View File
@@ -1,4 +1,12 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_API_URL?: string
}
interface ImportMeta {
readonly env: ImportMetaEnv
}
declare module '*.css' declare module '*.css'
declare module 'leaflet-polylinedecorator' declare module 'leaflet-polylinedecorator'
+1 -1
View File
@@ -1 +1 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/api/client.ts","./src/api/sse.ts","./src/auth/authcontext.tsx","./src/auth/protectedroute.tsx","./src/components/confirmdialog.tsx","./src/components/detectionclasses.tsx","./src/components/flightcontext.tsx","./src/components/header.tsx","./src/components/helpmodal.tsx","./src/features/admin/adminpage.tsx","./src/features/annotations/annotationspage.tsx","./src/features/annotations/annotationssidebar.tsx","./src/features/annotations/canvaseditor.tsx","./src/features/annotations/medialist.tsx","./src/features/annotations/videoplayer.tsx","./src/features/dataset/datasetpage.tsx","./src/features/flights/altitudechart.tsx","./src/features/flights/altitudedialog.tsx","./src/features/flights/drawcontrol.tsx","./src/features/flights/flightlistsidebar.tsx","./src/features/flights/flightmap.tsx","./src/features/flights/flightparamspanel.tsx","./src/features/flights/flightspage.tsx","./src/features/flights/jsoneditordialog.tsx","./src/features/flights/mappoint.tsx","./src/features/flights/minimap.tsx","./src/features/flights/waypointlist.tsx","./src/features/flights/windeffect.tsx","./src/features/flights/flightplanutils.ts","./src/features/flights/mapicons.ts","./src/features/flights/types.ts","./src/features/login/loginpage.tsx","./src/features/settings/settingspage.tsx","./src/hooks/usedebounce.ts","./src/hooks/useresizablepanel.ts","./src/i18n/i18n.ts","./src/types/index.ts"],"version":"5.7.3"} {"root":["./src/app.tsx","./src/config.ts","./src/main.tsx","./src/vite-env.d.ts","./src/api/client.ts","./src/api/sse.ts","./src/auth/authcontext.tsx","./src/auth/protectedroute.tsx","./src/components/confirmdialog.tsx","./src/components/detectionclasses.tsx","./src/components/flightcontext.tsx","./src/components/header.tsx","./src/components/helpmodal.tsx","./src/features/admin/adminpage.tsx","./src/features/annotations/annotationspage.tsx","./src/features/annotations/annotationssidebar.tsx","./src/features/annotations/canvaseditor.tsx","./src/features/annotations/medialist.tsx","./src/features/annotations/videoplayer.tsx","./src/features/annotations/classcolors.ts","./src/features/dataset/datasetpage.tsx","./src/features/flights/altitudechart.tsx","./src/features/flights/altitudedialog.tsx","./src/features/flights/drawcontrol.tsx","./src/features/flights/flightlistsidebar.tsx","./src/features/flights/flightmap.tsx","./src/features/flights/flightparamspanel.tsx","./src/features/flights/flightspage.tsx","./src/features/flights/jsoneditordialog.tsx","./src/features/flights/mappoint.tsx","./src/features/flights/minimap.tsx","./src/features/flights/waypointlist.tsx","./src/features/flights/windeffect.tsx","./src/features/flights/flightplanutils.ts","./src/features/flights/mapicons.ts","./src/features/flights/types.ts","./src/features/login/loginpage.tsx","./src/features/settings/settingspage.tsx","./src/hooks/usedebounce.ts","./src/hooks/useresizablepanel.ts","./src/i18n/i18n.ts","./src/types/index.ts"],"version":"5.7.3"}
+2 -1
View File
@@ -13,8 +13,9 @@ export default defineConfig({
server: { server: {
proxy: { proxy: {
'/api': { '/api': {
target: 'http://localhost:8080', target: 'https://api.azaion.com',
changeOrigin: true, changeOrigin: true,
secure: true,
}, },
}, },
}, },