auth
This commit is contained in:
parent
937827d08b
commit
643203b8a5
9 changed files with 822 additions and 44 deletions
|
|
@ -2,6 +2,13 @@ import { marked } from 'marked';
|
|||
import DOMPurify from 'dompurify';
|
||||
|
||||
const API_URL = 'http://localhost:3000';
|
||||
// These should ideally be environment-specific
|
||||
const AUTH_CONFIG = {
|
||||
issuer: 'https://idm.flegr.me/application/o/bot/',
|
||||
clientId: 'CicDk8mpSBY1SW4ofCamO3B583ttmvKTSPDQwvpb',
|
||||
redirectUri: window.location.origin + '/callback',
|
||||
authorizeEndpoint: 'https://idm.flegr.me/application/o/authorize/',
|
||||
};
|
||||
|
||||
const state = {
|
||||
tasks: [],
|
||||
|
|
@ -9,9 +16,15 @@ const state = {
|
|||
selectedRunId: null,
|
||||
currentView: 'dashboard', // 'dashboard' or 'task'
|
||||
isEditing: false,
|
||||
token: localStorage.getItem('auth_token')
|
||||
};
|
||||
|
||||
// DOM elements
|
||||
const loginOverlay = document.getElementById('login-overlay');
|
||||
const callbackOverlay = document.getElementById('callback-overlay');
|
||||
const loginBtn = document.getElementById('login-btn');
|
||||
const appEl = document.getElementById('app');
|
||||
const logoutBtn = document.getElementById('logout-btn');
|
||||
const taskListEl = document.getElementById('task-list');
|
||||
const newTaskBtn = document.getElementById('new-task-btn');
|
||||
const modalContainer = document.getElementById('modal-container');
|
||||
|
|
@ -42,9 +55,31 @@ const toggleCustomCronBtn = document.getElementById('toggle-custom-cron');
|
|||
const customCronContainer = document.getElementById('custom-cron-container');
|
||||
const presetBtns = document.querySelectorAll('.btn-preset');
|
||||
|
||||
// Wrapper for fetch to include Authorization header
|
||||
async function fetchWithAuth(url, options = {}) {
|
||||
if (!state.token) {
|
||||
showLogin();
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
const headers = {
|
||||
...options.headers,
|
||||
'Authorization': `Bearer ${state.token}`
|
||||
};
|
||||
|
||||
const response = await fetch(url, { ...options, headers });
|
||||
|
||||
if (response.status === 401) {
|
||||
logout();
|
||||
throw new Error('Session expired');
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
async function fetchTasks() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/tasks`);
|
||||
const response = await fetchWithAuth(`${API_URL}/tasks`);
|
||||
const newTasks = await response.json();
|
||||
|
||||
// Check if we should follow the latest run (if we were already watching it)
|
||||
|
|
@ -88,7 +123,7 @@ async function fetchTasks() {
|
|||
|
||||
async function fetchRecentRuns() {
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/runs/recent`);
|
||||
const response = await fetchWithAuth(`${API_URL}/runs/recent`);
|
||||
const recentRuns = await response.json();
|
||||
renderDashboard(recentRuns);
|
||||
} catch (error) {
|
||||
|
|
@ -230,8 +265,11 @@ function showTaskView(task) {
|
|||
|
||||
if (!run) {
|
||||
viewStatusEl.textContent = 'No runs';
|
||||
logsOutputEl.innerHTML = '';
|
||||
answerContainerEl.classList.add('hidden');
|
||||
viewStatusEl.className = 'status-badge pending';
|
||||
viewDateEl.textContent = '-';
|
||||
logsOutputEl.innerHTML = '<div class="waiting-placeholder">No logs available. Click "Run Task" to start the agent.</div>';
|
||||
answerContainerEl.classList.remove('hidden');
|
||||
answerOutputEl.innerHTML = '<div class="waiting-placeholder">Waiting for the first execution...</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -245,12 +283,12 @@ function showTaskView(task) {
|
|||
minute: '2-digit'
|
||||
});
|
||||
|
||||
answerContainerEl.classList.remove('hidden');
|
||||
if (run.answer) {
|
||||
answerContainerEl.classList.remove('hidden');
|
||||
const rawHtml = marked.parse(run.answer);
|
||||
answerOutputEl.innerHTML = DOMPurify.sanitize(rawHtml);
|
||||
} else {
|
||||
answerContainerEl.classList.add('hidden');
|
||||
answerOutputEl.innerHTML = '<div class="waiting-placeholder">Agent is working on the final answer...</div>';
|
||||
}
|
||||
|
||||
// Check if we should auto-scroll
|
||||
|
|
@ -279,7 +317,7 @@ rerunBtn.addEventListener('click', async () => {
|
|||
if (!state.selectedTaskId) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/tasks/${state.selectedTaskId}/runs`, {
|
||||
const response = await fetchWithAuth(`${API_URL}/tasks/${state.selectedTaskId}/runs`, {
|
||||
method: 'POST',
|
||||
});
|
||||
const updatedTask = await response.json();
|
||||
|
|
@ -289,15 +327,15 @@ rerunBtn.addEventListener('click', async () => {
|
|||
}
|
||||
selectTask(updatedTask.id);
|
||||
} catch (error) {
|
||||
console.error('Error re-running task:', error);
|
||||
alert('Failed to re-run task.');
|
||||
console.error('Error running task:', error);
|
||||
alert('Failed to run task.');
|
||||
}
|
||||
});
|
||||
|
||||
newTaskBtn.addEventListener('click', () => {
|
||||
state.isEditing = false;
|
||||
modalTitle.textContent = 'New Agent Task';
|
||||
submitTaskBtn.textContent = 'Execute Directive';
|
||||
submitTaskBtn.textContent = 'Save Task';
|
||||
goalTextarea.value = '';
|
||||
cronInput.value = '';
|
||||
updateScheduleUI('');
|
||||
|
|
@ -316,8 +354,8 @@ editTaskBtn.addEventListener('click', () => {
|
|||
if (!task) return;
|
||||
|
||||
state.isEditing = true;
|
||||
modalTitle.textContent = 'Edit Directive';
|
||||
submitTaskBtn.textContent = 'Update Directive';
|
||||
modalTitle.textContent = 'Edit Agent Task';
|
||||
submitTaskBtn.textContent = 'Save Task';
|
||||
goalTextarea.value = task.goal;
|
||||
cronInput.value = task.cron || '';
|
||||
updateScheduleUI(task.cron || '');
|
||||
|
|
@ -383,13 +421,13 @@ newTaskForm.addEventListener('submit', async (e) => {
|
|||
try {
|
||||
let response;
|
||||
if (state.isEditing) {
|
||||
response = await fetch(`${API_URL}/tasks/${state.selectedTaskId}`, {
|
||||
response = await fetchWithAuth(`${API_URL}/tasks/${state.selectedTaskId}`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ goal, cron }),
|
||||
});
|
||||
} else {
|
||||
response = await fetch(`${API_URL}/tasks`, {
|
||||
response = await fetchWithAuth(`${API_URL}/tasks`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ goal, cron }),
|
||||
|
|
@ -415,11 +453,6 @@ newTaskForm.addEventListener('submit', async (e) => {
|
|||
}
|
||||
});
|
||||
|
||||
// Initial load
|
||||
// Initial fetch
|
||||
fetchTasks();
|
||||
|
||||
// Auto-refresh every 3 seconds
|
||||
let isPolling = false;
|
||||
async function startAutoRefresh() {
|
||||
setInterval(async () => {
|
||||
|
|
@ -433,4 +466,71 @@ async function startAutoRefresh() {
|
|||
}, 3000);
|
||||
}
|
||||
|
||||
startAutoRefresh();
|
||||
async function showLogin() {
|
||||
loginOverlay.classList.remove('hidden');
|
||||
appEl.classList.add('hidden');
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
state.token = null;
|
||||
localStorage.removeItem('auth_token');
|
||||
showLogin();
|
||||
}
|
||||
|
||||
async function handleCallback() {
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const code = params.get('code');
|
||||
if (!code) return;
|
||||
|
||||
window.history.replaceState({}, document.title, "/");
|
||||
callbackOverlay.classList.remove('hidden');
|
||||
loginOverlay.classList.add('hidden');
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/auth/callback?code=${code}&redirect_uri=${encodeURIComponent(AUTH_CONFIG.redirectUri)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.access_token) {
|
||||
state.token = data.access_token;
|
||||
localStorage.setItem('auth_token', data.access_token);
|
||||
callbackOverlay.classList.add('hidden');
|
||||
appEl.classList.remove('hidden');
|
||||
initializeApp();
|
||||
} else {
|
||||
throw new Error('No access token in response');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Auth callback failed:', error);
|
||||
alert('Authentication failed.');
|
||||
showLogin();
|
||||
}
|
||||
}
|
||||
|
||||
loginBtn.addEventListener('click', () => {
|
||||
const authUrl = `${AUTH_CONFIG.authorizeEndpoint}?client_id=${AUTH_CONFIG.clientId}&response_type=code&redirect_uri=${encodeURIComponent(AUTH_CONFIG.redirectUri)}&scope=openid profile email`;
|
||||
window.location.href = authUrl;
|
||||
});
|
||||
|
||||
logoutBtn.addEventListener('click', () => {
|
||||
logout();
|
||||
});
|
||||
|
||||
async function initializeApp() {
|
||||
if (!state.token) {
|
||||
showLogin();
|
||||
return;
|
||||
}
|
||||
|
||||
appEl.classList.remove('hidden');
|
||||
loginOverlay.classList.add('hidden');
|
||||
|
||||
await fetchTasks();
|
||||
startAutoRefresh();
|
||||
}
|
||||
|
||||
// Check for callback on load
|
||||
if (window.location.pathname === '/callback' || window.location.search.includes('code=')) {
|
||||
handleCallback();
|
||||
} else {
|
||||
initializeApp();
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue