fix: conflate frontend_url and server_url

This commit is contained in:
tigattack
2025-09-21 23:08:45 +01:00
parent f23f075e41
commit c3365fedb2
4 changed files with 70 additions and 151 deletions

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "settings" DROP COLUMN "frontend_url";

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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 className="h-6 w-6 text-primary-600 mr-3" />
<h2 className="text-xl font-semibold text-secondary-900 dark:text-white">Server Configuration</h2>
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
@@ -581,7 +572,7 @@ const Settings = () => {
<div className="ml-3">
<h3 className="text-sm font-medium text-blue-800 dark:text-blue-200">Security Notice</h3>
<p className="mt-1 text-sm text-blue-700 dark:text-blue-300">
Changing these settings will affect all installation scripts and agent communications.
Changing these settings will affect all installation scripts and agent communications.
Make sure the server URL is accessible from your client networks.
</p>
</div>
@@ -627,74 +618,6 @@ const Settings = () => {
</form>
)}
{/* Frontend Configuration Tab */}
{activeTab === 'frontend' && (
<form onSubmit={handleSubmit} className="space-y-6">
<div className="flex items-center mb-6">
<Globe className="h-6 w-6 text-primary-600 mr-3" />
<h2 className="text-xl font-semibold text-secondary-900 dark:text-white">Frontend Configuration</h2>
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
Frontend URL *
</label>
<input
type="url"
value={formData.frontendUrl}
onChange={(e) => handleInputChange('frontendUrl', e.target.value)}
className={`w-full border rounded-md shadow-sm focus:ring-primary-500 focus:border-primary-500 bg-white dark:bg-secondary-700 text-secondary-900 dark:text-white ${
errors.frontendUrl ? 'border-red-300 dark:border-red-500' : 'border-secondary-300 dark:border-secondary-600'
}`}
placeholder="https://patchmon.example.com"
/>
{errors.frontendUrl && (
<p className="mt-1 text-sm text-red-600 dark:text-red-400">{errors.frontendUrl}</p>
)}
<p className="mt-1 text-sm text-secondary-500 dark:text-secondary-400">
The URL where users will access the PatchMon web interface.
</p>
</div>
{/* Save Button */}
<div className="flex justify-end">
<button
type="button"
onClick={handleSave}
disabled={!isDirty || updateSettingsMutation.isPending}
className={`inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white ${
!isDirty || updateSettingsMutation.isPending
? 'bg-secondary-400 cursor-not-allowed'
: 'bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500'
}`}
>
{updateSettingsMutation.isPending ? (
<>
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
Saving...
</>
) : (
<>
<Save className="h-4 w-4 mr-2" />
Save Settings
</>
)}
</button>
</div>
{updateSettingsMutation.isSuccess && (
<div className="bg-green-50 dark:bg-green-900 border border-green-200 dark:border-green-700 rounded-md p-4">
<div className="flex">
<CheckCircle className="h-5 w-5 text-green-400 dark:text-green-300" />
<div className="ml-3">
<p className="text-sm text-green-700 dark:text-green-300">Settings saved successfully!</p>
</div>
</div>
</div>
)}
</form>
)}
{/* Agent Management Tab */}
{activeTab === 'agent' && (
<div className="space-y-6">
@@ -826,7 +749,7 @@ const Settings = () => {
</div>
</div>
))}
{agentVersions?.length === 0 && (
<div className="text-center py-8">
<Code className="h-12 w-12 text-secondary-400 mx-auto mb-4" />
@@ -848,13 +771,13 @@ const Settings = () => {
<Code className="h-6 w-6 text-primary-600 mr-3" />
<h2 className="text-xl font-semibold text-secondary-900 dark:text-white">Server Version Management</h2>
</div>
<div className="bg-secondary-50 dark:bg-secondary-700 rounded-lg p-6">
<h3 className="text-lg font-medium text-secondary-900 dark:text-white mb-4">Version Check Configuration</h3>
<p className="text-sm text-secondary-600 dark:text-secondary-300 mb-6">
Configure automatic version checking against your GitHub repository to notify users of available updates.
</p>
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
@@ -894,7 +817,7 @@ const Settings = () => {
Choose whether your repository is public or private to determine the appropriate access method.
</p>
</div>
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
GitHub Repository URL
@@ -910,7 +833,7 @@ const Settings = () => {
SSH or HTTPS URL to your GitHub repository
</p>
</div>
{formData.repositoryType === 'private' && (
<div>
<div className="flex items-center gap-3 mb-3">
@@ -931,7 +854,7 @@ const Settings = () => {
Set custom SSH key path
</label>
</div>
{formData.useCustomSshKey && (
<div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
@@ -947,7 +870,7 @@ const Settings = () => {
<p className="mt-1 text-xs text-secondary-500 dark:text-secondary-400">
Path to your SSH deploy key. If not set, will auto-detect from common locations.
</p>
<div className="mt-3">
<button
type="button"
@@ -957,7 +880,7 @@ const Settings = () => {
>
{sshTestResult.testing ? 'Testing...' : 'Test SSH Key'}
</button>
{sshTestResult.success && (
<div className="mt-2 p-3 bg-green-50 dark:bg-green-900/20 border border-green-200 dark:border-green-800 rounded-md">
<div className="flex items-center">
@@ -968,7 +891,7 @@ const Settings = () => {
</div>
</div>
)}
{sshTestResult.error && (
<div className="mt-2 p-3 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md">
<div className="flex items-center">
@@ -982,7 +905,7 @@ const Settings = () => {
</div>
</div>
)}
{!formData.useCustomSshKey && (
<p className="text-xs text-secondary-500 dark:text-secondary-400">
Using auto-detection for SSH key location
@@ -990,7 +913,7 @@ const Settings = () => {
)}
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="bg-white dark:bg-secondary-800 rounded-lg p-4 border border-secondary-200 dark:border-secondary-600">
<div className="flex items-center gap-2 mb-2">
@@ -999,7 +922,7 @@ const Settings = () => {
</div>
<span className="text-lg font-mono text-secondary-900 dark:text-white">{versionInfo.currentVersion}</span>
</div>
<div className="bg-white dark:bg-secondary-800 rounded-lg p-4 border border-secondary-200 dark:border-secondary-600">
<div className="flex items-center gap-2 mb-2">
<Download className="h-4 w-4 text-blue-600 dark:text-blue-400" />
@@ -1019,7 +942,7 @@ const Settings = () => {
</span>
</div>
</div>
{/* Last Checked Time */}
{versionInfo.last_update_check && (
<div className="bg-white dark:bg-secondary-800 rounded-lg p-4 border border-secondary-200 dark:border-secondary-600">
@@ -1035,7 +958,7 @@ const Settings = () => {
</p>
</div>
)}
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<button
@@ -1047,7 +970,7 @@ const Settings = () => {
{versionInfo.checking ? 'Checking...' : 'Check for Updates'}
</button>
</div>
{/* Save Button for Version Settings */}
<button
type="button"
@@ -1072,7 +995,7 @@ const Settings = () => {
)}
</button>
</div>
{versionInfo.error && (
<div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-700 rounded-lg p-4">
<div className="flex">
@@ -1091,7 +1014,7 @@ const Settings = () => {
</div>
</div>
)}
{/* Success Message for Version Settings */}
{updateSettingsMutation.isSuccess && (
<div className="bg-green-50 dark:bg-green-900 border border-green-200 dark:border-green-700 rounded-md p-4">
@@ -1105,7 +1028,7 @@ const Settings = () => {
)}
</div>
</div>
</div>
)}
</div>
@@ -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 }) => {
</button>
</div>
</div>
<form onSubmit={handleSubmit} className="px-6 py-4">
<div className="space-y-4">
<div>
@@ -1253,7 +1176,7 @@ const AgentVersionModal = ({ isOpen, onClose, onSubmit, isLoading }) => {
</label>
</div>
</div>
<div className="flex justify-end gap-3 mt-6">
<button
type="button"