mirror of
https://github.com/bybrooklyn/alchemist.git
synced 2026-04-18 09:53:33 -04:00
fix: add Bearer token to all frontend API requests
This commit is contained in:
@@ -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 }),
|
||||
});
|
||||
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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
38
web/src/lib/api.ts
Normal 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
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user