418 lines
21 KiB
HTML
418 lines
21 KiB
HTML
{{define "settings"}}
|
|
{{template "base" .}}
|
|
{{end}}
|
|
|
|
{{define "settings_content"}}
|
|
<div class="max-w-6xl mx-auto p-6">
|
|
<!-- Header -->
|
|
<div class="mb-8">
|
|
<h1 class="text-3xl font-bold text-white mb-2">Settings</h1>
|
|
<p class="text-gray-400">Configure your {{.app_name}} instance</p>
|
|
</div>
|
|
|
|
<!-- Settings Sections -->
|
|
<div class="space-y-8">
|
|
<!-- Image Storage Settings -->
|
|
<div class="bg-gray-800 rounded-lg p-6">
|
|
<h2 class="text-xl font-semibold text-white mb-4">
|
|
<i class="fas fa-images mr-2"></i>Image Storage
|
|
</h2>
|
|
<p class="text-gray-400 mb-6">Configure how images are stored and referenced in your notes</p>
|
|
|
|
<form id="image-storage-form" class="space-y-6">
|
|
<!-- Storage Mode -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-300 mb-3">Storage Mode</label>
|
|
<div class="space-y-3">
|
|
<label class="flex items-start space-x-3">
|
|
<input type="radio" name="storage_mode" value="1" class="mt-1 text-blue-600">
|
|
<div>
|
|
<div class="text-white font-medium">Root Directory</div>
|
|
<div class="text-sm text-gray-400">Store images directly in the notes root directory</div>
|
|
</div>
|
|
</label>
|
|
<label class="flex items-start space-x-3">
|
|
<input type="radio" name="storage_mode" value="2" class="mt-1 text-blue-600">
|
|
<div>
|
|
<div class="text-white font-medium">Specific Folder</div>
|
|
<div class="text-sm text-gray-400">Store all images in a specific folder</div>
|
|
</div>
|
|
</label>
|
|
<label class="flex items-start space-x-3">
|
|
<input type="radio" name="storage_mode" value="3" class="mt-1 text-blue-600">
|
|
<div>
|
|
<div class="text-white font-medium">Same as Note</div>
|
|
<div class="text-sm text-gray-400">Store images in the same directory as the note</div>
|
|
</div>
|
|
</label>
|
|
<label class="flex items-start space-x-3">
|
|
<input type="radio" name="storage_mode" value="4" class="mt-1 text-blue-600">
|
|
<div>
|
|
<div class="text-white font-medium">Subfolder of Note</div>
|
|
<div class="text-sm text-gray-400">Store images in a subfolder within the note's directory</div>
|
|
</div>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Storage Path (for mode 2) -->
|
|
<div id="storage-path-section" class="hidden">
|
|
<label for="storage_path" class="block text-sm font-medium text-gray-300 mb-2">
|
|
Storage Path
|
|
</label>
|
|
<input type="text" id="storage_path" name="storage_path"
|
|
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
placeholder="e.g., images">
|
|
</div>
|
|
|
|
<!-- Subfolder Name (for mode 4) -->
|
|
<div id="subfolder-section" class="hidden">
|
|
<label for="subfolder_name" class="block text-sm font-medium text-gray-300 mb-2">
|
|
Subfolder Name
|
|
</label>
|
|
<input type="text" id="subfolder_name" name="subfolder_name"
|
|
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
placeholder="e.g., attached">
|
|
</div>
|
|
|
|
<div class="flex justify-end">
|
|
<button type="submit" class="btn-primary">
|
|
<i class="fas fa-save mr-2"></i>Save Image Settings
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Notes Directory Settings -->
|
|
<div class="bg-gray-800 rounded-lg p-6">
|
|
<h2 class="text-xl font-semibold text-white mb-4">
|
|
<i class="fas fa-folder mr-2"></i>Notes Directory
|
|
</h2>
|
|
<p class="text-gray-400 mb-6">Set the root directory where your notes are stored</p>
|
|
|
|
<form id="notes-dir-form" class="space-y-6">
|
|
<div>
|
|
<label for="notes_dir" class="block text-sm font-medium text-gray-300 mb-2">
|
|
Notes Directory Path
|
|
</label>
|
|
<input type="text" id="notes_dir" name="notes_dir"
|
|
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
placeholder="/path/to/your/notes">
|
|
<p class="text-xs text-gray-500 mt-1">Provide the absolute path to your notes directory</p>
|
|
</div>
|
|
|
|
<div class="flex justify-end">
|
|
<button type="submit" class="btn-primary">
|
|
<i class="fas fa-save mr-2"></i>Save Directory Settings
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- File Extensions Settings -->
|
|
<div class="bg-gray-800 rounded-lg p-6">
|
|
<h2 class="text-xl font-semibold text-white mb-4">
|
|
<i class="fas fa-file mr-2"></i>File Extensions
|
|
</h2>
|
|
<p class="text-gray-400 mb-6">Configure which file types are allowed and visible</p>
|
|
|
|
<form id="file-extensions-form" class="space-y-6">
|
|
<div>
|
|
<label for="allowed_image_extensions" class="block text-sm font-medium text-gray-300 mb-2">
|
|
Allowed Image Extensions
|
|
</label>
|
|
<input type="text" id="allowed_image_extensions" name="allowed_image_extensions"
|
|
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
placeholder="jpg, jpeg, png, webp, gif">
|
|
<p class="text-xs text-gray-500 mt-1">Comma-separated list of image file extensions</p>
|
|
</div>
|
|
|
|
<div>
|
|
<label for="allowed_file_extensions" class="block text-sm font-medium text-gray-300 mb-2">
|
|
Allowed File Extensions
|
|
</label>
|
|
<input type="text" id="allowed_file_extensions" name="allowed_file_extensions"
|
|
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
placeholder="txt, pdf, html, json, yaml, yml, conf, csv">
|
|
<p class="text-xs text-gray-500 mt-1">Comma-separated list of viewable file extensions</p>
|
|
</div>
|
|
|
|
<!-- Visibility: Left Navigation (Tree) -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mt-4">
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-300 mb-2">Left navigation (tree) visibility</div>
|
|
<label class="flex items-center space-x-2">
|
|
<input type="checkbox" id="show_images_in_tree" name="show_images_in_tree" class="h-4 w-4 text-blue-600 rounded border-gray-600 bg-gray-700">
|
|
<span class="text-sm text-gray-300">Show images in left navigation</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 mt-2">
|
|
<input type="checkbox" id="show_files_in_tree" name="show_files_in_tree" class="h-4 w-4 text-blue-600 rounded border-gray-600 bg-gray-700">
|
|
<span class="text-sm text-gray-300">Show other allowed files in left navigation</span>
|
|
</label>
|
|
</div>
|
|
<div>
|
|
<div class="text-sm font-medium text-gray-300 mb-2">Folder view visibility</div>
|
|
<label class="flex items-center space-x-2">
|
|
<input type="checkbox" id="show_images_in_folder" name="show_images_in_folder" class="h-4 w-4 text-blue-600 rounded border-gray-600 bg-gray-700">
|
|
<span class="text-sm text-gray-300">Show images in folder view</span>
|
|
</label>
|
|
<label class="flex items-center space-x-2 mt-2">
|
|
<input type="checkbox" id="show_files_in_folder" name="show_files_in_folder" class="h-4 w-4 text-blue-600 rounded border-gray-600 bg-gray-700">
|
|
<span class="text-sm text-gray-300">Show other allowed files in folder view</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex justify-end">
|
|
<button type="submit" class="btn-primary">
|
|
<i class="fas fa-save mr-2"></i>Save Extension Settings
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Security Settings -->
|
|
<div class="bg-gray-800 rounded-lg p-6">
|
|
<h2 class="text-xl font-semibold text-white mb-4">
|
|
<i class="fas fa-shield-alt mr-2"></i>Security (IP Ban & Thresholds)
|
|
</h2>
|
|
<p class="text-gray-400 mb-6">Configure failed login thresholds, window, and automatic ban behavior</p>
|
|
|
|
<form id="security-settings-form" class="space-y-6">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label for="pwd_failures_threshold" class="block text-sm font-medium text-gray-300 mb-2">Password Failures Threshold</label>
|
|
<input type="number" id="pwd_failures_threshold" name="pwd_failures_threshold" min="1"
|
|
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
placeholder="e.g., 5">
|
|
</div>
|
|
<div>
|
|
<label for="mfa_failures_threshold" class="block text-sm font-medium text-gray-300 mb-2">MFA Failures Threshold</label>
|
|
<input type="number" id="mfa_failures_threshold" name="mfa_failures_threshold" min="1"
|
|
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
placeholder="e.g., 10">
|
|
</div>
|
|
</div>
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label for="failures_window_minutes" class="block text-sm font-medium text-gray-300 mb-2">Failures Window (minutes)</label>
|
|
<input type="number" id="failures_window_minutes" name="failures_window_minutes" min="1"
|
|
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
placeholder="e.g., 30">
|
|
</div>
|
|
<div>
|
|
<label for="auto_ban_duration_hours" class="block text-sm font-medium text-gray-300 mb-2">Auto-ban Duration (hours)</label>
|
|
<input type="number" id="auto_ban_duration_hours" name="auto_ban_duration_hours" min="1"
|
|
class="w-full bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-white focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
placeholder="e.g., 12">
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<label class="flex items-center space-x-2">
|
|
<input type="checkbox" id="auto_ban_permanent" name="auto_ban_permanent" class="h-4 w-4 text-blue-600 rounded border-gray-600 bg-gray-700">
|
|
<span class="text-sm text-gray-300">Make auto-bans permanent</span>
|
|
</label>
|
|
<p class="text-xs text-gray-500 mt-1">If enabled, IPs exceeding thresholds are permanently banned instead of temporary bans.</p>
|
|
</div>
|
|
|
|
<div class="flex justify-end">
|
|
<button type="submit" class="btn-primary">
|
|
<i class="fas fa-save mr-2"></i>Save Security Settings
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
|
|
{{define "settings_scripts"}}
|
|
<script>
|
|
// Load current settings
|
|
function loadSettings() {
|
|
// Load image storage settings
|
|
fetch(window.prefix('/editor/settings/image_storage'))
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
document.querySelector(`input[name="storage_mode"][value="${data.mode}"]`).checked = true;
|
|
document.getElementById('storage_path').value = data.path || '';
|
|
document.getElementById('subfolder_name').value = data.subfolder || '';
|
|
toggleStorageOptions();
|
|
})
|
|
.catch(error => console.error('Error loading image storage settings:', error));
|
|
|
|
// Load notes directory settings
|
|
fetch(window.prefix('/editor/settings/notes_dir'))
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
document.getElementById('notes_dir').value = data.notes_dir || '';
|
|
})
|
|
.catch(error => console.error('Error loading notes directory settings:', error));
|
|
|
|
// Load file extensions settings
|
|
fetch(window.prefix('/editor/settings/file_extensions'))
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
document.getElementById('allowed_image_extensions').value = data.allowed_image_extensions || '';
|
|
document.getElementById('allowed_file_extensions').value = data.allowed_file_extensions || '';
|
|
// images_hide checkbox may be absent; guard to avoid breaking the rest
|
|
const imagesHideEl = document.getElementById('images_hide');
|
|
if (imagesHideEl) imagesHideEl.checked = !!data.images_hide;
|
|
// New visibility flags
|
|
document.getElementById('show_images_in_tree').checked = !!data.show_images_in_tree;
|
|
document.getElementById('show_files_in_tree').checked = !!data.show_files_in_tree;
|
|
document.getElementById('show_images_in_folder').checked = !!data.show_images_in_folder;
|
|
document.getElementById('show_files_in_folder').checked = !!data.show_files_in_folder;
|
|
})
|
|
.catch(error => console.error('Error loading file extensions settings:', error));
|
|
|
|
// Load security settings
|
|
fetch(window.prefix('/editor/settings/security'))
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
document.getElementById('pwd_failures_threshold').value = data.pwd_failures_threshold ?? '';
|
|
document.getElementById('mfa_failures_threshold').value = data.mfa_failures_threshold ?? '';
|
|
document.getElementById('failures_window_minutes').value = data.failures_window_minutes ?? '';
|
|
document.getElementById('auto_ban_duration_hours').value = data.auto_ban_duration_hours ?? '';
|
|
document.getElementById('auto_ban_permanent').checked = !!data.auto_ban_permanent;
|
|
})
|
|
.catch(error => console.error('Error loading security settings:', error));
|
|
}
|
|
|
|
// Toggle storage mode options
|
|
function toggleStorageOptions() {
|
|
const mode = document.querySelector('input[name="storage_mode"]:checked')?.value;
|
|
const pathSection = document.getElementById('storage-path-section');
|
|
const subfolderSection = document.getElementById('subfolder-section');
|
|
|
|
pathSection.classList.toggle('hidden', mode !== '2');
|
|
subfolderSection.classList.toggle('hidden', mode !== '4');
|
|
}
|
|
|
|
// Event listeners
|
|
document.addEventListener('change', function(e) {
|
|
if (e.target.name === 'storage_mode') {
|
|
toggleStorageOptions();
|
|
}
|
|
});
|
|
|
|
// Security settings form
|
|
document.getElementById('security-settings-form').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(this);
|
|
// Normalize checkbox to boolean string
|
|
formData.set('auto_ban_permanent', document.getElementById('auto_ban_permanent').checked ? 'true' : 'false');
|
|
|
|
const csrf = (document.cookie.match(/(?:^|; )csrf_token=([^;]+)/)||[])[1] ? decodeURIComponent((document.cookie.match(/(?:^|; )csrf_token=([^;]+)/)||[])[1]) : '';
|
|
|
|
fetch(window.prefix('/editor/settings/security'), {
|
|
method: 'POST',
|
|
headers: csrf ? { 'X-CSRF-Token': csrf } : {},
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showNotification('Security settings saved successfully', 'success');
|
|
} else {
|
|
throw new Error(data.error || 'Failed to save settings');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error: ' + error.message, 'error');
|
|
});
|
|
});
|
|
|
|
// Image storage form
|
|
document.getElementById('image-storage-form').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(this);
|
|
|
|
const csrf = (document.cookie.match(/(?:^|; )csrf_token=([^;]+)/)||[])[1] ? decodeURIComponent((document.cookie.match(/(?:^|; )csrf_token=([^;]+)/)||[])[1]) : '';
|
|
|
|
fetch(window.prefix('/editor/settings/image_storage'), {
|
|
method: 'POST',
|
|
headers: csrf ? { 'X-CSRF-Token': csrf } : {},
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showNotification('Image storage settings saved successfully', 'success');
|
|
if (data.reload_required) {
|
|
setTimeout(() => window.location.reload(), 1500);
|
|
}
|
|
} else {
|
|
throw new Error(data.error || 'Failed to save settings');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error: ' + error.message, 'error');
|
|
});
|
|
});
|
|
|
|
// Notes directory form
|
|
document.getElementById('notes-dir-form').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(this);
|
|
|
|
const csrf = (document.cookie.match(/(?:^|; )csrf_token=([^;]+)/)||[])[1] ? decodeURIComponent((document.cookie.match(/(?:^|; )csrf_token=([^;]+)/)||[])[1]) : '';
|
|
|
|
fetch(window.prefix('/editor/settings/notes_dir'), {
|
|
method: 'POST',
|
|
headers: csrf ? { 'X-CSRF-Token': csrf } : {},
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showNotification('Notes directory settings saved successfully', 'success');
|
|
if (data.reload_required) {
|
|
setTimeout(() => window.location.reload(), 1500);
|
|
}
|
|
} else {
|
|
throw new Error(data.error || 'Failed to save settings');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error: ' + error.message, 'error');
|
|
});
|
|
});
|
|
|
|
// File extensions form
|
|
document.getElementById('file-extensions-form').addEventListener('submit', function(e) {
|
|
e.preventDefault();
|
|
|
|
const formData = new FormData(this);
|
|
|
|
const csrf = (document.cookie.match(/(?:^|; )csrf_token=([^;]+)/)||[])[1] ? decodeURIComponent((document.cookie.match(/(?:^|; )csrf_token=([^;]+)/)||[])[1]) : '';
|
|
|
|
fetch(window.prefix('/editor/settings/file_extensions'), {
|
|
method: 'POST',
|
|
headers: csrf ? { 'X-CSRF-Token': csrf } : {},
|
|
body: formData
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
showNotification('File extension settings saved successfully', 'success');
|
|
if (data.reload_required) {
|
|
setTimeout(() => window.location.reload(), 1500);
|
|
}
|
|
} else {
|
|
throw new Error(data.error || 'Failed to save settings');
|
|
}
|
|
})
|
|
.catch(error => {
|
|
showNotification('Error: ' + error.message, 'error');
|
|
});
|
|
});
|
|
|
|
// Load settings on page load
|
|
document.addEventListener('DOMContentLoaded', loadSettings);
|
|
</script>
|
|
{{end}}
|