mirror of
https://github.com/StarFleetCPTN/GoMFT.git
synced 2026-09-08 15:41:20 +02:00
feat: Implement frontend asset build process and update Docker configuration
- Added a new build script using esbuild to bundle JavaScript and CSS assets, including Tailwind CSS and Font Awesome. - Updated the Dockerfile to include a multi-stage build process for frontend assets, ensuring efficient image creation. - Enhanced GitHub workflows to automate the installation of Node.js dependencies and build frontend assets during CI/CD. - Introduced a .gitignore entry for the dist directory to prevent unnecessary files from being tracked. - Improved caching and content type handling for static files served by the application.
This commit is contained in:
@@ -29,6 +29,23 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Needed to get all tags for versioning
|
fetch-depth: 0 # Needed to get all tags for versioning
|
||||||
|
|
||||||
|
# Set up Node.js for frontend build
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
# Build frontend assets
|
||||||
|
- name: Build frontend assets
|
||||||
|
run: |
|
||||||
|
node build.js
|
||||||
|
ls -la static/dist/
|
||||||
|
|
||||||
# Set version information
|
# Set version information
|
||||||
- name: Set Version
|
- name: Set Version
|
||||||
id: version
|
id: version
|
||||||
|
|||||||
@@ -30,6 +30,23 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0 # Needed to get all tags for versioning
|
fetch-depth: 0 # Needed to get all tags for versioning
|
||||||
|
|
||||||
|
# Set up Node.js for frontend build
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
# Install dependencies
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
# Build frontend assets
|
||||||
|
- name: Build frontend assets
|
||||||
|
run: |
|
||||||
|
node build.js
|
||||||
|
ls -la static/dist/
|
||||||
|
|
||||||
# Set version information
|
# Set version information
|
||||||
- name: Set Version
|
- name: Set Version
|
||||||
id: version
|
id: version
|
||||||
|
|||||||
@@ -27,6 +27,26 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '20'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install Node.js dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build frontend assets
|
||||||
|
run: |
|
||||||
|
# Build JavaScript and CSS assets
|
||||||
|
node build.js
|
||||||
|
|
||||||
|
# Ensure the dist directory exists
|
||||||
|
mkdir -p static/dist
|
||||||
|
|
||||||
|
# Verify the build output
|
||||||
|
ls -la static/dist
|
||||||
|
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v5
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -79,6 +79,8 @@ backups/
|
|||||||
/destination/
|
/destination/
|
||||||
/archive/
|
/archive/
|
||||||
|
|
||||||
|
# Ignore the dist directory
|
||||||
|
static/dist/
|
||||||
|
|
||||||
# Ignore binaries
|
# Ignore binaries
|
||||||
gomft
|
gomft
|
||||||
+44
-3
@@ -1,3 +1,32 @@
|
|||||||
|
# Build frontend assets
|
||||||
|
FROM node:20-alpine AS frontend-builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Copy all files needed for the build first
|
||||||
|
COPY package.json package-lock.json ./
|
||||||
|
COPY build.js ./
|
||||||
|
COPY static/ ./static/
|
||||||
|
COPY tailwind.config.js ./
|
||||||
|
|
||||||
|
# Debug: Show the contents of build.js
|
||||||
|
RUN echo "Contents of build.js:" && cat build.js
|
||||||
|
|
||||||
|
# Remove the build script from postinstall
|
||||||
|
RUN sed -i 's/"postinstall": "npm run build",//' package.json
|
||||||
|
|
||||||
|
# Install dependencies and build with verbose output
|
||||||
|
RUN npm ci && \
|
||||||
|
echo "Building frontend assets..." && \
|
||||||
|
node build.js && \
|
||||||
|
echo "Build complete. Contents of dist:" && \
|
||||||
|
ls -la static/dist/ && \
|
||||||
|
echo "Sample of app.js:" && \
|
||||||
|
head -n 10 static/dist/app.js && \
|
||||||
|
echo "Sample of app.css:" && \
|
||||||
|
head -n 10 static/dist/app.css
|
||||||
|
|
||||||
|
# Go build stage
|
||||||
FROM golang:1.24-alpine AS builder
|
FROM golang:1.24-alpine AS builder
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
@@ -17,7 +46,20 @@ RUN go install github.com/a-h/templ/cmd/templ@latest
|
|||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
RUN go mod download
|
RUN go mod download
|
||||||
|
|
||||||
# Copy the rest of the source code
|
# Create static directory structure
|
||||||
|
RUN mkdir -p /app/static/dist
|
||||||
|
|
||||||
|
# Copy built frontend assets from frontend-builder BEFORE copying Go source
|
||||||
|
COPY --from=frontend-builder /app/static/dist/ /app/static/dist/
|
||||||
|
|
||||||
|
# Copy the rest of the static files
|
||||||
|
COPY static/ /app/static/
|
||||||
|
|
||||||
|
# Verify static files are in place before Go build
|
||||||
|
RUN echo "Verifying static files before Go build:" && \
|
||||||
|
ls -la /app/static/dist/
|
||||||
|
|
||||||
|
# Now copy the rest of the source code
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Generate template files from .templ files
|
# Generate template files from .templ files
|
||||||
@@ -61,8 +103,7 @@ RUN addgroup -g ${GID} ${USERNAME} && \
|
|||||||
COPY --from=builder /app/gomft /app/
|
COPY --from=builder /app/gomft /app/
|
||||||
COPY --from=builder /usr/local/bin/rclone /usr/local/bin/rclone
|
COPY --from=builder /usr/local/bin/rclone /usr/local/bin/rclone
|
||||||
|
|
||||||
# Copy static files and configurations
|
# Copy components
|
||||||
COPY static/ /app/static/
|
|
||||||
COPY components/ /app/components/
|
COPY components/ /app/components/
|
||||||
|
|
||||||
# Copy entrypoint script
|
# Copy entrypoint script
|
||||||
|
|||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import * as esbuild from 'esbuild';
|
||||||
|
import path from 'path';
|
||||||
|
import { fileURLToPath } from 'url';
|
||||||
|
import { execSync } from 'child_process';
|
||||||
|
import fs from 'fs';
|
||||||
|
|
||||||
|
const __filename = fileURLToPath(import.meta.url);
|
||||||
|
const __dirname = path.dirname(__filename);
|
||||||
|
|
||||||
|
const isWatch = process.argv.includes('--watch');
|
||||||
|
|
||||||
|
// Ensure the dist directory exists
|
||||||
|
const distDir = path.join(__dirname, 'static', 'dist');
|
||||||
|
if (!fs.existsSync(distDir)) {
|
||||||
|
fs.mkdirSync(distDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy Font Awesome files
|
||||||
|
const fontAwesomeSrcDir = path.join(__dirname, 'node_modules', '@fortawesome', 'fontawesome-free');
|
||||||
|
const fontAwesomeDestDir = path.join(distDir, 'fontawesome');
|
||||||
|
|
||||||
|
// Copy CSS files
|
||||||
|
const cssFiles = [
|
||||||
|
'css/all.min.css',
|
||||||
|
'css/fontawesome.min.css',
|
||||||
|
'css/solid.min.css',
|
||||||
|
'css/regular.min.css',
|
||||||
|
'css/brands.min.css'
|
||||||
|
];
|
||||||
|
|
||||||
|
cssFiles.forEach(file => {
|
||||||
|
const srcFile = path.join(fontAwesomeSrcDir, file);
|
||||||
|
const destFile = path.join(fontAwesomeDestDir, file);
|
||||||
|
const destDir = path.dirname(destFile);
|
||||||
|
|
||||||
|
if (!fs.existsSync(destDir)) {
|
||||||
|
fs.mkdirSync(destDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fs.existsSync(srcFile)) {
|
||||||
|
fs.copyFileSync(srcFile, destFile);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Copy webfonts
|
||||||
|
const webfontsSrcDir = path.join(fontAwesomeSrcDir, 'webfonts');
|
||||||
|
const webfontsDestDir = path.join(fontAwesomeDestDir, 'webfonts');
|
||||||
|
|
||||||
|
if (!fs.existsSync(webfontsDestDir)) {
|
||||||
|
fs.mkdirSync(webfontsDestDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readdirSync(webfontsSrcDir).forEach(file => {
|
||||||
|
fs.copyFileSync(
|
||||||
|
path.join(webfontsSrcDir, file),
|
||||||
|
path.join(webfontsDestDir, file)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const commonConfig = {
|
||||||
|
sourcemap: true,
|
||||||
|
minify: true,
|
||||||
|
bundle: true,
|
||||||
|
platform: 'browser',
|
||||||
|
target: ['es2020'],
|
||||||
|
};
|
||||||
|
|
||||||
|
async function buildTailwind() {
|
||||||
|
console.log('Building Tailwind CSS...');
|
||||||
|
execSync('npx tailwindcss -i ./static/css/app.css -o ./static/dist/app.css --minify');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function build() {
|
||||||
|
try {
|
||||||
|
// Build vendor JavaScript bundle (CDN dependencies)
|
||||||
|
await esbuild.build({
|
||||||
|
...commonConfig,
|
||||||
|
entryPoints: ['static/js/vendor.js'],
|
||||||
|
outfile: 'static/dist/vendor.js',
|
||||||
|
format: 'iife',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build application JavaScript
|
||||||
|
await esbuild.build({
|
||||||
|
...commonConfig,
|
||||||
|
entryPoints: ['static/js/app.js'],
|
||||||
|
outfile: 'static/dist/app.js',
|
||||||
|
format: 'iife',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build initialization JavaScript
|
||||||
|
await esbuild.build({
|
||||||
|
...commonConfig,
|
||||||
|
entryPoints: ['static/js/init.js'],
|
||||||
|
outfile: 'static/dist/init.js',
|
||||||
|
format: 'iife',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build CSS with Tailwind
|
||||||
|
await buildTailwind();
|
||||||
|
|
||||||
|
console.log('Build completed successfully!');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Build failed:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isWatch) {
|
||||||
|
// Watch mode
|
||||||
|
console.log('Starting watch mode...');
|
||||||
|
const ctx = await esbuild.context(commonConfig);
|
||||||
|
await ctx.watch();
|
||||||
|
|
||||||
|
// Watch Tailwind CSS
|
||||||
|
execSync('npx tailwindcss -i ./static/css/app.css -o ./static/dist/app.css --watch');
|
||||||
|
|
||||||
|
console.log('Watching for changes...');
|
||||||
|
} else {
|
||||||
|
// Single build
|
||||||
|
build();
|
||||||
|
}
|
||||||
@@ -87,7 +87,7 @@ templ AdminRoles(ctx context.Context, data RolesData) {
|
|||||||
<button
|
<button
|
||||||
data-role-id={ fmt.Sprint(role.ID) }
|
data-role-id={ fmt.Sprint(role.ID) }
|
||||||
data-role-name={ role.Name }
|
data-role-name={ role.Name }
|
||||||
onclick={ showRoleDialog(fmt.Sprintf("delete-role-dialog-%d", role.ID)) }
|
onclick={ showModal(fmt.Sprintf("delete-role-dialog-%d", role.ID)) }
|
||||||
class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300">
|
class="text-red-600 hover:text-red-900 dark:text-red-400 dark:hover:text-red-300">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -269,7 +269,10 @@ templ AdminRoles(ctx context.Context, data RolesData) {
|
|||||||
|
|
||||||
// RoleDialog for confirmation actions
|
// RoleDialog for confirmation actions
|
||||||
templ RoleDialog(id string, title string, message string, confirmClass string, confirmText string, action string, roleID uint, roleName string) {
|
templ RoleDialog(id string, title string, message string, confirmClass string, confirmText string, action string, roleID uint, roleName string) {
|
||||||
<div id={ id } tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full bg-gray-900/50 dark:bg-gray-900/80 backdrop-blur-sm">
|
<div id={ id } tabindex="-1" aria-hidden="true" class="hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div id={ fmt.Sprintf("%s-backdrop", id) } class="fixed inset-0 bg-gray-900/50 dark:bg-gray-900/80 backdrop-blur-sm"></div>
|
||||||
|
<!-- Modal content -->
|
||||||
<div class="relative p-4 w-full max-w-md max-h-full mx-auto">
|
<div class="relative p-4 w-full max-w-md max-h-full mx-auto">
|
||||||
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
<div class="p-6 text-center">
|
<div class="p-6 text-center">
|
||||||
@@ -288,7 +291,7 @@ templ RoleDialog(id string, title string, message string, confirmClass string, c
|
|||||||
onclick={ triggerRoleDelete(id, roleID, roleName) }>
|
onclick={ triggerRoleDelete(id, roleID, roleName) }>
|
||||||
{ confirmText }
|
{ confirmText }
|
||||||
</button>
|
</button>
|
||||||
<button type="button" data-modal-hide={ id } class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
<button type="button" onclick={ closeModal(id) } class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -297,16 +300,6 @@ templ RoleDialog(id string, title string, message string, confirmClass string, c
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
script hideRoleDialog(id string) {
|
|
||||||
document.getElementById(id).classList.add("hidden");
|
|
||||||
document.getElementById(id).classList.remove("flex");
|
|
||||||
}
|
|
||||||
|
|
||||||
script showRoleDialog(id string) {
|
|
||||||
document.getElementById(id).classList.remove("hidden");
|
|
||||||
document.getElementById(id).classList.add("flex");
|
|
||||||
}
|
|
||||||
|
|
||||||
script triggerRoleDelete(dialogId string, roleID uint, roleName string) {
|
script triggerRoleDelete(dialogId string, roleID uint, roleName string) {
|
||||||
// Hide the dialog
|
// Hide the dialog
|
||||||
document.getElementById(dialogId).classList.add("hidden");
|
document.getElementById(dialogId).classList.add("hidden");
|
||||||
|
|||||||
@@ -8,7 +8,10 @@ import (
|
|||||||
|
|
||||||
// Dialog component for confirmation dialogs using Flowbite modal
|
// Dialog component for confirmation dialogs using Flowbite modal
|
||||||
templ ConfigDialog(id string, title string, message string, confirmClass string, confirmText string, action string, configID uint, configName string) {
|
templ ConfigDialog(id string, title string, message string, confirmClass string, confirmText string, action string, configID uint, configName string) {
|
||||||
<div id={ id } tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full bg-gray-900/50 dark:bg-gray-900/80 backdrop-blur-sm">
|
<div id={ id } tabindex="-1" aria-hidden="true" class="hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div id={ fmt.Sprintf("%s-backdrop", id) } class="fixed inset-0 bg-gray-900/50 dark:bg-gray-900/80 backdrop-blur-sm"></div>
|
||||||
|
<!-- Modal content -->
|
||||||
<div class="relative p-4 w-full max-w-md max-h-full mx-auto">
|
<div class="relative p-4 w-full max-w-md max-h-full mx-auto">
|
||||||
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
<div class="p-6 text-center">
|
<div class="p-6 text-center">
|
||||||
@@ -30,7 +33,7 @@ templ ConfigDialog(id string, title string, message string, confirmClass string,
|
|||||||
onclick={ triggerConfigDelete(id, configID, configName) }>
|
onclick={ triggerConfigDelete(id, configID, configName) }>
|
||||||
{ confirmText }
|
{ confirmText }
|
||||||
</button>
|
</button>
|
||||||
<button type="button" data-modal-hide={ id } class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
<button type="button" onclick={ closeModal(id) } class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,14 +42,26 @@ templ ConfigDialog(id string, title string, message string, confirmClass string,
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
script hideConfigDialog(id string) {
|
script closeModal(id string) {
|
||||||
document.getElementById(id).classList.add("hidden");
|
const modal = document.getElementById(id);
|
||||||
document.getElementById(id).classList.remove("flex");
|
const backdrop = document.getElementById(id + '-backdrop');
|
||||||
|
if (modal) {
|
||||||
|
modal.classList.add('hidden');
|
||||||
|
modal.classList.remove('flex');
|
||||||
|
}
|
||||||
|
if (backdrop) {
|
||||||
|
backdrop.remove();
|
||||||
|
}
|
||||||
|
document.body.style.overflow = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
script showConfigDialog(id string) {
|
script showModal(id string) {
|
||||||
document.getElementById(id).classList.remove("hidden");
|
const modal = document.getElementById(id);
|
||||||
document.getElementById(id).classList.add("flex");
|
if (modal) {
|
||||||
|
modal.classList.remove('hidden');
|
||||||
|
modal.classList.add('flex');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
script triggerConfigDelete(dialogId string, configID uint, configName string) {
|
script triggerConfigDelete(dialogId string, configID uint, configName string) {
|
||||||
@@ -429,7 +444,7 @@ templ Configs(ctx context.Context, data ConfigsData) {
|
|||||||
)
|
)
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={ showConfigDialog(fmt.Sprintf("delete-config-dialog-%d", config.ID)) }
|
onclick={ showModal(fmt.Sprintf("delete-config-dialog-%d", config.ID)) }
|
||||||
class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-3 py-1.5 text-center inline-flex items-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800">
|
class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-3 py-1.5 text-center inline-flex items-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800">
|
||||||
<i class="fas fa-trash-alt w-3.5 h-3.5 mr-1.5"></i>
|
<i class="fas fa-trash-alt w-3.5 h-3.5 mr-1.5"></i>
|
||||||
Delete
|
Delete
|
||||||
|
|||||||
@@ -76,7 +76,10 @@ func formatFileSize(size int64) string {
|
|||||||
|
|
||||||
// Updated Dialog component using Flowbite
|
// Updated Dialog component using Flowbite
|
||||||
templ FileMetadataDialog(id string, title string, message string, confirmClass string, confirmText string, action string, fileID uint, fileName string, section string) {
|
templ FileMetadataDialog(id string, title string, message string, confirmClass string, confirmText string, action string, fileID uint, fileName string, section string) {
|
||||||
<div id={ id } tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full bg-gray-900/50 dark:bg-gray-900/80 backdrop-blur-sm">
|
<div id={ id } tabindex="-1" aria-hidden="true" class="hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div id={ fmt.Sprintf("%s-backdrop", id) } class="fixed inset-0 bg-gray-900/50 dark:bg-gray-900/80 backdrop-blur-sm"></div>
|
||||||
|
<!-- Modal content -->
|
||||||
<div class="relative p-4 w-full max-w-md max-h-full mx-auto">
|
<div class="relative p-4 w-full max-w-md max-h-full mx-auto">
|
||||||
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
<div class="p-6 text-center">
|
<div class="p-6 text-center">
|
||||||
@@ -112,7 +115,7 @@ templ FileMetadataDialog(id string, title string, message string, confirmClass s
|
|||||||
{ confirmText }
|
{ confirmText }
|
||||||
</button>
|
</button>
|
||||||
}
|
}
|
||||||
<button type="button" data-modal-hide={ id } class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
<button type="button" onclick={ closeModal(id) } class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -121,16 +124,6 @@ templ FileMetadataDialog(id string, title string, message string, confirmClass s
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
script hideFileDialog(id string) {
|
|
||||||
document.getElementById(id).classList.add("hidden");
|
|
||||||
document.getElementById(id).classList.remove("flex");
|
|
||||||
}
|
|
||||||
|
|
||||||
script showFileDialog(id string) {
|
|
||||||
document.getElementById(id).classList.remove("hidden");
|
|
||||||
document.getElementById(id).classList.add("flex");
|
|
||||||
}
|
|
||||||
|
|
||||||
script triggerFileDelete(dialogId string, fileID uint, fileName string) {
|
script triggerFileDelete(dialogId string, fileID uint, fileName string) {
|
||||||
// Hide the dialog
|
// Hide the dialog
|
||||||
document.getElementById(dialogId).classList.add("hidden");
|
document.getElementById(dialogId).classList.add("hidden");
|
||||||
@@ -211,7 +204,7 @@ templ FileMetadataListPartial(data FileMetadataListData) {
|
|||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={ showFileDialog(fmt.Sprintf("delete-file-dialog-%d", file.ID)) }
|
onclick={ showModal(fmt.Sprintf("delete-file-dialog-%d", file.ID)) }
|
||||||
class="font-medium text-red-600 dark:text-red-500 hover:underline">
|
class="font-medium text-red-600 dark:text-red-500 hover:underline">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -393,8 +386,6 @@ templ FileMetadataList(ctx context.Context, data FileMetadataListData) {
|
|||||||
|
|
||||||
// Track all HTMX events for debugging
|
// Track all HTMX events for debugging
|
||||||
document.addEventListener('htmx:beforeRequest', function(event) {
|
document.addEventListener('htmx:beforeRequest', function(event) {
|
||||||
console.log("HTMX before request:", event.detail);
|
|
||||||
|
|
||||||
// Check if this is a DELETE request by examining the URL and method
|
// Check if this is a DELETE request by examining the URL and method
|
||||||
const path = event.detail.path;
|
const path = event.detail.path;
|
||||||
const method = event.detail.verb;
|
const method = event.detail.verb;
|
||||||
@@ -412,8 +403,6 @@ templ FileMetadataList(ctx context.Context, data FileMetadataListData) {
|
|||||||
|
|
||||||
// Track HTMX after-request events for file deletion
|
// Track HTMX after-request events for file deletion
|
||||||
document.addEventListener('htmx:afterRequest', function(event) {
|
document.addEventListener('htmx:afterRequest', function(event) {
|
||||||
console.log("HTMX after request:", event.detail);
|
|
||||||
|
|
||||||
// Check for file deletion multiple ways
|
// Check for file deletion multiple ways
|
||||||
const isDeleteRequest =
|
const isDeleteRequest =
|
||||||
// Check global flag from the triggerFileDelete function
|
// Check global flag from the triggerFileDelete function
|
||||||
@@ -970,7 +959,7 @@ templ FileMetadataDetails(ctx context.Context, data FileMetadataDetailsData) {
|
|||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={ showFileDialog(fmt.Sprintf("delete-file-dialog-%d", data.File.ID)) }
|
onclick={ showModal(fmt.Sprintf("delete-file-dialog-%d", data.File.ID)) }
|
||||||
class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800">
|
class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 dark:bg-red-600 dark:hover:bg-red-700 focus:outline-none dark:focus:ring-red-800">
|
||||||
<i class="fas fa-trash mr-2"></i> Delete Record
|
<i class="fas fa-trash mr-2"></i> Delete Record
|
||||||
</button>
|
</button>
|
||||||
@@ -1336,7 +1325,7 @@ templ FileMetadataSearchContent(data FileMetadataSearchData) {
|
|||||||
</a>
|
</a>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={ showFileDialog(fmt.Sprintf("delete-file-dialog-%d", file.ID)) }
|
onclick={ showModal(fmt.Sprintf("delete-file-dialog-%d", file.ID)) }
|
||||||
class="font-medium text-red-600 dark:text-red-500 hover:underline">
|
class="font-medium text-red-600 dark:text-red-500 hover:underline">
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
+6
-17
@@ -8,7 +8,10 @@ import (
|
|||||||
|
|
||||||
// Dialog component for confirmation dialogs using Flowbite modal
|
// Dialog component for confirmation dialogs using Flowbite modal
|
||||||
templ JobDialog(id string, title string, message string, confirmClass string, confirmText string, action string, jobID uint, jobName string) {
|
templ JobDialog(id string, title string, message string, confirmClass string, confirmText string, action string, jobID uint, jobName string) {
|
||||||
<div id={ id } tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full bg-gray-900/50 dark:bg-gray-900/80 backdrop-blur-sm">
|
<div id={ id } tabindex="-1" aria-hidden="true" class="hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div id={ fmt.Sprintf("%s-backdrop", id) } class="fixed inset-0 bg-gray-900/50 dark:bg-gray-900/80 backdrop-blur-sm"></div>
|
||||||
|
<!-- Modal content -->
|
||||||
<div class="relative p-4 w-full max-w-md max-h-full mx-auto">
|
<div class="relative p-4 w-full max-w-md max-h-full mx-auto">
|
||||||
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
<div class="p-6 text-center">
|
<div class="p-6 text-center">
|
||||||
@@ -30,7 +33,7 @@ templ JobDialog(id string, title string, message string, confirmClass string, co
|
|||||||
onclick={ triggerJobDelete(id, jobID, jobName) }>
|
onclick={ triggerJobDelete(id, jobID, jobName) }>
|
||||||
{ confirmText }
|
{ confirmText }
|
||||||
</button>
|
</button>
|
||||||
<button type="button" data-modal-hide={ id } class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
<button type="button" onclick={ closeModal(id) } class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,16 +42,6 @@ templ JobDialog(id string, title string, message string, confirmClass string, co
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
script hideJobDialog(id string) {
|
|
||||||
document.getElementById(id).classList.add("hidden");
|
|
||||||
document.getElementById(id).classList.remove("flex");
|
|
||||||
}
|
|
||||||
|
|
||||||
script showJobDialog(id string) {
|
|
||||||
document.getElementById(id).classList.remove("hidden");
|
|
||||||
document.getElementById(id).classList.add("flex");
|
|
||||||
}
|
|
||||||
|
|
||||||
script triggerJobDelete(dialogId string, jobID uint, jobName string) {
|
script triggerJobDelete(dialogId string, jobID uint, jobName string) {
|
||||||
// Hide the dialog
|
// Hide the dialog
|
||||||
document.getElementById(dialogId).classList.add("hidden");
|
document.getElementById(dialogId).classList.add("hidden");
|
||||||
@@ -197,8 +190,6 @@ templ Jobs(ctx context.Context, data JobsData) {
|
|||||||
|
|
||||||
// Track all HTMX events for debugging
|
// Track all HTMX events for debugging
|
||||||
document.addEventListener('htmx:beforeRequest', function(event) {
|
document.addEventListener('htmx:beforeRequest', function(event) {
|
||||||
console.log("HTMX before request:", event.detail);
|
|
||||||
|
|
||||||
// Check if this is a DELETE request by examining the URL and method
|
// Check if this is a DELETE request by examining the URL and method
|
||||||
const path = event.detail.path;
|
const path = event.detail.path;
|
||||||
const method = event.detail.verb;
|
const method = event.detail.verb;
|
||||||
@@ -216,8 +207,6 @@ templ Jobs(ctx context.Context, data JobsData) {
|
|||||||
|
|
||||||
// Track HTMX after-request events for job deletion
|
// Track HTMX after-request events for job deletion
|
||||||
document.addEventListener('htmx:afterRequest', function(event) {
|
document.addEventListener('htmx:afterRequest', function(event) {
|
||||||
console.log("HTMX after request:", event.detail);
|
|
||||||
|
|
||||||
// Check for job deletion multiple ways
|
// Check for job deletion multiple ways
|
||||||
const isDeleteRequest =
|
const isDeleteRequest =
|
||||||
// Check global flag from the triggerJobDelete function
|
// Check global flag from the triggerJobDelete function
|
||||||
@@ -395,7 +384,7 @@ templ Jobs(ctx context.Context, data JobsData) {
|
|||||||
)
|
)
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={ showJobDialog(fmt.Sprintf("delete-job-dialog-%d", job.ID)) }
|
onclick={ showModal(fmt.Sprintf("delete-job-dialog-%d", job.ID)) }
|
||||||
class="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-xs px-3 py-1.5 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900">
|
class="focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-xs px-3 py-1.5 dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-900">
|
||||||
<i class="fas fa-trash mr-1"></i>
|
<i class="fas fa-trash mr-1"></i>
|
||||||
Delete
|
Delete
|
||||||
|
|||||||
+116
-96
@@ -54,42 +54,36 @@ templ LayoutWithContext(title string, ctx context.Context) {
|
|||||||
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png"/>
|
<link rel="icon" type="image/png" sizes="16x16" href="/static/favicon-16x16.png"/>
|
||||||
<link rel="manifest" href="/static/site.webmanifest"/>
|
<link rel="manifest" href="/static/site.webmanifest"/>
|
||||||
<title>{ title } - GoMFT</title>
|
<title>{ title } - GoMFT</title>
|
||||||
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
|
<!-- Font Awesome -->
|
||||||
<script src="https://unpkg.com/alpinejs@3.13.5/dist/cdn.min.js" defer></script>
|
<link rel="stylesheet" href="/static/dist/fontawesome/css/all.min.css"/>
|
||||||
<!-- Tailwind CSS with Flowbite -->
|
<!-- Application assets -->
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<link rel="stylesheet" href="/static/dist/app.css"/>
|
||||||
<link href="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.2.1/flowbite.min.css" rel="stylesheet"/>
|
<link rel="stylesheet" href="https://rsms.me/inter/inter.css"/>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/flowbite/2.2.1/flowbite.min.js"></script>
|
|
||||||
<script src="/static/js/app.js"></script>
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"/>
|
|
||||||
<link rel="stylesheet" href="/static/css/app.css"/>
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
tailwind.config = {
|
// Immediate theme application
|
||||||
darkMode: 'class',
|
// This script runs before the DOM is fully loaded
|
||||||
theme: {
|
(function() {
|
||||||
extend: {
|
const isDark = localStorage.theme === 'dark' ||
|
||||||
colors: {
|
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
primary: {
|
|
||||||
50: '#f0f9ff',
|
if (isDark) {
|
||||||
100: '#e0f2fe',
|
document.documentElement.classList.add('dark');
|
||||||
200: '#bae6fd',
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
300: '#7dd3fc',
|
document.body.classList.add('dark');
|
||||||
400: '#38bdf8',
|
|
||||||
500: '#0ea5e9',
|
// Apply dark theme to containers
|
||||||
600: '#0284c7',
|
const containers = ['jobs-container', 'configs-container'];
|
||||||
700: '#0369a1',
|
containers.forEach(function(id) {
|
||||||
800: '#075985',
|
const container = document.getElementById(id);
|
||||||
900: '#0c4a6e',
|
if (container) {
|
||||||
950: '#082f49',
|
container.classList.add('dark');
|
||||||
|
container.style.backgroundColor = '#111827';
|
||||||
}
|
}
|
||||||
},
|
});
|
||||||
fontFamily: {
|
});
|
||||||
sans: ['Inter var', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'sans-serif'],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})();
|
||||||
</script>
|
</script>
|
||||||
<style>
|
<style>
|
||||||
html, body {
|
html, body {
|
||||||
@@ -146,32 +140,6 @@ templ LayoutWithContext(title string, ctx context.Context) {
|
|||||||
background-color: #1f2937 !important;
|
background-color: #1f2937 !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<link rel="stylesheet" href="https://rsms.me/inter/inter.css"/>
|
|
||||||
<script>
|
|
||||||
// Immediate theme application
|
|
||||||
// This script runs before the DOM is fully loaded
|
|
||||||
(function() {
|
|
||||||
const isDark = localStorage.theme === 'dark' ||
|
|
||||||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
||||||
|
|
||||||
if (isDark) {
|
|
||||||
document.documentElement.classList.add('dark');
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
document.body.classList.add('dark');
|
|
||||||
|
|
||||||
// Apply dark theme to containers
|
|
||||||
const containers = ['jobs-container', 'configs-container'];
|
|
||||||
containers.forEach(function(id) {
|
|
||||||
const container = document.getElementById(id);
|
|
||||||
if (container) {
|
|
||||||
container.classList.add('dark');
|
|
||||||
container.style.backgroundColor = '#111827';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
<body class="min-h-full bg-gray-50 dark:bg-gray-900" style="min-height: 100vh; display: flex; flex-direction: column;">
|
<body class="min-h-full bg-gray-50 dark:bg-gray-900" style="min-height: 100vh; display: flex; flex-direction: column;">
|
||||||
if isLoggedIn(ctx) {
|
if isLoggedIn(ctx) {
|
||||||
@@ -269,20 +237,100 @@ templ LayoutWithContext(title string, ctx context.Context) {
|
|||||||
<button data-drawer-target="mobile-menu" data-drawer-toggle="mobile-menu" class="p-2 text-gray-500 rounded-lg hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700">
|
<button data-drawer-target="mobile-menu" data-drawer-toggle="mobile-menu" class="p-2 text-gray-500 rounded-lg hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700">
|
||||||
<i class="fas fa-bars text-lg"></i>
|
<i class="fas fa-bars text-lg"></i>
|
||||||
</button>
|
</button>
|
||||||
<!-- User menu (same as desktop) -->
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Add this drawer element for mobile menu -->
|
||||||
|
<div id="mobile-menu" class="fixed top-0 left-0 z-40 h-screen p-4 overflow-y-auto transition-transform -translate-x-full bg-white w-64 dark:bg-gray-800" tabindex="-1">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<a href="/" class="flex items-center text-xl font-bold text-primary-600 dark:text-primary-400">
|
||||||
|
<i class="fas fa-exchange-alt mr-2"></i>
|
||||||
|
GoMFT
|
||||||
|
</a>
|
||||||
|
<button type="button" data-drawer-hide="mobile-menu" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm p-1.5 inline-flex items-center dark:hover:bg-gray-600 dark:hover:text-white">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Copy the same navigation items from the desktop sidebar -->
|
||||||
|
<nav class="mt-5">
|
||||||
|
<div class="space-y-1">
|
||||||
|
<!-- Copy the same links from the desktop sidebar -->
|
||||||
|
<a href="/dashboard" class="group flex items-center px-2 py-2 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700">
|
||||||
|
<i class="fas fa-tachometer-alt mr-3 text-gray-500 dark:text-gray-400"></i>
|
||||||
|
Dashboard
|
||||||
|
</a>
|
||||||
|
<a href="/configs" class="group flex items-center px-2 py-2 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700">
|
||||||
|
<i class="fas fa-cogs mr-3 text-gray-500 dark:text-gray-400"></i>
|
||||||
|
Configs
|
||||||
|
</a>
|
||||||
|
<a href="/jobs" class="group flex items-center px-2 py-2 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700">
|
||||||
|
<i class="fas fa-tasks mr-3 text-gray-500 dark:text-gray-400"></i>
|
||||||
|
Jobs
|
||||||
|
</a>
|
||||||
|
<a href="/history" class="group flex items-center px-2 py-2 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700">
|
||||||
|
<i class="fas fa-history mr-3 text-gray-500 dark:text-gray-400"></i>
|
||||||
|
History
|
||||||
|
</a>
|
||||||
|
<a href="/files" class="group flex items-center px-2 py-2 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700">
|
||||||
|
<i class="fas fa-file-alt mr-3 text-gray-500 dark:text-gray-400"></i>
|
||||||
|
Files
|
||||||
|
</a>
|
||||||
|
if isAdmin(ctx) {
|
||||||
|
<p class="px-3 text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">
|
||||||
|
Administration
|
||||||
|
</p>
|
||||||
|
<a href="/admin/users" class="group flex items-center px-2 py-2 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700">
|
||||||
|
<i class="fas fa-users w-4 h-4 mr-2 text-gray-500 dark:text-gray-400"></i>
|
||||||
|
Users
|
||||||
|
</a>
|
||||||
|
<a href="/admin/roles" class="group flex items-center px-2 py-2 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700">
|
||||||
|
<i class="fas fa-user-shield w-4 h-4 mr-2 text-gray-500 dark:text-gray-400"></i>
|
||||||
|
Roles
|
||||||
|
</a>
|
||||||
|
<a href="/admin/audit" class="group flex items-center px-2 py-2 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700">
|
||||||
|
<i class="fas fa-clipboard-list w-4 h-4 mr-2 text-gray-500 dark:text-gray-400"></i>
|
||||||
|
Audit Logs
|
||||||
|
</a>
|
||||||
|
<a href="/admin/database" class="group flex items-center px-2 py-2 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700">
|
||||||
|
<i class="fas fa-database w-4 h-4 mr-2 text-gray-500 dark:text-gray-400"></i>
|
||||||
|
Database Tools
|
||||||
|
</a>
|
||||||
|
// Settings Dropdown
|
||||||
|
<button type="button" class="flex items-center w-full p-2 text-base text-gray-900 transition duration-75 rounded-lg group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700" aria-controls="dropdown-settings" data-collapse-toggle="dropdown-settings">
|
||||||
|
<i class="fas fa-cog w-4 h-4 mr-2 text-gray-500 dark:text-gray-400"></i>
|
||||||
|
<span class="flex-1 ms-3 text-left rtl:text-right whitespace-nowrap">Settings</span>
|
||||||
|
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 10 6">
|
||||||
|
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 4 4 4-4"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<ul id="dropdown-settings" class="hidden py-2 space-y-2">
|
||||||
|
// <li>
|
||||||
|
// <a href="#" class="flex items-center w-full p-2 text-gray-900 transition duration-75 rounded-lg pl-11 group hover:bg-gray-100 dark:text-white dark:hover:bg-gray-700">General (Coming Soon)</a>
|
||||||
|
// </li>
|
||||||
|
<li>
|
||||||
|
<a href="/admin/settings/auth-providers" class="group flex items-center px-2 py-2 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700">
|
||||||
|
<i class="fas fa-user-lock w-4 h-4 mr-2 text-gray-500 dark:text-gray-400"></i>
|
||||||
|
Authentication Providers
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="/admin/settings/notifications" class="group flex items-center px-2 py-2 text-sm font-medium rounded-md text-gray-700 hover:bg-gray-100 dark:text-gray-300 dark:hover:bg-gray-700">
|
||||||
|
<i class="fas fa-bell w-4 h-4 mr-2 text-gray-500 dark:text-gray-400"></i>
|
||||||
|
Notification Services
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<div class="md:pl-64 flex flex-col flex-1 w-full bg-gray-50 dark:bg-gray-900">
|
<div class="md:pl-64 flex flex-col flex-1 w-full bg-gray-50 dark:bg-gray-900">
|
||||||
<!-- Main header -->
|
<!-- Main header -->
|
||||||
<header class="bg-white dark:bg-gray-800 shadow-sm">
|
<header class="bg-white dark:bg-gray-800 shadow-sm">
|
||||||
<div class="flex items-center justify-between px-4 py-3 sm:px-6 lg:px-8">
|
<div class="flex items-center justify-between px-4 py-3 sm:px-6 lg:px-8">
|
||||||
<div class="flex items-center space-x-3">
|
<div class="flex items-center space-x-3">
|
||||||
<button data-drawer-target="desktop-menu" data-drawer-toggle="desktop-menu" class="md:hidden p-2 text-gray-500 rounded-lg hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700">
|
|
||||||
<i class="fas fa-bars text-lg"></i>
|
|
||||||
</button>
|
|
||||||
<h1 class="text-lg font-semibold text-gray-900 dark:text-white">{ title }</h1>
|
<h1 class="text-lg font-semibold text-gray-900 dark:text-white">{ title }</h1>
|
||||||
</div>
|
</div>
|
||||||
<!-- Theme toggle and user menu -->
|
<!-- Theme toggle and user menu -->
|
||||||
@@ -447,40 +495,12 @@ templ LayoutWithContext(title string, ctx context.Context) {
|
|||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
<script>
|
<!-- Scripts -->
|
||||||
// Add script to ensure dark mode is properly applied
|
<!-- Alpine.js and dependencies -->
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
<script defer src="/static/dist/vendor.js"></script>
|
||||||
// Apply body dark class when theme changes
|
<!-- Application scripts -->
|
||||||
const isDark = document.documentElement.classList.contains('dark');
|
<script defer src="/static/dist/app.js"></script>
|
||||||
if (isDark) {
|
<script defer src="/static/dist/init.js"></script>
|
||||||
document.body.classList.add('dark');
|
|
||||||
|
|
||||||
// Also apply to containers
|
|
||||||
const jobsContainer = document.getElementById('jobs-container');
|
|
||||||
const configsContainer = document.getElementById('configs-container');
|
|
||||||
|
|
||||||
if (jobsContainer) jobsContainer.classList.add('dark');
|
|
||||||
if (configsContainer) configsContainer.classList.add('dark');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialize admin dropdown toggle if available
|
|
||||||
const adminDropdownToggle = document.querySelector('[data-collapse-toggle="dropdown-settings"]');
|
|
||||||
const adminDropdown = document.getElementById('dropdown-settings');
|
|
||||||
|
|
||||||
if (adminDropdownToggle && adminDropdown) {
|
|
||||||
// Check if we should show the dropdown (if current page is under admin section)
|
|
||||||
const currentPath = window.location.pathname;
|
|
||||||
if (currentPath.startsWith('/admin')) {
|
|
||||||
adminDropdown.classList.remove('hidden');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add click event listener
|
|
||||||
adminDropdownToggle.addEventListener('click', function() {
|
|
||||||
adminDropdown.classList.toggle('hidden');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import (
|
|||||||
|
|
||||||
// Dialog component for confirmation dialogs using Flowbite modal
|
// Dialog component for confirmation dialogs using Flowbite modal
|
||||||
templ NotificationDialog(id string, title string, message string, confirmClass string, confirmText string, action string, serviceID uint, serviceName string) {
|
templ NotificationDialog(id string, title string, message string, confirmClass string, confirmText string, action string, serviceID uint, serviceName string) {
|
||||||
<div id={ id } tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full bg-gray-900/50 dark:bg-gray-900/80 backdrop-blur-sm">
|
<div id={ id } tabindex="-1" aria-hidden="true" class="hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div id={ fmt.Sprintf("%s-backdrop", id) } class="fixed inset-0 bg-gray-900/50 dark:bg-gray-900/80 backdrop-blur-sm"></div>
|
||||||
|
<!-- Modal content -->
|
||||||
<div class="relative p-4 w-full max-w-md max-h-full mx-auto">
|
<div class="relative p-4 w-full max-w-md max-h-full mx-auto">
|
||||||
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
<div class="p-6 text-center">
|
<div class="p-6 text-center">
|
||||||
@@ -28,7 +31,7 @@ templ NotificationDialog(id string, title string, message string, confirmClass s
|
|||||||
onclick={ triggerServiceDelete(id, serviceID, serviceName) }>
|
onclick={ triggerServiceDelete(id, serviceID, serviceName) }>
|
||||||
{ confirmText }
|
{ confirmText }
|
||||||
</button>
|
</button>
|
||||||
<button type="button" data-modal-hide={ id } class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
<button type="button" onclick={ closeModal(id) } class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -37,16 +40,6 @@ templ NotificationDialog(id string, title string, message string, confirmClass s
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
script hideNotificationDialog(id string) {
|
|
||||||
document.getElementById(id).classList.add("hidden");
|
|
||||||
document.getElementById(id).classList.remove("flex");
|
|
||||||
}
|
|
||||||
|
|
||||||
script showNotificationDialog(id string) {
|
|
||||||
document.getElementById(id).classList.remove("hidden");
|
|
||||||
document.getElementById(id).classList.add("flex");
|
|
||||||
}
|
|
||||||
|
|
||||||
script triggerServiceDelete(dialogId string, serviceID uint, serviceName string) {
|
script triggerServiceDelete(dialogId string, serviceID uint, serviceName string) {
|
||||||
// Hide the dialog
|
// Hide the dialog
|
||||||
document.getElementById(dialogId).classList.add("hidden");
|
document.getElementById(dialogId).classList.add("hidden");
|
||||||
@@ -144,8 +137,6 @@ templ Notifications(ctx context.Context, data SettingsNotificationsData) {
|
|||||||
|
|
||||||
// Track all HTMX events for debugging
|
// Track all HTMX events for debugging
|
||||||
document.addEventListener('htmx:beforeRequest', function(event) {
|
document.addEventListener('htmx:beforeRequest', function(event) {
|
||||||
console.log("HTMX before request:", event.detail);
|
|
||||||
|
|
||||||
// Check if this is a DELETE request for a notification service
|
// Check if this is a DELETE request for a notification service
|
||||||
const path = event.detail.path;
|
const path = event.detail.path;
|
||||||
const method = event.detail.verb;
|
const method = event.detail.verb;
|
||||||
@@ -162,8 +153,6 @@ templ Notifications(ctx context.Context, data SettingsNotificationsData) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener('htmx:afterRequest', function(event) {
|
document.addEventListener('htmx:afterRequest', function(event) {
|
||||||
console.log("HTMX after request:", event.detail);
|
|
||||||
|
|
||||||
// Check for notification service deletion multiple ways
|
// Check for notification service deletion multiple ways
|
||||||
const isDeleteRequest =
|
const isDeleteRequest =
|
||||||
// Check global flag from the triggerServiceDelete function
|
// Check global flag from the triggerServiceDelete function
|
||||||
@@ -369,7 +358,7 @@ templ Notifications(ctx context.Context, data SettingsNotificationsData) {
|
|||||||
)
|
)
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onclick={ showNotificationDialog(fmt.Sprintf("delete-notification-dialog-%d", service.ID)) }
|
onclick={ showModal(fmt.Sprintf("delete-notification-dialog-%d", service.ID)) }
|
||||||
class="text-red-500 bg-white focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 rounded-lg text-sm p-2 dark:bg-gray-800 dark:text-red-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
|
class="text-red-500 bg-white focus:outline-none hover:bg-gray-100 focus:ring-4 focus:ring-gray-200 rounded-lg text-sm p-2 dark:bg-gray-800 dark:text-red-400 dark:hover:bg-gray-700 dark:hover:text-white dark:focus:ring-gray-700"
|
||||||
>
|
>
|
||||||
<i class="fas fa-trash-alt"></i>
|
<i class="fas fa-trash-alt"></i>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ templ FTPDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_host" name="dest_host" x-model="destinationHost" x-bind:required="requiresDestination"
|
<input type="text" id="dest_host" name="dest_host" x-model="destHost" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="ftp.example.com" />
|
placeholder="ftp.example.com" />
|
||||||
</div>
|
</div>
|
||||||
@@ -21,7 +21,7 @@ templ FTPDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-network-wired text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-network-wired text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="number" id="dest_port" name="dest_port" x-model="destinationPort"
|
<input type="number" id="dest_port" name="dest_port" x-model="destPort"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="21" min="1" max="65535" />
|
placeholder="21" min="1" max="65535" />
|
||||||
</div>
|
</div>
|
||||||
@@ -34,7 +34,7 @@ templ FTPDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-user text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-user text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_user" name="dest_user" x-model="destinationUser" x-bind:required="requiresDestination"
|
<input type="text" id="dest_user" name="dest_user" x-model="destUser" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="username" />
|
placeholder="username" />
|
||||||
</div>
|
</div>
|
||||||
@@ -47,7 +47,7 @@ templ FTPDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="dest_password" name="dest_password" x-model="destinationPassword" x-bind:required="requiresDestination"
|
<input type="password" id="dest_password" name="dest_password" x-model="destPassword" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="Enter password" />
|
placeholder="Enter password" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ templ GoogleDriveDestinationForm() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div x-show="!useBuiltinAuthDestination">
|
<div x-show="!useBuiltinAuthDest">
|
||||||
<div class="p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-yellow-900/30 dark:text-yellow-300" role="alert">
|
<div class="p-4 mb-4 text-sm text-yellow-800 rounded-lg bg-yellow-50 dark:bg-yellow-900/30 dark:text-yellow-300" role="alert">
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<i class="fas fa-exclamation-triangle mr-2 flex-shrink-0"></i>
|
<i class="fas fa-exclamation-triangle mr-2 flex-shrink-0"></i>
|
||||||
@@ -37,9 +37,9 @@ templ GoogleDriveDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-id-card text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-id-card text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_client_id" name="dest_client_id" x-model="destinationClientId"
|
<input type="text" id="dest_client_id" name="dest_client_id" x-model="destClientId"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="Your Google API Client ID" x-bind:required="!useBuiltinAuthDestination" />
|
placeholder="Your Google API Client ID" x-bind:required="!useBuiltinAuthDest" />
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">OAuth 2.0 Client ID from Google Cloud Console</p>
|
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">OAuth 2.0 Client ID from Google Cloud Console</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -50,9 +50,9 @@ templ GoogleDriveDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-key text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-key text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="dest_client_secret" name="dest_client_secret" x-model="destinationClientSecret"
|
<input type="password" id="dest_client_secret" name="dest_client_secret" x-model="destClientSecret"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="Your Google API Client Secret" x-bind:required="!useBuiltinAuthDestination" />
|
placeholder="Your Google API Client Secret" x-bind:required="!useBuiltinAuthDest" />
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">OAuth 2.0 Client Secret from Google Cloud Console</p>
|
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400">OAuth 2.0 Client Secret from Google Cloud Console</p>
|
||||||
</div>
|
</div>
|
||||||
@@ -79,7 +79,7 @@ templ GoogleDriveDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-fingerprint text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-fingerprint text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_drive_id" name="dest_drive_id" x-model="destinationDriveId"
|
<input type="text" id="dest_drive_id" name="dest_drive_id" x-model="destDriveId"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="Specific Google Drive ID" />
|
placeholder="Specific Google Drive ID" />
|
||||||
</div>
|
</div>
|
||||||
@@ -94,7 +94,7 @@ templ GoogleDriveDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-users text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-users text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_team_drive" name="dest_team_drive" x-model="destinationTeamDrive"
|
<input type="text" id="dest_team_drive" name="dest_team_drive" x-model="destTeamDrive"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="Team Drive ID" />
|
placeholder="Team Drive ID" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package destination
|
package destination
|
||||||
|
|
||||||
templ GooglePhotosDestinationForm() {
|
templ GooglePhotosDestinationForm() {
|
||||||
<div class="space-y-6 mt-4" x-init="$watch('useBuiltinAuthDestination', value => {
|
<div class="space-y-6 mt-4" x-init="$watch('useBuiltinAuthDest', value => {
|
||||||
if(value) {
|
if(value) {
|
||||||
destinationClientId = '';
|
destClientId = '';
|
||||||
destinationClientSecret = '';
|
destClientSecret = '';
|
||||||
}
|
}
|
||||||
})">
|
})">
|
||||||
<div class="p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-blue-900/30 dark:text-blue-300" role="alert">
|
<div class="p-4 mb-4 text-sm text-blue-800 rounded-lg bg-blue-50 dark:bg-blue-900/30 dark:text-blue-300" role="alert">
|
||||||
@@ -38,7 +38,7 @@ templ GooglePhotosDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-id-card text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-id-card text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_client_id" name="dest_client_id" x-model="destinationClientId"
|
<input type="text" id="dest_client_id" name="dest_client_id" x-model="destClientId"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
x-bind:disabled="useBuiltinAuthDest"
|
x-bind:disabled="useBuiltinAuthDest"
|
||||||
placeholder="Google Photos OAuth Client ID" />
|
placeholder="Google Photos OAuth Client ID" />
|
||||||
@@ -57,7 +57,7 @@ templ GooglePhotosDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-key text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-key text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="dest_client_secret" name="dest_client_secret" x-model="destinationClientSecret"
|
<input type="password" id="dest_client_secret" name="dest_client_secret" x-model="destClientSecret"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
x-bind:disabled="useBuiltinAuthDest"
|
x-bind:disabled="useBuiltinAuthDest"
|
||||||
placeholder="Google Photos OAuth Client Secret" />
|
placeholder="Google Photos OAuth Client Secret" />
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ templ MinIODestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_endpoint" name="dest_endpoint" x-model="destinationEndpoint" x-bind:required="requiresDestination"
|
<input type="text" id="dest_endpoint" name="dest_endpoint" x-model="destEndpoint" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="https://minio.example.com" />
|
placeholder="https://minio.example.com" />
|
||||||
</div>
|
</div>
|
||||||
@@ -30,7 +30,7 @@ templ MinIODestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-archive text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-archive text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_bucket" name="dest_bucket" x-model="destinationBucket" x-bind:required="requiresDestination"
|
<input type="text" id="dest_bucket" name="dest_bucket" x-model="destBucket" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="my-bucket" />
|
placeholder="my-bucket" />
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,7 @@ templ MinIODestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-key text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-key text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_access_key" name="dest_access_key" x-model="destinationAccessKey" x-bind:required="requiresDestination"
|
<input type="text" id="dest_access_key" name="dest_access_key" x-model="destAccessKey" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="minioadmin" />
|
placeholder="minioadmin" />
|
||||||
</div>
|
</div>
|
||||||
@@ -60,7 +60,7 @@ templ MinIODestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="dest_secret_key" name="dest_secret_key" x-model="destinationSecretKey" x-bind:required="requiresDestination"
|
<input type="password" id="dest_secret_key" name="dest_secret_key" x-model="destSecretKey" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="minioadmin" />
|
placeholder="minioadmin" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ templ NextCloudDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-cloud text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-cloud text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_endpoint" name="dest_endpoint" x-model="destinationEndpoint" x-bind:required="requiresDestination"
|
<input type="text" id="dest_endpoint" name="dest_endpoint" x-model="destEndpoint" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="https://nextcloud.example.com" />
|
placeholder="https://nextcloud.example.com" />
|
||||||
</div>
|
</div>
|
||||||
@@ -30,7 +30,7 @@ templ NextCloudDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-user text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-user text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_user" name="dest_user" x-model="destinationUser" x-bind:required="requiresDestination"
|
<input type="text" id="dest_user" name="dest_user" x-model="destUser" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="nextcloud_username" />
|
placeholder="nextcloud_username" />
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,7 @@ templ NextCloudDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="dest_password" name="dest_password" x-model="destinationPassword" x-bind:required="requiresDestination"
|
<input type="password" id="dest_password" name="dest_password" x-model="destPassword" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="NextCloud password" />
|
placeholder="NextCloud password" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ templ S3DestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-globe text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-globe text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_region" name="dest_region" x-model="destinationRegion" x-bind:required="requiresDestination"
|
<input type="text" id="dest_region" name="dest_region" x-model="destRegion" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="us-east-1" />
|
placeholder="us-east-1" />
|
||||||
</div>
|
</div>
|
||||||
@@ -30,7 +30,7 @@ templ S3DestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-archive text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-archive text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_bucket" name="dest_bucket" x-model="destinationBucket" x-bind:required="requiresDestination"
|
<input type="text" id="dest_bucket" name="dest_bucket" x-model="destBucket" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="my-bucket" />
|
placeholder="my-bucket" />
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,7 @@ templ S3DestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-key text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-key text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_access_key" name="dest_access_key" x-model="destinationAccessKey" x-bind:required="requiresDestination"
|
<input type="text" id="dest_access_key" name="dest_access_key" x-model="destAccessKey" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="AKIAIOSFODNN7EXAMPLE" />
|
placeholder="AKIAIOSFODNN7EXAMPLE" />
|
||||||
</div>
|
</div>
|
||||||
@@ -60,7 +60,7 @@ templ S3DestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="dest_secret_key" name="dest_secret_key" x-model="destinationSecretKey" x-bind:required="requiresDestination"
|
<input type="password" id="dest_secret_key" name="dest_secret_key" x-model="destSecretKey" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" />
|
placeholder="wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ templ SFTPDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_host" name="dest_host" x-model="destinationHost" x-bind:required="requiresDestination"
|
<input type="text" id="dest_host" name="dest_host" x-model="destHost" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="sftp.example.com" />
|
placeholder="sftp.example.com" />
|
||||||
</div>
|
</div>
|
||||||
@@ -21,7 +21,7 @@ templ SFTPDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-network-wired text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-network-wired text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="number" id="dest_port" name="dest_port" x-model="destinationPort"
|
<input type="number" id="dest_port" name="dest_port" x-model="destPort"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="22" min="1" max="65535" />
|
placeholder="22" min="1" max="65535" />
|
||||||
</div>
|
</div>
|
||||||
@@ -34,7 +34,7 @@ templ SFTPDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-user text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-user text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_user" name="dest_user" x-model="destinationUser" x-bind:required="requiresDestination"
|
<input type="text" id="dest_user" name="dest_user" x-model="destUser" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="username" />
|
placeholder="username" />
|
||||||
</div>
|
</div>
|
||||||
@@ -61,7 +61,7 @@ templ SFTPDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="dest_password" name="dest_password" x-model="destinationPassword"
|
<input type="password" id="dest_password" name="dest_password" x-model="destPassword"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="Enter password" x-bind:required="destAuthType === 'password'" />
|
placeholder="Enter password" x-bind:required="destAuthType === 'password'" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ templ SMBDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-server text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_host" name="dest_host" x-model="destinationHost" x-bind:required="requiresDestination"
|
<input type="text" id="dest_host" name="dest_host" x-model="destHost" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="192.168.1.100 or server.example.com" />
|
placeholder="192.168.1.100 or server.example.com" />
|
||||||
</div>
|
</div>
|
||||||
@@ -30,7 +30,7 @@ templ SMBDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-share-alt text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-share-alt text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_share" name="dest_share" x-model="destinationShare" x-bind:required="requiresDestination"
|
<input type="text" id="dest_share" name="dest_share" x-model="destShare" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="Documents" />
|
placeholder="Documents" />
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,7 @@ templ SMBDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-sitemap text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-sitemap text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_domain" name="dest_domain" x-model="destinationDomain"
|
<input type="text" id="dest_domain" name="dest_domain" x-model="destDomain"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="WORKGROUP or DOMAIN" />
|
placeholder="WORKGROUP or DOMAIN" />
|
||||||
</div>
|
</div>
|
||||||
@@ -60,7 +60,7 @@ templ SMBDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-user text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-user text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_user" name="dest_user" x-model="destinationUser" x-bind:required="requiresDestination"
|
<input type="text" id="dest_user" name="dest_user" x-model="destUser" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="smb_username" />
|
placeholder="smb_username" />
|
||||||
</div>
|
</div>
|
||||||
@@ -75,7 +75,7 @@ templ SMBDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="dest_password" name="dest_password" x-model="destinationPassword" x-bind:required="requiresDestination"
|
<input type="password" id="dest_password" name="dest_password" x-model="destPassword" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="SMB password" />
|
placeholder="SMB password" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ templ WebDAVDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-globe text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-globe text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_endpoint" name="dest_endpoint" x-model="destinationEndpoint" x-bind:required="requiresDestination"
|
<input type="text" id="dest_endpoint" name="dest_endpoint" x-model="destEndpoint" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="https://webdav.example.com" />
|
placeholder="https://webdav.example.com" />
|
||||||
</div>
|
</div>
|
||||||
@@ -30,7 +30,7 @@ templ WebDAVDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-user text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-user text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="text" id="dest_user" name="dest_user" x-model="destinationUser" x-bind:required="requiresDestination"
|
<input type="text" id="dest_user" name="dest_user" x-model="destUser" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="webdav_username" />
|
placeholder="webdav_username" />
|
||||||
</div>
|
</div>
|
||||||
@@ -45,7 +45,7 @@ templ WebDAVDestinationForm() {
|
|||||||
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
<div class="absolute inset-y-0 start-0 flex items-center ps-3.5 pointer-events-none">
|
||||||
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
<i class="fas fa-lock text-gray-400 dark:text-gray-500"></i>
|
||||||
</div>
|
</div>
|
||||||
<input type="password" id="dest_password" name="dest_password" x-model="destinationPassword" x-bind:required="requiresDestination"
|
<input type="password" id="dest_password" name="dest_password" x-model="destPassword" x-bind:required="requiresDestination"
|
||||||
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
class="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-500 focus:border-blue-500 block w-full ps-10 p-2.5 dark:bg-gray-700 dark:border-gray-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
|
||||||
placeholder="WebDAV password" />
|
placeholder="WebDAV password" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -118,8 +118,6 @@ templ UserManagementContent(data UserManagementData) {
|
|||||||
|
|
||||||
// Track user operations in HTMX events
|
// Track user operations in HTMX events
|
||||||
document.addEventListener('htmx:beforeRequest', function(event) {
|
document.addEventListener('htmx:beforeRequest', function(event) {
|
||||||
console.log("HTMX before request:", event.detail);
|
|
||||||
|
|
||||||
// Check if this is a DELETE request for a user
|
// Check if this is a DELETE request for a user
|
||||||
const path = event.detail.path;
|
const path = event.detail.path;
|
||||||
const method = event.detail.verb;
|
const method = event.detail.verb;
|
||||||
@@ -421,7 +419,7 @@ templ userList(users []db.User) {
|
|||||||
class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-3 py-1.5 text-center inline-flex items-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800"
|
class="text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-3 py-1.5 text-center inline-flex items-center dark:bg-red-600 dark:hover:bg-red-700 dark:focus:ring-red-800"
|
||||||
data-user-email={ user.Email }
|
data-user-email={ user.Email }
|
||||||
data-user-id={ strconv.Itoa(int(user.ID)) }
|
data-user-id={ strconv.Itoa(int(user.ID)) }
|
||||||
onclick={ showUserDialog(fmt.Sprintf("delete-user-dialog-%d", user.ID)) }
|
onclick={ showModal(fmt.Sprintf("delete-user-dialog-%d", user.ID)) }
|
||||||
>
|
>
|
||||||
<i class="fas fa-trash-alt w-3.5 h-3.5 mr-1.5"></i> Delete
|
<i class="fas fa-trash-alt w-3.5 h-3.5 mr-1.5"></i> Delete
|
||||||
</button>
|
</button>
|
||||||
@@ -634,7 +632,10 @@ func hasRole(user *db.User, roleID uint) bool {
|
|||||||
|
|
||||||
// UserDialog for confirmation actions
|
// UserDialog for confirmation actions
|
||||||
templ UserDialog(id string, title string, message string, confirmClass string, confirmText string, action string, userID uint, userEmail string) {
|
templ UserDialog(id string, title string, message string, confirmClass string, confirmText string, action string, userID uint, userEmail string) {
|
||||||
<div id={ id } tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full bg-gray-900/50 dark:bg-gray-900/80 backdrop-blur-sm">
|
<div id={ id } tabindex="-1" aria-hidden="true" class="hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full">
|
||||||
|
<!-- Backdrop -->
|
||||||
|
<div id={ fmt.Sprintf("%s-backdrop", id) } class="fixed inset-0 bg-gray-900/50 dark:bg-gray-900/80 backdrop-blur-sm"></div>
|
||||||
|
<!-- Modal content -->
|
||||||
<div class="relative p-4 w-full max-w-md max-h-full mx-auto">
|
<div class="relative p-4 w-full max-w-md max-h-full mx-auto">
|
||||||
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
<div class="relative bg-white rounded-lg shadow dark:bg-gray-700">
|
||||||
<div class="p-6 text-center">
|
<div class="p-6 text-center">
|
||||||
@@ -654,7 +655,7 @@ templ UserDialog(id string, title string, message string, confirmClass string, c
|
|||||||
onclick={ triggerUserDelete(id, userID, userEmail) }>
|
onclick={ triggerUserDelete(id, userID, userEmail) }>
|
||||||
{ confirmText }
|
{ confirmText }
|
||||||
</button>
|
</button>
|
||||||
<button type="button" data-modal-hide={ id } class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
<button type="button" onclick={ closeModal(id) } class="text-gray-500 bg-white hover:bg-gray-100 focus:ring-4 focus:outline-none focus:ring-gray-200 rounded-lg border border-gray-200 text-sm font-medium px-5 py-2.5 hover:text-gray-900 focus:z-10 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-500 dark:hover:text-white dark:hover:bg-gray-600 dark:focus:ring-gray-600">
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -663,16 +664,6 @@ templ UserDialog(id string, title string, message string, confirmClass string, c
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
script hideUserDialog(id string) {
|
|
||||||
document.getElementById(id).classList.add("hidden");
|
|
||||||
document.getElementById(id).classList.remove("flex");
|
|
||||||
}
|
|
||||||
|
|
||||||
script showUserDialog(id string) {
|
|
||||||
document.getElementById(id).classList.remove("hidden");
|
|
||||||
document.getElementById(id).classList.add("flex");
|
|
||||||
}
|
|
||||||
|
|
||||||
script triggerUserDelete(dialogId string, userID uint, userEmail string) {
|
script triggerUserDelete(dialogId string, userID uint, userEmail string) {
|
||||||
// Hide the dialog
|
// Hide the dialog
|
||||||
document.getElementById(dialogId).classList.add("hidden");
|
document.getElementById(dialogId).classList.add("hidden");
|
||||||
|
|||||||
+7
-4
@@ -653,7 +653,7 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
|
|||||||
case "webdav":
|
case "webdav":
|
||||||
args := []string{
|
args := []string{
|
||||||
"config", "create", sourceName, "webdav",
|
"config", "create", sourceName, "webdav",
|
||||||
"url", config.SourceHost,
|
"url", config.SourceEndpoint,
|
||||||
"user", config.SourceUser,
|
"user", config.SourceUser,
|
||||||
"pass", config.SourcePassword,
|
"pass", config.SourcePassword,
|
||||||
"--non-interactive",
|
"--non-interactive",
|
||||||
@@ -668,7 +668,7 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
|
|||||||
case "nextcloud":
|
case "nextcloud":
|
||||||
args := []string{
|
args := []string{
|
||||||
"config", "create", sourceName, "webdav",
|
"config", "create", sourceName, "webdav",
|
||||||
"url", config.SourceHost,
|
"url", config.SourceEndpoint,
|
||||||
"user", config.SourceUser,
|
"user", config.SourceUser,
|
||||||
"pass", config.SourcePassword,
|
"pass", config.SourcePassword,
|
||||||
"vendor", "nextcloud",
|
"vendor", "nextcloud",
|
||||||
@@ -760,6 +760,9 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
destName := fmt.Sprintf("dest_%d", config.ID)
|
destName := fmt.Sprintf("dest_%d", config.ID)
|
||||||
|
|
||||||
|
fmt.Println("config", config)
|
||||||
|
|
||||||
switch config.DestinationType {
|
switch config.DestinationType {
|
||||||
case "sftp":
|
case "sftp":
|
||||||
args := []string{
|
args := []string{
|
||||||
@@ -875,7 +878,7 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
|
|||||||
case "webdav":
|
case "webdav":
|
||||||
args := []string{
|
args := []string{
|
||||||
"config", "create", destName, "webdav",
|
"config", "create", destName, "webdav",
|
||||||
"url", config.DestHost,
|
"url", config.DestEndpoint,
|
||||||
"user", config.DestUser,
|
"user", config.DestUser,
|
||||||
"pass", config.DestPassword,
|
"pass", config.DestPassword,
|
||||||
"--non-interactive",
|
"--non-interactive",
|
||||||
@@ -890,7 +893,7 @@ func (db *DB) GenerateRcloneConfig(config *TransferConfig) error {
|
|||||||
case "nextcloud":
|
case "nextcloud":
|
||||||
args := []string{
|
args := []string{
|
||||||
"config", "create", destName, "webdav",
|
"config", "create", destName, "webdav",
|
||||||
"url", config.DestHost,
|
"url", config.DestEndpoint,
|
||||||
"user", config.DestUser,
|
"user", config.DestUser,
|
||||||
"pass", config.DestPassword,
|
"pass", config.DestPassword,
|
||||||
"vendor", "nextcloud",
|
"vendor", "nextcloud",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
@@ -23,6 +24,40 @@ import (
|
|||||||
//go:embed static
|
//go:embed static
|
||||||
var staticFiles embed.FS
|
var staticFiles embed.FS
|
||||||
|
|
||||||
|
// setCacheHeaders sets appropriate cache headers based on file type
|
||||||
|
func setCacheHeaders(c *gin.Context, path string) {
|
||||||
|
// Set cache headers based on file type
|
||||||
|
if strings.HasSuffix(path, ".js") || strings.HasSuffix(path, ".css") {
|
||||||
|
c.Header("Cache-Control", "public, max-age=31536000") // Cache for 1 year
|
||||||
|
} else if strings.HasSuffix(path, ".png") || strings.HasSuffix(path, ".jpg") || strings.HasSuffix(path, ".webp") {
|
||||||
|
c.Header("Cache-Control", "public, max-age=86400") // Cache for 1 day
|
||||||
|
} else {
|
||||||
|
c.Header("Cache-Control", "no-cache")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// setContentType sets the correct content type based on file extension
|
||||||
|
func setContentType(c *gin.Context, path string) {
|
||||||
|
switch {
|
||||||
|
case strings.HasSuffix(path, ".js"):
|
||||||
|
c.Header("Content-Type", "application/javascript")
|
||||||
|
case strings.HasSuffix(path, ".css"):
|
||||||
|
c.Header("Content-Type", "text/css")
|
||||||
|
case strings.HasSuffix(path, ".png"):
|
||||||
|
c.Header("Content-Type", "image/png")
|
||||||
|
case strings.HasSuffix(path, ".jpg"), strings.HasSuffix(path, ".jpeg"):
|
||||||
|
c.Header("Content-Type", "image/jpeg")
|
||||||
|
case strings.HasSuffix(path, ".webp"):
|
||||||
|
c.Header("Content-Type", "image/webp")
|
||||||
|
case strings.HasSuffix(path, ".svg"):
|
||||||
|
c.Header("Content-Type", "image/svg+xml")
|
||||||
|
case strings.HasSuffix(path, ".woff2"):
|
||||||
|
c.Header("Content-Type", "font/woff2")
|
||||||
|
case strings.HasSuffix(path, ".woff"):
|
||||||
|
c.Header("Content-Type", "font/woff")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// Set Gin to release mode
|
// Set Gin to release mode
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
@@ -110,12 +145,24 @@ func main() {
|
|||||||
}))
|
}))
|
||||||
router.Use(gin.Recovery())
|
router.Use(gin.Recovery())
|
||||||
|
|
||||||
// Serve embedded static files
|
// Serve embedded static files with proper content types and caching
|
||||||
staticFS, err := fs.Sub(staticFiles, "static")
|
staticFS, err := fs.Sub(staticFiles, "static")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to create sub-filesystem: %v", err)
|
log.Fatalf("Failed to create sub-filesystem: %v", err)
|
||||||
}
|
}
|
||||||
router.StaticFS("/static", http.FS(staticFS))
|
|
||||||
|
// Custom static file handler
|
||||||
|
router.GET("/static/*filepath", func(c *gin.Context) {
|
||||||
|
path := c.Param("filepath")
|
||||||
|
|
||||||
|
// Set content type and cache headers
|
||||||
|
setContentType(c, path)
|
||||||
|
setCacheHeaders(c, path)
|
||||||
|
|
||||||
|
// Serve the file
|
||||||
|
c.FileFromFS(path, http.FS(staticFS))
|
||||||
|
})
|
||||||
|
|
||||||
log.Printf("Embedded static files configured for serving")
|
log.Printf("Embedded static files configured for serving")
|
||||||
|
|
||||||
// Initialize web handlers
|
// Initialize web handlers
|
||||||
|
|||||||
Generated
+1872
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "gomft",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "GoMFT static assets bundling",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"build": "node build.js",
|
||||||
|
"watch": "node build.js --watch",
|
||||||
|
"postinstall": "npm run build"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fortawesome/fontawesome-free": "^6.4.0",
|
||||||
|
"alpinejs": "^3.13.5",
|
||||||
|
"esbuild": "^0.20.1",
|
||||||
|
"flowbite": "^2.2.1",
|
||||||
|
"htmx.org": "^1.9.10",
|
||||||
|
"tailwindcss": "^3.4.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,54 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
/* Custom styles */
|
||||||
|
html, body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
overflow-x: hidden;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fadeIn {
|
||||||
|
animation: fadeIn 0.3s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pb-mobile-nav {
|
||||||
|
padding-bottom: 4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 640px) {
|
||||||
|
.pb-mobile-nav {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode styles */
|
||||||
|
body.dark {
|
||||||
|
background-color: #111827; /* gray-900 */
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .configs-page,
|
||||||
|
body.dark .jobs-page {
|
||||||
|
background-color: #111827 !important; /* gray-900 */
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
#jobs-container.dark, #configs-container.dark {
|
||||||
|
background-color: #111827 !important;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.dark .bg-white {
|
||||||
|
background-color: #1f2937 !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Base styles */
|
/* Base styles */
|
||||||
:root {
|
:root {
|
||||||
--primary-color: #2563eb;
|
--primary-color: #2563eb;
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// Add script to ensure dark mode is properly applied
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Apply body dark class when theme changes
|
||||||
|
const isDark = document.documentElement.classList.contains('dark');
|
||||||
|
if (isDark) {
|
||||||
|
document.body.classList.add('dark');
|
||||||
|
|
||||||
|
// Also apply to containers
|
||||||
|
const jobsContainer = document.getElementById('jobs-container');
|
||||||
|
const configsContainer = document.getElementById('configs-container');
|
||||||
|
|
||||||
|
if (jobsContainer) jobsContainer.classList.add('dark');
|
||||||
|
if (configsContainer) configsContainer.classList.add('dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize admin dropdown toggle if available
|
||||||
|
const adminDropdownToggle = document.querySelector('[data-collapse-toggle="dropdown-settings"]');
|
||||||
|
const adminDropdown = document.getElementById('dropdown-settings');
|
||||||
|
|
||||||
|
if (adminDropdownToggle && adminDropdown) {
|
||||||
|
// Check if we should show the dropdown (if current page is under admin section)
|
||||||
|
const currentPath = window.location.pathname;
|
||||||
|
if (currentPath.startsWith('/admin')) {
|
||||||
|
adminDropdown.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add click event listener
|
||||||
|
adminDropdownToggle.addEventListener('click', function() {
|
||||||
|
adminDropdown.classList.toggle('hidden');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// Import HTMX
|
||||||
|
import 'htmx.org';
|
||||||
|
|
||||||
|
// Import Alpine.js
|
||||||
|
import Alpine from 'alpinejs';
|
||||||
|
window.Alpine = Alpine;
|
||||||
|
|
||||||
|
// Import Flowbite
|
||||||
|
import 'flowbite';
|
||||||
|
import 'flowbite/dist/flowbite.css';
|
||||||
|
|
||||||
|
// Initialize Flowbite components
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Initialize Alpine.js
|
||||||
|
Alpine.start();
|
||||||
|
|
||||||
|
// Initialize Flowbite modals
|
||||||
|
const modalTriggers = document.querySelectorAll('[data-modal-target]');
|
||||||
|
modalTriggers.forEach(trigger => {
|
||||||
|
const targetId = trigger.getAttribute('data-modal-target');
|
||||||
|
const targetModal = document.getElementById(targetId);
|
||||||
|
if (targetModal) {
|
||||||
|
// Show modal
|
||||||
|
trigger.addEventListener('click', () => {
|
||||||
|
targetModal.classList.remove('hidden');
|
||||||
|
targetModal.classList.add('flex');
|
||||||
|
// Add backdrop
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle modal hide buttons
|
||||||
|
const hideButtons = targetModal.querySelectorAll('[data-modal-hide]');
|
||||||
|
hideButtons.forEach(button => {
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
targetModal.classList.add('hidden');
|
||||||
|
targetModal.classList.remove('flex');
|
||||||
|
// Remove backdrop
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle clicking outside the modal
|
||||||
|
targetModal.addEventListener('click', (event) => {
|
||||||
|
if (event.target === targetModal || event.target.classList.contains('fixed')) {
|
||||||
|
targetModal.classList.add('hidden');
|
||||||
|
targetModal.classList.remove('flex');
|
||||||
|
// Remove backdrop
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
export default {
|
||||||
|
content: [
|
||||||
|
"./components/**/*.{html,js,templ,go}",
|
||||||
|
"./node_modules/flowbite/**/*.js"
|
||||||
|
],
|
||||||
|
darkMode: 'class',
|
||||||
|
theme: {
|
||||||
|
extend: {
|
||||||
|
colors: {
|
||||||
|
primary: {
|
||||||
|
50: '#f0f9ff',
|
||||||
|
100: '#e0f2fe',
|
||||||
|
200: '#bae6fd',
|
||||||
|
300: '#7dd3fc',
|
||||||
|
400: '#38bdf8',
|
||||||
|
500: '#0ea5e9',
|
||||||
|
600: '#0284c7',
|
||||||
|
700: '#0369a1',
|
||||||
|
800: '#075985',
|
||||||
|
900: '#0c4a6e',
|
||||||
|
950: '#082f49',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fontFamily: {
|
||||||
|
sans: ['Inter var', 'ui-sans-serif', 'system-ui', '-apple-system', 'BlinkMacSystemFont', 'Segoe UI', 'Roboto', 'Helvetica Neue', 'Arial', 'sans-serif'],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
require('flowbite/plugin')
|
||||||
|
],
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user