rapid interations of server side app and firefox extension
This commit is contained in:
@@ -0,0 +1,474 @@
|
||||
<template>
|
||||
<div>
|
||||
<v-card :loading="loading" class="mb-6">
|
||||
<v-card-title>
|
||||
<v-icon class="mr-2">mdi-cog</v-icon>
|
||||
Application Settings
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<v-form ref="settingsForm">
|
||||
<!-- Download Settings -->
|
||||
<h3 class="text-h6 mb-4">Download Settings</h3>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model.number="settings['download.parallel_limit']"
|
||||
label="Parallel Download Limit"
|
||||
type="number"
|
||||
min="1"
|
||||
max="10"
|
||||
hint="Maximum number of concurrent downloads (1-10)"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model.number="settings['download.rate_limit']"
|
||||
label="Rate Limit (seconds)"
|
||||
type="number"
|
||||
min="0.5"
|
||||
max="30"
|
||||
step="0.5"
|
||||
hint="Delay between downloads to avoid rate limiting"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model.number="settings['download.retry_count']"
|
||||
label="Retry Count"
|
||||
type="number"
|
||||
min="0"
|
||||
max="10"
|
||||
hint="Number of times to retry failed downloads"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model.number="settings['download.schedule_interval']"
|
||||
label="Schedule Interval (seconds)"
|
||||
type="number"
|
||||
min="300"
|
||||
hint="How often to check for new content (minimum 5 minutes)"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-divider class="my-6" />
|
||||
|
||||
<!-- Notification Settings -->
|
||||
<h3 class="text-h6 mb-4">Notifications</h3>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<v-switch
|
||||
v-model="settings['notification.enabled']"
|
||||
label="Enable Notifications"
|
||||
color="primary"
|
||||
hint="Receive notifications when new content is downloaded"
|
||||
persistent-hint
|
||||
/>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-text-field
|
||||
v-model="settings['notification.webhook_url']"
|
||||
label="Webhook URL (optional)"
|
||||
:disabled="!settings['notification.enabled']"
|
||||
hint="Discord/Slack webhook for push notifications"
|
||||
persistent-hint
|
||||
placeholder="https://discord.com/api/webhooks/..."
|
||||
/>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-divider class="my-6" />
|
||||
|
||||
<!-- Extension API Key -->
|
||||
<h3 class="text-h6 mb-4">Extension API Key</h3>
|
||||
<v-row>
|
||||
<v-col cols="12">
|
||||
<v-alert type="info" variant="tonal" class="mb-4">
|
||||
Use this API key to configure the browser extension. Keep it secret - anyone with this key can add subscriptions to your account.
|
||||
</v-alert>
|
||||
<v-text-field
|
||||
v-model="settings['security.extension_api_key']"
|
||||
label="API Key"
|
||||
readonly
|
||||
:type="showApiKey ? 'text' : 'password'"
|
||||
:append-inner-icon="showApiKey ? 'mdi-eye-off' : 'mdi-eye'"
|
||||
@click:append-inner="showApiKey = !showApiKey"
|
||||
>
|
||||
<template v-slot:append>
|
||||
<v-btn
|
||||
icon
|
||||
variant="text"
|
||||
size="small"
|
||||
@click="copyApiKey"
|
||||
>
|
||||
<v-icon>mdi-content-copy</v-icon>
|
||||
<v-tooltip activator="parent">Copy to clipboard</v-tooltip>
|
||||
</v-btn>
|
||||
<v-btn
|
||||
icon
|
||||
variant="text"
|
||||
size="small"
|
||||
color="warning"
|
||||
@click="confirmRegenerateKey"
|
||||
>
|
||||
<v-icon>mdi-refresh</v-icon>
|
||||
<v-tooltip activator="parent">Regenerate key</v-tooltip>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-text-field>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="loadSettings"
|
||||
:disabled="saving"
|
||||
>
|
||||
Reset
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="saveSettings"
|
||||
:loading="saving"
|
||||
>
|
||||
Save Settings
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
|
||||
<!-- Gallery-dl Configuration -->
|
||||
<v-card class="mb-6">
|
||||
<v-card-title>
|
||||
<v-icon class="mr-2">mdi-code-json</v-icon>
|
||||
Gallery-dl Configuration
|
||||
<v-chip class="ml-2" size="small" color="warning" variant="tonal">
|
||||
Advanced
|
||||
</v-chip>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<v-alert type="warning" variant="tonal" class="mb-4">
|
||||
This is the raw gallery-dl configuration file. Only modify if you know what you're doing.
|
||||
Invalid configuration may cause downloads to fail.
|
||||
</v-alert>
|
||||
|
||||
<v-textarea
|
||||
v-model="galleryDLConfigText"
|
||||
label="gallery-dl.conf (JSON)"
|
||||
rows="20"
|
||||
variant="outlined"
|
||||
:error-messages="configError"
|
||||
@input="validateConfig"
|
||||
class="font-monospace"
|
||||
/>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn
|
||||
variant="text"
|
||||
href="https://github.com/mikf/gallery-dl/blob/master/docs/configuration.rst"
|
||||
target="_blank"
|
||||
>
|
||||
<v-icon start>mdi-open-in-new</v-icon>
|
||||
Documentation
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="loadGalleryDLConfig"
|
||||
:disabled="savingConfig"
|
||||
>
|
||||
Reset
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="saveGalleryDLConfig"
|
||||
:loading="savingConfig"
|
||||
:disabled="!!configError"
|
||||
>
|
||||
Save Configuration
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
|
||||
<!-- Platform Default Settings -->
|
||||
<v-card class="mb-6">
|
||||
<v-card-title>
|
||||
<v-icon class="mr-2">mdi-web</v-icon>
|
||||
Platform Defaults
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<p class="text-body-2 mb-4">
|
||||
Default settings for each platform. These can be overridden per-source.
|
||||
</p>
|
||||
|
||||
<v-expansion-panels>
|
||||
<v-expansion-panel
|
||||
v-for="(info, platform) in platforms"
|
||||
:key="platform"
|
||||
>
|
||||
<v-expansion-panel-title>
|
||||
<v-icon :color="getPlatformColor(platform)" class="mr-2">
|
||||
{{ getPlatformIcon(platform) }}
|
||||
</v-icon>
|
||||
{{ info.name }}
|
||||
</v-expansion-panel-title>
|
||||
<v-expansion-panel-text>
|
||||
<v-table density="compact">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="200"><strong>Auth Required</strong></td>
|
||||
<td>
|
||||
<v-chip :color="info.requires_auth ? 'warning' : 'success'" size="small">
|
||||
{{ info.requires_auth ? 'Yes' : 'No' }}
|
||||
</v-chip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Auth Type</strong></td>
|
||||
<td class="text-capitalize">{{ info.auth_type }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Content Types</strong></td>
|
||||
<td>
|
||||
<v-chip
|
||||
v-for="type in info.content_types"
|
||||
:key="type"
|
||||
size="small"
|
||||
class="mr-1 mb-1"
|
||||
>
|
||||
{{ type }}
|
||||
</v-chip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Default Sleep</strong></td>
|
||||
<td>{{ info.default_config?.sleep || 3.0 }} seconds</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>URL Examples</strong></td>
|
||||
<td>
|
||||
<div v-for="url in info.url_examples" :key="url" class="text-caption">
|
||||
{{ url }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-expansion-panel-text>
|
||||
</v-expansion-panel>
|
||||
</v-expansion-panels>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- System Information -->
|
||||
<v-card>
|
||||
<v-card-title>
|
||||
<v-icon class="mr-2">mdi-information</v-icon>
|
||||
System Information
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<v-table density="compact">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="200"><strong>Version</strong></td>
|
||||
<td>1.0.0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>API Status</strong></td>
|
||||
<td>
|
||||
<v-chip :color="apiHealthy ? 'success' : 'error'" size="small">
|
||||
{{ apiHealthy ? 'Connected' : 'Disconnected' }}
|
||||
</v-chip>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Download Path</strong></td>
|
||||
<td>/data/downloads</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</v-table>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useSettingsStore } from '../stores/settings'
|
||||
import { useNotificationStore } from '../stores/notifications'
|
||||
import { platformsApi } from '../services/api'
|
||||
import api from '../services/api'
|
||||
|
||||
const settingsStore = useSettingsStore()
|
||||
const notifications = useNotificationStore()
|
||||
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const savingConfig = ref(false)
|
||||
const settings = ref({})
|
||||
const galleryDLConfigText = ref('')
|
||||
const configError = ref('')
|
||||
const platforms = ref({})
|
||||
const apiHealthy = ref(true)
|
||||
const showApiKey = ref(false)
|
||||
const regenerateDialog = ref(false)
|
||||
|
||||
onMounted(async () => {
|
||||
await loadAll()
|
||||
})
|
||||
|
||||
async function loadAll() {
|
||||
loading.value = true
|
||||
try {
|
||||
await Promise.all([
|
||||
loadSettings(),
|
||||
loadGalleryDLConfig(),
|
||||
loadPlatforms(),
|
||||
checkApiHealth(),
|
||||
])
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSettings() {
|
||||
try {
|
||||
const data = await settingsStore.fetchSettings()
|
||||
settings.value = { ...data }
|
||||
} catch (error) {
|
||||
notifications.error(`Failed to load settings: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function saveSettings() {
|
||||
saving.value = true
|
||||
try {
|
||||
await settingsStore.updateSettings(settings.value)
|
||||
notifications.success('Settings saved')
|
||||
} catch (error) {
|
||||
notifications.error(`Failed to save: ${error.message}`)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGalleryDLConfig() {
|
||||
try {
|
||||
const config = await settingsStore.fetchGalleryDLConfig()
|
||||
galleryDLConfigText.value = JSON.stringify(config, null, 2)
|
||||
configError.value = ''
|
||||
} catch (error) {
|
||||
notifications.error(`Failed to load config: ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
function validateConfig() {
|
||||
try {
|
||||
JSON.parse(galleryDLConfigText.value)
|
||||
configError.value = ''
|
||||
} catch (e) {
|
||||
configError.value = `Invalid JSON: ${e.message}`
|
||||
}
|
||||
}
|
||||
|
||||
async function saveGalleryDLConfig() {
|
||||
if (configError.value) return
|
||||
|
||||
savingConfig.value = true
|
||||
try {
|
||||
const config = JSON.parse(galleryDLConfigText.value)
|
||||
await settingsStore.updateGalleryDLConfig(config)
|
||||
notifications.success('Gallery-dl configuration saved')
|
||||
} catch (error) {
|
||||
notifications.error(`Failed to save: ${error.message}`)
|
||||
} finally {
|
||||
savingConfig.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPlatforms() {
|
||||
try {
|
||||
const response = await platformsApi.list()
|
||||
platforms.value = response.data.platforms
|
||||
} catch (error) {
|
||||
console.error('Failed to load platforms:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async function checkApiHealth() {
|
||||
try {
|
||||
await api.get('/health')
|
||||
apiHealthy.value = true
|
||||
} catch {
|
||||
apiHealthy.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function getPlatformIcon(platform) {
|
||||
const icons = {
|
||||
patreon: 'mdi-patreon',
|
||||
subscribestar: 'mdi-star',
|
||||
hentaifoundry: 'mdi-palette',
|
||||
discord: 'mdi-discord',
|
||||
}
|
||||
return icons[platform] || 'mdi-web'
|
||||
}
|
||||
|
||||
function getPlatformColor(platform) {
|
||||
const colors = {
|
||||
patreon: '#FF424D',
|
||||
subscribestar: '#FFD700',
|
||||
hentaifoundry: '#9C27B0',
|
||||
discord: '#5865F2',
|
||||
}
|
||||
return colors[platform] || 'grey'
|
||||
}
|
||||
|
||||
async function copyApiKey() {
|
||||
try {
|
||||
await navigator.clipboard.writeText(settings.value['security.extension_api_key'])
|
||||
notifications.success('API key copied to clipboard')
|
||||
} catch (error) {
|
||||
notifications.error('Failed to copy to clipboard')
|
||||
}
|
||||
}
|
||||
|
||||
async function confirmRegenerateKey() {
|
||||
if (confirm('Are you sure you want to regenerate the API key? The old key will stop working immediately.')) {
|
||||
await regenerateApiKey()
|
||||
}
|
||||
}
|
||||
|
||||
async function regenerateApiKey() {
|
||||
try {
|
||||
const response = await api.post('/settings/api-key/regenerate')
|
||||
settings.value['security.extension_api_key'] = response.data.api_key
|
||||
notifications.success('API key regenerated')
|
||||
} catch (error) {
|
||||
notifications.error(`Failed to regenerate: ${error.message}`)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.font-monospace {
|
||||
font-family: 'Courier New', Courier, monospace;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user