diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx
index d376925..eb00f29 100644
--- a/frontend/src/App.jsx
+++ b/frontend/src/App.jsx
@@ -20,9 +20,13 @@ const App = () => {
const [showAddMcp, setShowAddMcp] = useState(false);
const [mcpForm, setMcpForm] = useState({ name: '', server_type: 'sse', url: '', command: '' });
const [loading, setLoading] = useState(false);
- const [activeView, setActiveView] = useState('tasks'); // 'tasks' | 'dashboard'
+ const [activeView, setActiveView] = useState('tasks');
+
+ // Auth token state
+ const [authToken, setAuthToken] = useState('');
+ const [tokenType, setTokenType] = useState('oauth_token');
+ const [tokenSubmitting, setTokenSubmitting] = useState(false);
- // Fetch tasks on component mount
useEffect(() => {
fetchTasks();
fetchSystemInfo();
@@ -79,50 +83,32 @@ const App = () => {
}
};
- const [loginLoading, setLoginLoading] = useState(false);
- const [authCode, setAuthCode] = useState('');
- const [codeSubmitting, setCodeSubmitting] = useState(false);
-
- const handleLogin = async () => {
- setLoginLoading(true);
- setAuthCode('');
- try {
- const response = await fetch('/api/auth/login', { method: 'POST' });
- const data = await response.json();
- setAuthStatus(prev => ({ ...prev, ...data }));
- if (data.status === 'error') {
- alert(data.message || 'Login failed. Check container logs.');
- }
- // Don't try window.open — popup blockers kill it.
- // The auth_url will be shown as a clickable link in the pending state.
- } catch (error) {
- console.error('Error initiating login:', error);
- alert('Failed to connect to auth endpoint');
- }
- setLoginLoading(false);
- };
-
- const handleSubmitCode = async (e) => {
+ const handleSubmitToken = async (e) => {
e.preventDefault();
- if (!authCode.trim()) return;
- setCodeSubmitting(true);
+ if (!authToken.trim()) return;
+ setTokenSubmitting(true);
try {
- const response = await fetch('/api/auth/code', {
+ const response = await fetch('/api/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ code: authCode.trim() })
+ body: JSON.stringify({ token: authToken.trim(), token_type: tokenType })
});
const data = await response.json();
if (data.status === 'logged_in') {
- setAuthCode('');
+ setAuthToken('');
fetchAuthStatus();
+ } else if (data.status === 'token_saved') {
+ setAuthToken('');
+ fetchAuthStatus();
+ alert(data.message);
} else {
- alert(data.message || 'Code submission failed');
+ alert(data.message || 'Token submission failed');
}
} catch (error) {
- console.error('Error submitting auth code:', error);
+ console.error('Error submitting token:', error);
+ alert('Failed to submit token');
}
- setCodeSubmitting(false);
+ setTokenSubmitting(false);
};
const handleLogout = async () => {
@@ -178,40 +164,26 @@ const App = () => {
const handleCreateTask = async (e) => {
e.preventDefault();
setLoading(true);
-
try {
const response = await fetch('/api/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
-
if (response.ok) {
await fetchTasks();
setShowForm(false);
- setFormData({
- name: '',
- description: '',
- prompt: '',
- schedule_type: 'manual',
- schedule_value: '',
- enabled: true
- });
+ setFormData({ name: '', description: '', prompt: '', schedule_type: 'manual', schedule_value: '', enabled: true });
}
} catch (error) {
console.error('Error creating task:', error);
}
-
setLoading(false);
};
const handleRunTask = async (taskId) => {
try {
- const response = await fetch(`/api/tasks/${taskId}/run`, {
- method: 'POST'
- });
- const data = await response.json();
- console.log('Task started:', data);
+ await fetch(`/api/tasks/${taskId}/run`, { method: 'POST' });
await fetchTasks();
} catch (error) {
console.error('Error running task:', error);
@@ -253,24 +225,17 @@ const App = () => {
{authStatus?.status === 'logged_in' ? (
-
● {authStatus.account || 'Logged in'}
- ) : authStatus?.status === 'pending' ? (
-
+
+ ● {authStatus.account || `Authenticated (${authStatus.auth_method || 'token'})`}
+
+ ) : authStatus?.has_saved_token ? (
+
+ ● Token saved ({authStatus.token_type === 'api_key' ? 'API Key' : 'OAuth'})
+
) : (
-
+
setActiveView('dashboard')} style={{cursor:'pointer'}}>
+ ⚠ Not authenticated
+
)}
{systemInfo && (
@@ -289,40 +254,17 @@ const App = () => {
{systemInfo && (
<>
-
-
📋
-
{systemInfo.task_count}
-
Total Tasks
-
-
-
✅
-
{systemInfo.completed_runs || 0}
-
Completed Runs
-
-
-
❌
-
{systemInfo.failed_runs || 0}
-
Failed Runs
-
-
-
⚡
-
{systemInfo.running_runs || 0}
-
Currently Running
-
-
-
🔄
-
{systemInfo.total_runs || 0}
-
Total Runs Ever
-
-
-
{systemInfo.scheduler_running ? '🟢' : '🔴'}
-
{systemInfo.scheduler_running ? 'Active' : 'Stopped'}
-
Scheduler
-
+
📋
{systemInfo.task_count}
Total Tasks
+
✅
{systemInfo.completed_runs || 0}
Completed Runs
+
❌
{systemInfo.failed_runs || 0}
Failed Runs
+
⚡
{systemInfo.running_runs || 0}
Currently Running
+
🔄
{systemInfo.total_runs || 0}
Total Runs
+
{systemInfo.scheduler_running ? '🟢' : '🔴'}
{systemInfo.scheduler_running ? 'Active' : 'Stopped'}
Scheduler
>
)}
+ {/* Auth Section */}
Claude Authentication
@@ -330,46 +272,59 @@ const App = () => {
✅
-
Connected to Claude Max
-
{authStatus.account}
+
Connected to Claude
+
{authStatus.account || 'Authenticated'}{authStatus.auth_method && ` (${authStatus.auth_method})`}
- ) : authStatus?.status === 'pending' ? (
-
-
⏳
-
-
Waiting for authorization code…
-
1. Click the link below to authorize in your browser
-
2. After authorizing, you'll see a code — paste it below
- {authStatus.auth_url && (
-
- Open authorization page →
-
- )}
-
-
-
) : (
-
-
🔐
-
-
Not logged in
-
Log in with your Claude Max account to run tasks
+
+
+
🔐
+
+
{authStatus?.has_saved_token ? 'Token Saved — Update or Replace' : 'Authenticate with Token'}
+
Two options to authenticate:
+
-
+
+
+
+
Option 1: Setup Token (Claude Max/Pro)
+
Run this on TrueNAS to generate a long-lived token:
+
docker exec -it claude-persistent-agent claude setup-token
+
Then paste the token (starts with sk-ant-oat) below.
+
+
+
Option 2: API Key (Console billing)
+
+
+
+
+
+
+ {authStatus?.has_saved_token && (
+
+ Saved: {authStatus.token_type === 'api_key' ? 'API Key' : 'OAuth Token'}
+
+
+ )}
)}
+ {/* MCP Section */}
MCP Servers
@@ -390,24 +345,19 @@ const App = () => {
{mcpServers?.raw &&
{mcpServers.raw}}
)}
-
{showAddMcp ? (
+ {/* Usage Section */}
Claude API Usage
{usageStats ? (
<>
-
- Claude Runs (all time)
- {usageStats.claude_runs_total ?? '—'}
-
-
- Active Sessions
- {usageStats.session_count ?? '—'}
-
-
- First Run
- {usageStats.first_run ? new Date(usageStats.first_run).toLocaleString() : '—'}
-
-
- Last Run
- {usageStats.last_run ? new Date(usageStats.last_run).toLocaleString() : '—'}
-
-
- 🔄 Next Monthly Reset
- {usageStats.next_reset ? new Date(usageStats.next_reset).toLocaleDateString() : '—'}
-
-
- ⏳ Days Until Reset
- {usageStats.days_until_reset ?? '—'}
-
- {usageStats.note && (
-
{usageStats.note}
- )}
+
Claude Runs (all time){usageStats.claude_runs_total ?? '—'}
+
Active Sessions{usageStats.session_count ?? '—'}
+
First Run{usageStats.first_run ? new Date(usageStats.first_run).toLocaleString() : '—'}
+
Last Run{usageStats.last_run ? new Date(usageStats.last_run).toLocaleString() : '—'}
+
🔄 Next Monthly Reset{usageStats.next_reset ? new Date(usageStats.next_reset).toLocaleDateString() : '—'}
+
⏳ Days Until Reset{usageStats.days_until_reset ?? '—'}
>
) : (
-
Loading usage stats…
+
Loading usage stats...
)}
+ {/* Task Breakdown */}
Task Breakdown
@@ -481,41 +412,20 @@ const App = () => {
{activeView === 'tasks' && (<>
)}
- >)} {/* end activeView === 'tasks' */}
+ >)}
);
@@ -713,7 +526,6 @@ const TaskRuns = ({ taskId }) => {
console.error('Error fetching runs:', error);
}
};
-
fetchRuns();
const interval = setInterval(fetchRuns, 3000);
return () => clearInterval(interval);
@@ -730,22 +542,10 @@ const TaskRuns = ({ taskId }) => {
{run.status}
-
- {new Date(run.started_at).toLocaleString()}
-
+ {new Date(run.started_at).toLocaleString()}
- {run.output && (
-
- Output
- {run.output}
-
- )}
- {run.error && (
-
- Error
- {run.error}
-
- )}
+ {run.output &&
Output
{run.output}}
+ {run.error &&
Error
{run.error}}
))}