diff --git a/backend/prisma/migrations/20250921215150_remove_frontend_url/migration.sql b/backend/prisma/migrations/20250921215150_remove_frontend_url/migration.sql new file mode 100644 index 0000000..1efb445 --- /dev/null +++ b/backend/prisma/migrations/20250921215150_remove_frontend_url/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "settings" DROP COLUMN "frontend_url"; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index 87e455a..8881b63 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -157,7 +157,6 @@ model settings { server_protocol String @default("http") server_host String @default("localhost") server_port Int @default(3001) - frontend_url String @default("http://localhost:3000") created_at DateTime @default(now()) updated_at DateTime update_interval Int @default(60) diff --git a/backend/src/routes/settingsRoutes.js b/backend/src/routes/settingsRoutes.js index 08a4f3b..89674c5 100644 --- a/backend/src/routes/settingsRoutes.js +++ b/backend/src/routes/settingsRoutes.js @@ -12,10 +12,10 @@ const prisma = new PrismaClient(); async function triggerCrontabUpdates() { try { console.log('Triggering crontab updates on all hosts with auto-update enabled...'); - + // Get all hosts that have auto-update enabled const hosts = await prisma.hosts.findMany({ - where: { + where: { auto_update: true, status: 'active' // Only update active hosts }, @@ -26,15 +26,15 @@ async function triggerCrontabUpdates() { api_key: true } }); - + console.log(`Found ${hosts.length} hosts with auto-update enabled`); - + // For each host, we'll send a special update command that triggers crontab update // This is done by sending a ping with a special flag for (const host of hosts) { try { console.log(`Triggering crontab update for host: ${host.friendly_name}`); - + // We'll use the existing ping endpoint but add a special parameter // The agent will detect this and run update-crontab command const http = require('http'); @@ -47,12 +47,12 @@ async function triggerCrontabUpdates() { const url = new URL(`${serverUrl}/api/v1/hosts/ping`); const isHttps = url.protocol === 'https:'; const client = isHttps ? https : http; - + const postData = JSON.stringify({ triggerCrontabUpdate: true, message: 'Update interval changed, please update your crontab' }); - + const options = { hostname: url.hostname, port: url.port || (isHttps ? 443 : 80), @@ -65,7 +65,7 @@ async function triggerCrontabUpdates() { 'X-API-KEY': host.api_key } }; - + const req = client.request(options, (res) => { if (res.statusCode === 200) { console.log(`Successfully triggered crontab update for ${host.friendly_name}`); @@ -73,18 +73,18 @@ async function triggerCrontabUpdates() { console.error(`Failed to trigger crontab update for ${host.friendly_name}: ${res.statusCode}`); } }); - + req.on('error', (error) => { console.error(`Error triggering crontab update for ${host.friendly_name}:`, error.message); }); - + req.write(postData); req.end(); } catch (error) { console.error(`Error triggering crontab update for ${host.friendly_name}:`, error.message); } } - + console.log('Crontab update trigger completed'); } catch (error) { console.error('Error in triggerCrontabUpdates:', error); @@ -97,7 +97,7 @@ router.get('/', authenticateToken, requireManageSettings, async (req, res) => { let settings = await prisma.settings.findFirst({ orderBy: { updated_at: 'desc' } }); - + // If no settings exist, create default settings if (!settings) { settings = await prisma.settings.create({ @@ -107,7 +107,6 @@ router.get('/', authenticateToken, requireManageSettings, async (req, res) => { server_protocol: 'http', server_host: 'localhost', server_port: 3001, - frontend_url: 'http://localhost:3000', update_interval: 60, auto_update: false, signup_enabled: false, @@ -128,7 +127,6 @@ router.put('/', authenticateToken, requireManageSettings, [ body('serverProtocol').isIn(['http', 'https']).withMessage('Protocol must be http or https'), body('serverHost').isLength({ min: 1 }).withMessage('Server host is required'), body('serverPort').isInt({ min: 1, max: 65535 }).withMessage('Port must be between 1 and 65535'), - body('frontendUrl').isLength({ min: 1 }).withMessage('Frontend URL is required'), body('updateInterval').isInt({ min: 5, max: 1440 }).withMessage('Update interval must be between 5 and 1440 minutes'), body('autoUpdate').isBoolean().withMessage('Auto update must be a boolean'), body('signupEnabled').isBoolean().withMessage('Signup enabled must be a boolean'), @@ -151,19 +149,19 @@ router.put('/', authenticateToken, requireManageSettings, [ return res.status(400).json({ errors: errors.array() }); } - const { serverProtocol, serverHost, serverPort, frontendUrl, updateInterval, autoUpdate, signupEnabled, githubRepoUrl, repositoryType, sshKeyPath } = req.body; - + const { serverProtocol, serverHost, serverPort, updateInterval, autoUpdate, signupEnabled, githubRepoUrl, repositoryType, sshKeyPath } = req.body; + // Construct server URL from components const serverUrl = `${serverProtocol}://${serverHost}:${serverPort}`; - + let settings = await prisma.settings.findFirst({ orderBy: { updated_at: 'desc' } }); - + if (settings) { // Update existing settings const oldUpdateInterval = settings.update_interval; - + settings = await prisma.settings.update({ where: { id: settings.id }, data: { @@ -171,7 +169,6 @@ router.put('/', authenticateToken, requireManageSettings, [ server_protocol: serverProtocol, server_host: serverHost, server_port: serverPort, - frontend_url: frontendUrl, update_interval: updateInterval || 60, auto_update: autoUpdate || false, signup_enabled: signupEnabled || false, @@ -181,7 +178,6 @@ router.put('/', authenticateToken, requireManageSettings, [ updated_at: new Date() } }); - // If update interval changed, trigger crontab updates on all hosts with auto-update enabled if (oldUpdateInterval !== (updateInterval || 60)) { console.log(`Update interval changed from ${oldUpdateInterval} to ${updateInterval || 60} minutes. Triggering crontab updates...`); @@ -196,7 +192,6 @@ router.put('/', authenticateToken, requireManageSettings, [ server_protocol: serverProtocol, server_host: serverHost, server_port: serverPort, - frontend_url: frontendUrl, update_interval: updateInterval || 60, auto_update: autoUpdate || false, signup_enabled: signupEnabled || false, @@ -207,7 +202,7 @@ router.put('/', authenticateToken, requireManageSettings, [ } }); } - + res.json({ message: 'Settings updated successfully', settings @@ -224,11 +219,11 @@ router.get('/server-url', async (req, res) => { const settings = await prisma.settings.findFirst({ orderBy: { updated_at: 'desc' } }); - + if (!settings) { return res.json({ server_url: 'http://localhost:3001' }); } - + res.json({ server_url: settings.server_url }); } catch (error) { console.error('Server URL fetch error:', error); @@ -242,12 +237,12 @@ router.get('/update-interval', async (req, res) => { const settings = await prisma.settings.findFirst({ orderBy: { updated_at: 'desc' } }); - + if (!settings) { return res.json({ updateInterval: 60 }); } - - res.json({ + + res.json({ updateInterval: settings.update_interval, cronExpression: `*/${settings.update_interval} * * * *` // Generate cron expression }); @@ -263,12 +258,12 @@ router.get('/auto-update', async (req, res) => { const settings = await prisma.settings.findFirst({ orderBy: { updated_at: 'desc' } }); - + if (!settings) { return res.json({ autoUpdate: false }); } - - res.json({ + + res.json({ autoUpdate: settings.auto_update || false }); } catch (error) { diff --git a/frontend/src/pages/Settings.jsx b/frontend/src/pages/Settings.jsx index 561a403..c961ead 100644 --- a/frontend/src/pages/Settings.jsx +++ b/frontend/src/pages/Settings.jsx @@ -1,6 +1,6 @@ import React, { useState, useEffect } from 'react'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; -import { Save, Server, Globe, Shield, AlertCircle, CheckCircle, Code, Plus, Trash2, Star, Download, X, Settings as SettingsIcon, Clock } from 'lucide-react'; +import { Save, Server, Shield, AlertCircle, CheckCircle, Code, Plus, Trash2, Star, Download, X, Settings as SettingsIcon, Clock } from 'lucide-react'; import { settingsAPI, agentVersionAPI, versionAPI } from '../utils/api'; import { useUpdateNotification } from '../contexts/UpdateNotificationContext'; import UpgradeNotificationIcon from '../components/UpgradeNotificationIcon'; @@ -10,7 +10,6 @@ const Settings = () => { serverProtocol: 'http', serverHost: 'localhost', serverPort: 3001, - frontendUrl: 'http://localhost:3000', updateInterval: 60, autoUpdate: false, signupEnabled: false, @@ -21,21 +20,20 @@ const Settings = () => { }); const [errors, setErrors] = useState({}); const [isDirty, setIsDirty] = useState(false); - + // Tab management const [activeTab, setActiveTab] = useState('server'); - + // Get update notification state const { updateAvailable } = useUpdateNotification(); - + // Tab configuration const tabs = [ { id: 'server', name: 'Server Configuration', icon: Server }, - { id: 'frontend', name: 'Frontend Configuration', icon: Globe }, { id: 'agent', name: 'Agent Management', icon: SettingsIcon }, { id: 'version', name: 'Server Version', icon: Code, showUpgradeIcon: updateAvailable } ]; - + // Agent version management state const [showAgentVersionModal, setShowAgentVersionModal] = useState(false); const [editingAgentVersion, setEditingAgentVersion] = useState(null); @@ -54,7 +52,7 @@ const Settings = () => { checking: false, error: null }); - + const [sshTestResult, setSshTestResult] = useState({ testing: false, success: null, @@ -77,7 +75,6 @@ const Settings = () => { serverProtocol: settings.server_protocol || 'http', serverHost: settings.server_host || 'localhost', serverPort: settings.server_port || 3001, - frontendUrl: settings.frontend_url || 'http://localhost:3000', updateInterval: settings.update_interval || 60, autoUpdate: settings.auto_update || false, signupEnabled: settings.signup_enabled === true ? true : false, // Explicit boolean conversion @@ -189,11 +186,11 @@ const Settings = () => { // Version checking functions const checkForUpdates = async () => { setVersionInfo(prev => ({ ...prev, checking: true, error: null })); - + try { const response = await versionAPI.checkUpdates(); const data = response.data; - + setVersionInfo({ currentVersion: data.currentVersion, latestVersion: data.latestVersion, @@ -224,13 +221,13 @@ const Settings = () => { } setSshTestResult({ testing: true, success: null, message: null, error: null }); - + try { const response = await versionAPI.testSshKey({ sshKeyPath: formData.sshKeyPath, githubRepoUrl: formData.githubRepoUrl }); - + setSshTestResult({ testing: false, success: true, @@ -261,7 +258,7 @@ const Settings = () => { const handleSubmit = (e) => { e.preventDefault(); - + // Only include sshKeyPath if the toggle is enabled const dataToSubmit = { ...formData }; if (!dataToSubmit.useCustomSshKey) { @@ -269,31 +266,25 @@ const Settings = () => { } // Remove the frontend-only field delete dataToSubmit.useCustomSshKey; - + updateSettingsMutation.mutate(dataToSubmit); }; const validateForm = () => { const newErrors = {}; - + if (!formData.serverHost.trim()) { newErrors.serverHost = 'Server host is required'; } - + if (!formData.serverPort || formData.serverPort < 1 || formData.serverPort > 65535) { newErrors.serverPort = 'Port must be between 1 and 65535'; } - - try { - new URL(formData.frontendUrl); - } catch { - newErrors.frontendUrl = 'Frontend URL must be a valid URL'; - } - + if (!formData.updateInterval || formData.updateInterval < 5 || formData.updateInterval > 1440) { newErrors.updateInterval = 'Update interval must be between 5 and 1440 minutes'; } - + setErrors(newErrors); return Object.keys(newErrors).length === 0; }; @@ -307,7 +298,7 @@ const Settings = () => { } // Remove the frontend-only field delete dataToSubmit.useCustomSshKey; - + updateSettingsMutation.mutate(dataToSubmit); } }; @@ -391,7 +382,7 @@ const Settings = () => {

Server Configuration

- +
))} - + {agentVersions?.length === 0 && (
@@ -848,13 +771,13 @@ const Settings = () => {

Server Version Management

- +

Version Check Configuration

Configure automatic version checking against your GitHub repository to notify users of available updates.

- +
- +
- + {formData.repositoryType === 'private' && (
@@ -931,7 +854,7 @@ const Settings = () => { Set custom SSH key path
- + {formData.useCustomSshKey && (
)}
@@ -1139,17 +1062,17 @@ const AgentVersionModal = ({ isOpen, onClose, onSubmit, isLoading }) => { const handleSubmit = (e) => { e.preventDefault(); - + // Basic validation const newErrors = {}; if (!formData.version.trim()) newErrors.version = 'Version is required'; if (!formData.scriptContent.trim()) newErrors.scriptContent = 'Script content is required'; - + if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } - + onSubmit(formData); }; @@ -1180,7 +1103,7 @@ const AgentVersionModal = ({ isOpen, onClose, onSubmit, isLoading }) => {
- +
@@ -1253,7 +1176,7 @@ const AgentVersionModal = ({ isOpen, onClose, onSubmit, isLoading }) => {
- +