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_protocol String @default("http")
server_host String @default("localhost") server_host String @default("localhost")
server_port Int @default(3001) server_port Int @default(3001)
frontend_url String @default("http://localhost:3000")
created_at DateTime @default(now()) created_at DateTime @default(now())
updated_at DateTime updated_at DateTime
update_interval Int @default(60) update_interval Int @default(60)

View File

@@ -12,10 +12,10 @@ const prisma = new PrismaClient();
async function triggerCrontabUpdates() { async function triggerCrontabUpdates() {
try { try {
console.log('Triggering crontab updates on all hosts with auto-update enabled...'); console.log('Triggering crontab updates on all hosts with auto-update enabled...');
// Get all hosts that have auto-update enabled // Get all hosts that have auto-update enabled
const hosts = await prisma.hosts.findMany({ const hosts = await prisma.hosts.findMany({
where: { where: {
auto_update: true, auto_update: true,
status: 'active' // Only update active hosts status: 'active' // Only update active hosts
}, },
@@ -26,15 +26,15 @@ async function triggerCrontabUpdates() {
api_key: true api_key: true
} }
}); });
console.log(`Found ${hosts.length} hosts with auto-update enabled`); console.log(`Found ${hosts.length} hosts with auto-update enabled`);
// For each host, we'll send a special update command that triggers crontab update // 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 // This is done by sending a ping with a special flag
for (const host of hosts) { for (const host of hosts) {
try { try {
console.log(`Triggering crontab update for host: ${host.friendly_name}`); console.log(`Triggering crontab update for host: ${host.friendly_name}`);
// We'll use the existing ping endpoint but add a special parameter // We'll use the existing ping endpoint but add a special parameter
// The agent will detect this and run update-crontab command // The agent will detect this and run update-crontab command
const http = require('http'); const http = require('http');
@@ -47,12 +47,12 @@ async function triggerCrontabUpdates() {
const url = new URL(`${serverUrl}/api/v1/hosts/ping`); const url = new URL(`${serverUrl}/api/v1/hosts/ping`);
const isHttps = url.protocol === 'https:'; const isHttps = url.protocol === 'https:';
const client = isHttps ? https : http; const client = isHttps ? https : http;
const postData = JSON.stringify({ const postData = JSON.stringify({
triggerCrontabUpdate: true, triggerCrontabUpdate: true,
message: 'Update interval changed, please update your crontab' message: 'Update interval changed, please update your crontab'
}); });
const options = { const options = {
hostname: url.hostname, hostname: url.hostname,
port: url.port || (isHttps ? 443 : 80), port: url.port || (isHttps ? 443 : 80),
@@ -65,7 +65,7 @@ async function triggerCrontabUpdates() {
'X-API-KEY': host.api_key 'X-API-KEY': host.api_key
} }
}; };
const req = client.request(options, (res) => { const req = client.request(options, (res) => {
if (res.statusCode === 200) { if (res.statusCode === 200) {
console.log(`Successfully triggered crontab update for ${host.friendly_name}`); 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}`); console.error(`Failed to trigger crontab update for ${host.friendly_name}: ${res.statusCode}`);
} }
}); });
req.on('error', (error) => { req.on('error', (error) => {
console.error(`Error triggering crontab update for ${host.friendly_name}:`, error.message); console.error(`Error triggering crontab update for ${host.friendly_name}:`, error.message);
}); });
req.write(postData); req.write(postData);
req.end(); req.end();
} catch (error) { } catch (error) {
console.error(`Error triggering crontab update for ${host.friendly_name}:`, error.message); console.error(`Error triggering crontab update for ${host.friendly_name}:`, error.message);
} }
} }
console.log('Crontab update trigger completed'); console.log('Crontab update trigger completed');
} catch (error) { } catch (error) {
console.error('Error in triggerCrontabUpdates:', error); console.error('Error in triggerCrontabUpdates:', error);
@@ -97,7 +97,7 @@ router.get('/', authenticateToken, requireManageSettings, async (req, res) => {
let settings = await prisma.settings.findFirst({ let settings = await prisma.settings.findFirst({
orderBy: { updated_at: 'desc' } orderBy: { updated_at: 'desc' }
}); });
// If no settings exist, create default settings // If no settings exist, create default settings
if (!settings) { if (!settings) {
settings = await prisma.settings.create({ settings = await prisma.settings.create({
@@ -107,7 +107,6 @@ router.get('/', authenticateToken, requireManageSettings, async (req, res) => {
server_protocol: 'http', server_protocol: 'http',
server_host: 'localhost', server_host: 'localhost',
server_port: 3001, server_port: 3001,
frontend_url: 'http://localhost:3000',
update_interval: 60, update_interval: 60,
auto_update: false, auto_update: false,
signup_enabled: 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('serverProtocol').isIn(['http', 'https']).withMessage('Protocol must be http or https'),
body('serverHost').isLength({ min: 1 }).withMessage('Server host is required'), 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('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('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('autoUpdate').isBoolean().withMessage('Auto update must be a boolean'),
body('signupEnabled').isBoolean().withMessage('Signup enabled 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() }); 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 // Construct server URL from components
const serverUrl = `${serverProtocol}://${serverHost}:${serverPort}`; const serverUrl = `${serverProtocol}://${serverHost}:${serverPort}`;
let settings = await prisma.settings.findFirst({ let settings = await prisma.settings.findFirst({
orderBy: { updated_at: 'desc' } orderBy: { updated_at: 'desc' }
}); });
if (settings) { if (settings) {
// Update existing settings // Update existing settings
const oldUpdateInterval = settings.update_interval; const oldUpdateInterval = settings.update_interval;
settings = await prisma.settings.update({ settings = await prisma.settings.update({
where: { id: settings.id }, where: { id: settings.id },
data: { data: {
@@ -171,7 +169,6 @@ router.put('/', authenticateToken, requireManageSettings, [
server_protocol: serverProtocol, server_protocol: serverProtocol,
server_host: serverHost, server_host: serverHost,
server_port: serverPort, server_port: serverPort,
frontend_url: frontendUrl,
update_interval: updateInterval || 60, update_interval: updateInterval || 60,
auto_update: autoUpdate || false, auto_update: autoUpdate || false,
signup_enabled: signupEnabled || false, signup_enabled: signupEnabled || false,
@@ -181,7 +178,6 @@ router.put('/', authenticateToken, requireManageSettings, [
updated_at: new Date() updated_at: new Date()
} }
}); });
// If update interval changed, trigger crontab updates on all hosts with auto-update enabled // If update interval changed, trigger crontab updates on all hosts with auto-update enabled
if (oldUpdateInterval !== (updateInterval || 60)) { if (oldUpdateInterval !== (updateInterval || 60)) {
console.log(`Update interval changed from ${oldUpdateInterval} to ${updateInterval || 60} minutes. Triggering crontab updates...`); 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_protocol: serverProtocol,
server_host: serverHost, server_host: serverHost,
server_port: serverPort, server_port: serverPort,
frontend_url: frontendUrl,
update_interval: updateInterval || 60, update_interval: updateInterval || 60,
auto_update: autoUpdate || false, auto_update: autoUpdate || false,
signup_enabled: signupEnabled || false, signup_enabled: signupEnabled || false,
@@ -207,7 +202,7 @@ router.put('/', authenticateToken, requireManageSettings, [
} }
}); });
} }
res.json({ res.json({
message: 'Settings updated successfully', message: 'Settings updated successfully',
settings settings
@@ -224,11 +219,11 @@ router.get('/server-url', async (req, res) => {
const settings = await prisma.settings.findFirst({ const settings = await prisma.settings.findFirst({
orderBy: { updated_at: 'desc' } orderBy: { updated_at: 'desc' }
}); });
if (!settings) { if (!settings) {
return res.json({ server_url: 'http://localhost:3001' }); return res.json({ server_url: 'http://localhost:3001' });
} }
res.json({ server_url: settings.server_url }); res.json({ server_url: settings.server_url });
} catch (error) { } catch (error) {
console.error('Server URL fetch error:', 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({ const settings = await prisma.settings.findFirst({
orderBy: { updated_at: 'desc' } orderBy: { updated_at: 'desc' }
}); });
if (!settings) { if (!settings) {
return res.json({ updateInterval: 60 }); return res.json({ updateInterval: 60 });
} }
res.json({ res.json({
updateInterval: settings.update_interval, updateInterval: settings.update_interval,
cronExpression: `*/${settings.update_interval} * * * *` // Generate cron expression cronExpression: `*/${settings.update_interval} * * * *` // Generate cron expression
}); });
@@ -263,12 +258,12 @@ router.get('/auto-update', async (req, res) => {
const settings = await prisma.settings.findFirst({ const settings = await prisma.settings.findFirst({
orderBy: { updated_at: 'desc' } orderBy: { updated_at: 'desc' }
}); });
if (!settings) { if (!settings) {
return res.json({ autoUpdate: false }); return res.json({ autoUpdate: false });
} }
res.json({ res.json({
autoUpdate: settings.auto_update || false autoUpdate: settings.auto_update || false
}); });
} catch (error) { } catch (error) {

View File

@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; 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 { settingsAPI, agentVersionAPI, versionAPI } from '../utils/api';
import { useUpdateNotification } from '../contexts/UpdateNotificationContext'; import { useUpdateNotification } from '../contexts/UpdateNotificationContext';
import UpgradeNotificationIcon from '../components/UpgradeNotificationIcon'; import UpgradeNotificationIcon from '../components/UpgradeNotificationIcon';
@@ -10,7 +10,6 @@ const Settings = () => {
serverProtocol: 'http', serverProtocol: 'http',
serverHost: 'localhost', serverHost: 'localhost',
serverPort: 3001, serverPort: 3001,
frontendUrl: 'http://localhost:3000',
updateInterval: 60, updateInterval: 60,
autoUpdate: false, autoUpdate: false,
signupEnabled: false, signupEnabled: false,
@@ -21,21 +20,20 @@ const Settings = () => {
}); });
const [errors, setErrors] = useState({}); const [errors, setErrors] = useState({});
const [isDirty, setIsDirty] = useState(false); const [isDirty, setIsDirty] = useState(false);
// Tab management // Tab management
const [activeTab, setActiveTab] = useState('server'); const [activeTab, setActiveTab] = useState('server');
// Get update notification state // Get update notification state
const { updateAvailable } = useUpdateNotification(); const { updateAvailable } = useUpdateNotification();
// Tab configuration // Tab configuration
const tabs = [ const tabs = [
{ id: 'server', name: 'Server Configuration', icon: Server }, { id: 'server', name: 'Server Configuration', icon: Server },
{ id: 'frontend', name: 'Frontend Configuration', icon: Globe },
{ id: 'agent', name: 'Agent Management', icon: SettingsIcon }, { id: 'agent', name: 'Agent Management', icon: SettingsIcon },
{ id: 'version', name: 'Server Version', icon: Code, showUpgradeIcon: updateAvailable } { id: 'version', name: 'Server Version', icon: Code, showUpgradeIcon: updateAvailable }
]; ];
// Agent version management state // Agent version management state
const [showAgentVersionModal, setShowAgentVersionModal] = useState(false); const [showAgentVersionModal, setShowAgentVersionModal] = useState(false);
const [editingAgentVersion, setEditingAgentVersion] = useState(null); const [editingAgentVersion, setEditingAgentVersion] = useState(null);
@@ -54,7 +52,7 @@ const Settings = () => {
checking: false, checking: false,
error: null error: null
}); });
const [sshTestResult, setSshTestResult] = useState({ const [sshTestResult, setSshTestResult] = useState({
testing: false, testing: false,
success: null, success: null,
@@ -77,7 +75,6 @@ const Settings = () => {
serverProtocol: settings.server_protocol || 'http', serverProtocol: settings.server_protocol || 'http',
serverHost: settings.server_host || 'localhost', serverHost: settings.server_host || 'localhost',
serverPort: settings.server_port || 3001, serverPort: settings.server_port || 3001,
frontendUrl: settings.frontend_url || 'http://localhost:3000',
updateInterval: settings.update_interval || 60, updateInterval: settings.update_interval || 60,
autoUpdate: settings.auto_update || false, autoUpdate: settings.auto_update || false,
signupEnabled: settings.signup_enabled === true ? true : false, // Explicit boolean conversion signupEnabled: settings.signup_enabled === true ? true : false, // Explicit boolean conversion
@@ -189,11 +186,11 @@ const Settings = () => {
// Version checking functions // Version checking functions
const checkForUpdates = async () => { const checkForUpdates = async () => {
setVersionInfo(prev => ({ ...prev, checking: true, error: null })); setVersionInfo(prev => ({ ...prev, checking: true, error: null }));
try { try {
const response = await versionAPI.checkUpdates(); const response = await versionAPI.checkUpdates();
const data = response.data; const data = response.data;
setVersionInfo({ setVersionInfo({
currentVersion: data.currentVersion, currentVersion: data.currentVersion,
latestVersion: data.latestVersion, latestVersion: data.latestVersion,
@@ -224,13 +221,13 @@ const Settings = () => {
} }
setSshTestResult({ testing: true, success: null, message: null, error: null }); setSshTestResult({ testing: true, success: null, message: null, error: null });
try { try {
const response = await versionAPI.testSshKey({ const response = await versionAPI.testSshKey({
sshKeyPath: formData.sshKeyPath, sshKeyPath: formData.sshKeyPath,
githubRepoUrl: formData.githubRepoUrl githubRepoUrl: formData.githubRepoUrl
}); });
setSshTestResult({ setSshTestResult({
testing: false, testing: false,
success: true, success: true,
@@ -261,7 +258,7 @@ const Settings = () => {
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
// Only include sshKeyPath if the toggle is enabled // Only include sshKeyPath if the toggle is enabled
const dataToSubmit = { ...formData }; const dataToSubmit = { ...formData };
if (!dataToSubmit.useCustomSshKey) { if (!dataToSubmit.useCustomSshKey) {
@@ -269,31 +266,25 @@ const Settings = () => {
} }
// Remove the frontend-only field // Remove the frontend-only field
delete dataToSubmit.useCustomSshKey; delete dataToSubmit.useCustomSshKey;
updateSettingsMutation.mutate(dataToSubmit); updateSettingsMutation.mutate(dataToSubmit);
}; };
const validateForm = () => { const validateForm = () => {
const newErrors = {}; const newErrors = {};
if (!formData.serverHost.trim()) { if (!formData.serverHost.trim()) {
newErrors.serverHost = 'Server host is required'; newErrors.serverHost = 'Server host is required';
} }
if (!formData.serverPort || formData.serverPort < 1 || formData.serverPort > 65535) { if (!formData.serverPort || formData.serverPort < 1 || formData.serverPort > 65535) {
newErrors.serverPort = 'Port must be between 1 and 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) { if (!formData.updateInterval || formData.updateInterval < 5 || formData.updateInterval > 1440) {
newErrors.updateInterval = 'Update interval must be between 5 and 1440 minutes'; newErrors.updateInterval = 'Update interval must be between 5 and 1440 minutes';
} }
setErrors(newErrors); setErrors(newErrors);
return Object.keys(newErrors).length === 0; return Object.keys(newErrors).length === 0;
}; };
@@ -307,7 +298,7 @@ const Settings = () => {
} }
// Remove the frontend-only field // Remove the frontend-only field
delete dataToSubmit.useCustomSshKey; delete dataToSubmit.useCustomSshKey;
updateSettingsMutation.mutate(dataToSubmit); updateSettingsMutation.mutate(dataToSubmit);
} }
}; };
@@ -391,7 +382,7 @@ const Settings = () => {
<Server className="h-6 w-6 text-primary-600 mr-3" /> <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> <h2 className="text-xl font-semibold text-secondary-900 dark:text-white">Server Configuration</h2>
</div> </div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6"> <div className="grid grid-cols-1 md:grid-cols-3 gap-6">
<div> <div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2"> <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"> <div className="ml-3">
<h3 className="text-sm font-medium text-blue-800 dark:text-blue-200">Security Notice</h3> <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"> <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. Make sure the server URL is accessible from your client networks.
</p> </p>
</div> </div>
@@ -627,74 +618,6 @@ const Settings = () => {
</form> </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 */} {/* Agent Management Tab */}
{activeTab === 'agent' && ( {activeTab === 'agent' && (
<div className="space-y-6"> <div className="space-y-6">
@@ -826,7 +749,7 @@ const Settings = () => {
</div> </div>
</div> </div>
))} ))}
{agentVersions?.length === 0 && ( {agentVersions?.length === 0 && (
<div className="text-center py-8"> <div className="text-center py-8">
<Code className="h-12 w-12 text-secondary-400 mx-auto mb-4" /> <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" /> <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> <h2 className="text-xl font-semibold text-secondary-900 dark:text-white">Server Version Management</h2>
</div> </div>
<div className="bg-secondary-50 dark:bg-secondary-700 rounded-lg p-6"> <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> <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"> <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. Configure automatic version checking against your GitHub repository to notify users of available updates.
</p> </p>
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2"> <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. Choose whether your repository is public or private to determine the appropriate access method.
</p> </p>
</div> </div>
<div> <div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2"> <label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2">
GitHub Repository URL GitHub Repository URL
@@ -910,7 +833,7 @@ const Settings = () => {
SSH or HTTPS URL to your GitHub repository SSH or HTTPS URL to your GitHub repository
</p> </p>
</div> </div>
{formData.repositoryType === 'private' && ( {formData.repositoryType === 'private' && (
<div> <div>
<div className="flex items-center gap-3 mb-3"> <div className="flex items-center gap-3 mb-3">
@@ -931,7 +854,7 @@ const Settings = () => {
Set custom SSH key path Set custom SSH key path
</label> </label>
</div> </div>
{formData.useCustomSshKey && ( {formData.useCustomSshKey && (
<div> <div>
<label className="block text-sm font-medium text-secondary-700 dark:text-secondary-200 mb-2"> <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"> <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. Path to your SSH deploy key. If not set, will auto-detect from common locations.
</p> </p>
<div className="mt-3"> <div className="mt-3">
<button <button
type="button" type="button"
@@ -957,7 +880,7 @@ const Settings = () => {
> >
{sshTestResult.testing ? 'Testing...' : 'Test SSH Key'} {sshTestResult.testing ? 'Testing...' : 'Test SSH Key'}
</button> </button>
{sshTestResult.success && ( {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="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"> <div className="flex items-center">
@@ -968,7 +891,7 @@ const Settings = () => {
</div> </div>
</div> </div>
)} )}
{sshTestResult.error && ( {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="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"> <div className="flex items-center">
@@ -982,7 +905,7 @@ const Settings = () => {
</div> </div>
</div> </div>
)} )}
{!formData.useCustomSshKey && ( {!formData.useCustomSshKey && (
<p className="text-xs text-secondary-500 dark:text-secondary-400"> <p className="text-xs text-secondary-500 dark:text-secondary-400">
Using auto-detection for SSH key location Using auto-detection for SSH key location
@@ -990,7 +913,7 @@ const Settings = () => {
)} )}
</div> </div>
)} )}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4"> <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="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"> <div className="flex items-center gap-2 mb-2">
@@ -999,7 +922,7 @@ const Settings = () => {
</div> </div>
<span className="text-lg font-mono text-secondary-900 dark:text-white">{versionInfo.currentVersion}</span> <span className="text-lg font-mono text-secondary-900 dark:text-white">{versionInfo.currentVersion}</span>
</div> </div>
<div className="bg-white dark:bg-secondary-800 rounded-lg p-4 border border-secondary-200 dark:border-secondary-600"> <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"> <div className="flex items-center gap-2 mb-2">
<Download className="h-4 w-4 text-blue-600 dark:text-blue-400" /> <Download className="h-4 w-4 text-blue-600 dark:text-blue-400" />
@@ -1019,7 +942,7 @@ const Settings = () => {
</span> </span>
</div> </div>
</div> </div>
{/* Last Checked Time */} {/* Last Checked Time */}
{versionInfo.last_update_check && ( {versionInfo.last_update_check && (
<div className="bg-white dark:bg-secondary-800 rounded-lg p-4 border border-secondary-200 dark:border-secondary-600"> <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> </p>
</div> </div>
)} )}
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<button <button
@@ -1047,7 +970,7 @@ const Settings = () => {
{versionInfo.checking ? 'Checking...' : 'Check for Updates'} {versionInfo.checking ? 'Checking...' : 'Check for Updates'}
</button> </button>
</div> </div>
{/* Save Button for Version Settings */} {/* Save Button for Version Settings */}
<button <button
type="button" type="button"
@@ -1072,7 +995,7 @@ const Settings = () => {
)} )}
</button> </button>
</div> </div>
{versionInfo.error && ( {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="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-700 rounded-lg p-4">
<div className="flex"> <div className="flex">
@@ -1091,7 +1014,7 @@ const Settings = () => {
</div> </div>
</div> </div>
)} )}
{/* Success Message for Version Settings */} {/* Success Message for Version Settings */}
{updateSettingsMutation.isSuccess && ( {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="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>
</div> </div>
)} )}
</div> </div>
@@ -1139,17 +1062,17 @@ const AgentVersionModal = ({ isOpen, onClose, onSubmit, isLoading }) => {
const handleSubmit = (e) => { const handleSubmit = (e) => {
e.preventDefault(); e.preventDefault();
// Basic validation // Basic validation
const newErrors = {}; const newErrors = {};
if (!formData.version.trim()) newErrors.version = 'Version is required'; if (!formData.version.trim()) newErrors.version = 'Version is required';
if (!formData.scriptContent.trim()) newErrors.scriptContent = 'Script content is required'; if (!formData.scriptContent.trim()) newErrors.scriptContent = 'Script content is required';
if (Object.keys(newErrors).length > 0) { if (Object.keys(newErrors).length > 0) {
setErrors(newErrors); setErrors(newErrors);
return; return;
} }
onSubmit(formData); onSubmit(formData);
}; };
@@ -1180,7 +1103,7 @@ const AgentVersionModal = ({ isOpen, onClose, onSubmit, isLoading }) => {
</button> </button>
</div> </div>
</div> </div>
<form onSubmit={handleSubmit} className="px-6 py-4"> <form onSubmit={handleSubmit} className="px-6 py-4">
<div className="space-y-4"> <div className="space-y-4">
<div> <div>
@@ -1253,7 +1176,7 @@ const AgentVersionModal = ({ isOpen, onClose, onSubmit, isLoading }) => {
</label> </label>
</div> </div>
</div> </div>
<div className="flex justify-end gap-3 mt-6"> <div className="flex justify-end gap-3 mt-6">
<button <button
type="button" type="button"