fix: add Bearer token to all frontend API requests

This commit is contained in:
Brooklyn
2026-01-09 16:16:42 -05:00
parent 5e9ba00b8c
commit f6d84ac78e
6 changed files with 71 additions and 30 deletions

View File

@@ -11,6 +11,7 @@ import {
} from "lucide-react";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { apiFetch } from "../lib/api";
function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@@ -165,9 +166,8 @@ export default function AppearanceSettings() {
// Since we don't have the full Helios API, we'll implement a simple one or just use local storage for now if backend isn't ready.
// But the plan says "Implement PUT /api/ui/preferences".
// We'll try to fetch it.
const response = await fetch("/api/ui/preferences", {
method: "POST", // Using POST for simplicity if PUT is tricky in backend routing without full REST
headers: { "Content-Type": "application/json" },
const response = await apiFetch("/api/ui/preferences", {
method: "POST",
body: JSON.stringify({ active_theme_id: themeId }),
});

View File

@@ -8,6 +8,7 @@ import {
Database,
Zap
} from "lucide-react";
import { apiFetch } from "../lib/api";
interface Stats {
total: number;
@@ -32,8 +33,8 @@ export default function Dashboard() {
const fetchData = async () => {
try {
const [statsRes, jobsRes] = await Promise.all([
fetch("/api/stats"),
fetch("/api/jobs/table")
apiFetch("/api/stats"),
apiFetch("/api/jobs/table")
]);
if (statsRes.ok) {
@@ -119,9 +120,9 @@ export default function Dashboard() {
<div key={job.id} className="flex items-center justify-between p-3 rounded-xl bg-helios-surface-soft hover:bg-white/5 transition-colors border border-transparent hover:border-helios-line/20">
<div className="flex items-center gap-3 min-w-0">
<div className={`w-2 h-2 rounded-full shrink-0 ${job.status === 'Completed' ? 'bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.4)]' :
job.status === 'Failed' ? 'bg-red-500' :
job.status === 'Encoding' ? 'bg-amber-500 animate-pulse' :
'bg-helios-slate'
job.status === 'Failed' ? 'bg-red-500' :
job.status === 'Encoding' ? 'bg-amber-500 animate-pulse' :
'bg-helios-slate'
}`} />
<div className="flex flex-col min-w-0">
<span className="text-sm font-medium text-helios-ink truncate" title={job.input_path}>

View File

@@ -10,6 +10,7 @@ import {
FileVideo,
Timer
} from "lucide-react";
import { apiFetch } from "../lib/api";
interface AggregatedStats {
total_input_bytes: number;
@@ -54,9 +55,9 @@ export default function StatsCharts() {
const fetchAllStats = async () => {
try {
const [aggRes, dailyRes, detailedRes] = await Promise.all([
fetch("/api/stats/aggregated"),
fetch("/api/stats/daily"),
fetch("/api/stats/detailed")
apiFetch("/api/stats/aggregated"),
apiFetch("/api/stats/daily"),
apiFetch("/api/stats/detailed")
]);
if (aggRes.ok) setStats(await aggRes.json());
@@ -305,7 +306,7 @@ export default function StatsCharts() {
</td>
<td className="py-3 px-2 text-right">
<span className={`font-bold text-xs ${job.vmaf_score && job.vmaf_score > 90 ? 'text-emerald-500' :
job.vmaf_score && job.vmaf_score > 80 ? 'text-amber-500' : 'text-helios-slate'
job.vmaf_score && job.vmaf_score > 80 ? 'text-amber-500' : 'text-helios-slate'
}`}>
{job.vmaf_score ? job.vmaf_score.toFixed(1) : 'N/A'}
</span>

View File

@@ -3,6 +3,7 @@ import { motion, AnimatePresence } from "framer-motion";
import { Activity, X, Zap, CheckCircle2, AlertTriangle, Database } from "lucide-react";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { apiFetch } from "../lib/api";
function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@@ -23,7 +24,7 @@ export default function SystemStatus() {
useEffect(() => {
const fetchStats = async () => {
try {
const res = await fetch("/api/stats");
const res = await apiFetch("/api/stats");
if (res.ok) {
const data = await res.json();
setStats({

View File

@@ -1,15 +1,16 @@
import { useState, useEffect } from "react";
import {
Cpu,
Save,
Video,
Gauge,
Zap,
Scale,
Film
import {
Cpu,
Save,
Video,
Gauge,
Zap,
Scale,
Film
} from "lucide-react";
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
import { apiFetch } from "../lib/api";
function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
@@ -37,7 +38,7 @@ export default function TranscodeSettings() {
const fetchSettings = async () => {
try {
const res = await fetch("/api/settings/transcode");
const res = await apiFetch("/api/settings/transcode");
if (!res.ok) throw new Error("Failed to load settings");
const data = await res.json();
setSettings(data);
@@ -56,9 +57,8 @@ export default function TranscodeSettings() {
setSuccess(false);
try {
const res = await fetch("/api/settings/transcode", {
const res = await apiFetch("/api/settings/transcode", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(settings),
});
if (!res.ok) throw new Error("Failed to save settings");
@@ -81,7 +81,7 @@ export default function TranscodeSettings() {
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between pb-2 border-b border-helios-line/10">
<div className="flex items-center justify-between pb-2 border-b border-helios-line/10">
<div>
<h3 className="text-base font-bold text-helios-ink tracking-tight uppercase tracking-[0.1em]">Transcoding Engine</h3>
<p className="text-xs text-helios-slate mt-0.5">Configure encoder behavior and performance limits.</p>
@@ -96,7 +96,7 @@ export default function TranscodeSettings() {
{error}
</div>
)}
{success && (
<div className="p-4 bg-green-500/10 border border-green-500/20 text-green-500 rounded-xl text-sm font-semibold">
Settings saved successfully.
@@ -165,7 +165,7 @@ export default function TranscodeSettings() {
<label className="text-xs font-bold uppercase tracking-wider text-helios-slate flex items-center gap-2">
<Zap size={14} /> Concurrent Jobs
</label>
<input
<input
type="number"
min="1"
max="8"
@@ -180,7 +180,7 @@ export default function TranscodeSettings() {
<label className="text-xs font-bold uppercase tracking-wider text-helios-slate flex items-center gap-2">
<Scale size={14} /> Min. Reduction (%)
</label>
<input
<input
type="number"
min="0"
max="100"
@@ -189,14 +189,14 @@ export default function TranscodeSettings() {
onChange={(e) => setSettings({ ...settings, size_reduction_threshold: (parseInt(e.target.value) || 0) / 100 })}
className="w-full bg-helios-surface border border-helios-line/30 rounded-xl px-4 py-3 text-helios-ink focus:border-helios-solar focus:ring-1 focus:ring-helios-solar outline-none transition-all"
/>
<p className="text-[10px] text-helios-slate ml-1">Files must shrink by at least this percentage or they are reverted.</p>
<p className="text-[10px] text-helios-slate ml-1">Files must shrink by at least this percentage or they are reverted.</p>
</div>
<div className="space-y-3">
<label className="text-xs font-bold uppercase tracking-wider text-helios-slate flex items-center gap-2">
<Film size={14} /> Min. File Size (MB)
</label>
<input
<input
type="number"
min="0"
value={settings.min_file_size_mb}

38
web/src/lib/api.ts Normal file
View File

@@ -0,0 +1,38 @@
/**
* Authenticated fetch utility - automatically adds Bearer token from localStorage
*/
export async function apiFetch(url: string, options: RequestInit = {}): Promise<Response> {
const token = localStorage.getItem('alchemist_token');
const headers = new Headers(options.headers);
if (token) {
headers.set('Authorization', `Bearer ${token}`);
}
if (!headers.has('Content-Type') && options.body) {
headers.set('Content-Type', 'application/json');
}
return fetch(url, {
...options,
headers
});
}
/**
* Helper for GET requests
*/
export async function apiGet(url: string): Promise<Response> {
return apiFetch(url);
}
/**
* Helper for POST requests with JSON body
*/
export async function apiPost(url: string, body?: unknown): Promise<Response> {
return apiFetch(url, {
method: 'POST',
body: body ? JSON.stringify(body) : undefined
});
}