Add dedicated pages for viewing module details and configuring module settings. Includes routes, templates, and JS components for module info page showing features, menu items, dependencies, and self-contained module paths. Also adds View/Configure navigation buttons to the platform modules list. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
283 lines
16 KiB
HTML
283 lines
16 KiB
HTML
{# app/templates/admin/platform-modules.html #}
|
|
{% extends "admin/base.html" %}
|
|
{% from 'shared/macros/alerts.html' import alert_dynamic, error_state %}
|
|
{% from 'shared/macros/headers.html' import page_header %}
|
|
|
|
{% block title %}Module Configuration{% endblock %}
|
|
|
|
{% block alpine_data %}adminPlatformModules('{{ platform_code }}'){% endblock %}
|
|
|
|
{% block content %}
|
|
{{ page_header('Module Configuration', back_url='/admin/platforms/' + platform_code) }}
|
|
|
|
{{ alert_dynamic(type='success', title='Success', message_var='successMessage', show_condition='successMessage') }}
|
|
{{ error_state('Error', show_condition='error') }}
|
|
|
|
<!-- Platform Info -->
|
|
<div class="mb-6 p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="flex items-center justify-between">
|
|
<div>
|
|
<h2 class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="platform?.name || 'Loading...'"></h2>
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
Enable or disable feature modules for this platform. Core modules cannot be disabled.
|
|
</p>
|
|
</div>
|
|
<span class="px-3 py-1 text-sm font-medium rounded-full bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200" x-text="platform?.code?.toUpperCase()"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stats Cards -->
|
|
<div class="grid gap-4 mb-6 md:grid-cols-4">
|
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-3 mr-4 text-blue-500 bg-blue-100 rounded-full dark:text-blue-100 dark:bg-blue-500">
|
|
<span x-html="$icon('puzzle', 'w-5 h-5')"></span>
|
|
</div>
|
|
<div>
|
|
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Total Modules</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="moduleConfig?.total || 0"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-3 mr-4 text-green-500 bg-green-100 rounded-full dark:text-green-100 dark:bg-green-500">
|
|
<span x-html="$icon('check-circle', 'w-5 h-5')"></span>
|
|
</div>
|
|
<div>
|
|
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Enabled</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="moduleConfig?.enabled || 0"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-3 mr-4 text-gray-500 bg-gray-100 rounded-full dark:text-gray-100 dark:bg-gray-600">
|
|
<span x-html="$icon('x-circle', 'w-5 h-5')"></span>
|
|
</div>
|
|
<div>
|
|
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Disabled</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="moduleConfig?.disabled || 0"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center p-4 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<div class="p-3 mr-4 text-purple-500 bg-purple-100 rounded-full dark:text-purple-100 dark:bg-purple-500">
|
|
<span x-html="$icon('shield', 'w-5 h-5')"></span>
|
|
</div>
|
|
<div>
|
|
<p class="mb-1 text-sm font-medium text-gray-600 dark:text-gray-400">Core Modules</p>
|
|
<p class="text-lg font-semibold text-gray-700 dark:text-gray-200" x-text="coreModulesCount"></p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Actions -->
|
|
<div class="mb-4 flex items-center justify-between">
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">
|
|
Toggle modules on/off. Dependencies are resolved automatically.
|
|
</p>
|
|
<div class="flex gap-2">
|
|
<button
|
|
@click="enableAll()"
|
|
:disabled="saving"
|
|
class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600"
|
|
>
|
|
<span x-html="$icon('check-circle', 'w-4 h-4 mr-2')"></span>
|
|
Enable All
|
|
</button>
|
|
<button
|
|
@click="disableOptional()"
|
|
:disabled="saving"
|
|
class="inline-flex items-center px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-lg hover:bg-gray-50 dark:bg-gray-700 dark:text-gray-300 dark:border-gray-600 dark:hover:bg-gray-600"
|
|
>
|
|
<span x-html="$icon('x-circle', 'w-4 h-4 mr-2')"></span>
|
|
Core Only
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Loading State -->
|
|
<div x-show="loading" class="flex items-center justify-center py-12 bg-white rounded-lg shadow-xs dark:bg-gray-800">
|
|
<span x-html="$icon('refresh', 'w-8 h-8 animate-spin text-purple-600')"></span>
|
|
<span class="ml-3 text-gray-500 dark:text-gray-400">Loading module configuration...</span>
|
|
</div>
|
|
|
|
<!-- Module Groups -->
|
|
<div x-show="!loading" class="space-y-6">
|
|
<!-- Core Modules -->
|
|
<div class="bg-white rounded-lg shadow-xs dark:bg-gray-800 overflow-hidden">
|
|
<div class="px-4 py-3 bg-purple-50 dark:bg-purple-900/20 border-b border-purple-200 dark:border-purple-800">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<span x-html="$icon('shield', 'w-5 h-5 text-purple-600 dark:text-purple-400 mr-2')"></span>
|
|
<h3 class="text-sm font-semibold text-purple-800 dark:text-purple-200">Core Modules</h3>
|
|
<span class="ml-2 px-2 py-0.5 text-xs font-medium rounded bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-200">
|
|
Always Enabled
|
|
</span>
|
|
</div>
|
|
<span class="text-xs text-purple-600 dark:text-purple-400" x-text="`${coreModulesCount} modules`"></span>
|
|
</div>
|
|
</div>
|
|
<div class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
<template x-for="module in coreModules" :key="module.code">
|
|
<div class="flex items-center justify-between px-4 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/30">
|
|
<div class="flex items-center flex-1">
|
|
<div class="p-2 mr-3 bg-purple-100 dark:bg-purple-900/30 rounded-lg">
|
|
<span x-html="$icon(getModuleIcon(module.code), 'w-5 h-5 text-purple-600 dark:text-purple-400')"></span>
|
|
</div>
|
|
<div class="flex-1">
|
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-200" x-text="module.name"></p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400" x-text="module.description"></p>
|
|
<!-- Features -->
|
|
<div x-show="module.features?.length > 0" class="mt-1 flex flex-wrap gap-1">
|
|
<template x-for="feature in module.features.slice(0, 3)" :key="feature">
|
|
<span class="px-1.5 py-0.5 text-xs rounded bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400" x-text="feature"></span>
|
|
</template>
|
|
<span x-show="module.features?.length > 3" class="text-xs text-gray-400" x-text="`+${module.features.length - 3} more`"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-3 ml-4">
|
|
<!-- View Details Button -->
|
|
<a :href="`/admin/platforms/${platformCode}/modules/${module.code}`"
|
|
class="p-1.5 rounded-lg text-gray-500 hover:text-purple-600 hover:bg-purple-50 dark:text-gray-400 dark:hover:text-purple-400 dark:hover:bg-purple-900/20 transition-colors"
|
|
title="View Details">
|
|
<span x-html="$icon('eye', 'w-4 h-4')"></span>
|
|
</a>
|
|
|
|
<!-- Configure Button (if has config) -->
|
|
<a x-show="hasConfig(module.code)"
|
|
:href="`/admin/platforms/${platformCode}/modules/${module.code}/config`"
|
|
class="p-1.5 rounded-lg text-gray-500 hover:text-blue-600 hover:bg-blue-50 dark:text-gray-400 dark:hover:text-blue-400 dark:hover:bg-blue-900/20 transition-colors"
|
|
title="Configure">
|
|
<span x-html="$icon('cog', 'w-4 h-4')"></span>
|
|
</a>
|
|
|
|
<span class="px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
|
|
Enabled
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Optional Modules -->
|
|
<div class="bg-white rounded-lg shadow-xs dark:bg-gray-800 overflow-hidden">
|
|
<div class="px-4 py-3 bg-gray-50 dark:bg-gray-700/50 border-b border-gray-200 dark:border-gray-700">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center">
|
|
<span x-html="$icon('puzzle', 'w-5 h-5 text-gray-600 dark:text-gray-400 mr-2')"></span>
|
|
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-200">Optional Modules</h3>
|
|
</div>
|
|
<span class="text-xs text-gray-500 dark:text-gray-400" x-text="`${enabledOptionalCount}/${optionalModules.length} enabled`"></span>
|
|
</div>
|
|
</div>
|
|
<div class="divide-y divide-gray-100 dark:divide-gray-700">
|
|
<template x-for="module in optionalModules" :key="module.code">
|
|
<div class="flex items-center justify-between px-4 py-4 hover:bg-gray-50 dark:hover:bg-gray-700/30">
|
|
<div class="flex items-center flex-1">
|
|
<div class="p-2 mr-3 rounded-lg"
|
|
:class="module.is_enabled ? 'bg-green-100 dark:bg-green-900/30' : 'bg-gray-100 dark:bg-gray-700'">
|
|
<span x-html="$icon(getModuleIcon(module.code), 'w-5 h-5')"
|
|
:class="module.is_enabled ? 'text-green-600 dark:text-green-400' : 'text-gray-400'"></span>
|
|
</div>
|
|
<div class="flex-1">
|
|
<div class="flex items-center gap-2">
|
|
<p class="text-sm font-medium text-gray-700 dark:text-gray-200" x-text="module.name"></p>
|
|
<!-- Dependencies Badge -->
|
|
<template x-if="module.requires?.length > 0">
|
|
<span class="px-1.5 py-0.5 text-xs rounded bg-amber-100 text-amber-700 dark:bg-amber-900/30 dark:text-amber-400"
|
|
:title="`Requires: ${module.requires.join(', ')}`">
|
|
<span x-html="$icon('link', 'w-3 h-3 inline')"></span>
|
|
<span x-text="module.requires.length"></span>
|
|
</span>
|
|
</template>
|
|
<!-- Dependents Badge -->
|
|
<template x-if="module.dependent_modules?.length > 0">
|
|
<span class="px-1.5 py-0.5 text-xs rounded bg-blue-100 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
|
:title="`Required by: ${module.dependent_modules.join(', ')}`">
|
|
<span x-html="$icon('users', 'w-3 h-3 inline')"></span>
|
|
<span x-text="module.dependent_modules.length"></span>
|
|
</span>
|
|
</template>
|
|
</div>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400" x-text="module.description"></p>
|
|
<!-- Dependencies Info -->
|
|
<div x-show="module.requires?.length > 0" class="mt-1">
|
|
<span class="text-xs text-amber-600 dark:text-amber-400">
|
|
Requires: <span x-text="module.requires.join(', ')"></span>
|
|
</span>
|
|
</div>
|
|
<!-- Features -->
|
|
<div x-show="module.features?.length > 0" class="mt-1 flex flex-wrap gap-1">
|
|
<template x-for="feature in module.features.slice(0, 3)" :key="feature">
|
|
<span class="px-1.5 py-0.5 text-xs rounded bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400" x-text="feature"></span>
|
|
</template>
|
|
<span x-show="module.features?.length > 3" class="text-xs text-gray-400" x-text="`+${module.features.length - 3} more`"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-3 ml-4">
|
|
<!-- View Details Button -->
|
|
<a :href="`/admin/platforms/${platformCode}/modules/${module.code}`"
|
|
class="p-1.5 rounded-lg text-gray-500 hover:text-purple-600 hover:bg-purple-50 dark:text-gray-400 dark:hover:text-purple-400 dark:hover:bg-purple-900/20 transition-colors"
|
|
title="View Details">
|
|
<span x-html="$icon('eye', 'w-4 h-4')"></span>
|
|
</a>
|
|
|
|
<!-- Configure Button (if has config) -->
|
|
<a x-show="hasConfig(module.code)"
|
|
:href="`/admin/platforms/${platformCode}/modules/${module.code}/config`"
|
|
class="p-1.5 rounded-lg text-gray-500 hover:text-blue-600 hover:bg-blue-50 dark:text-gray-400 dark:hover:text-blue-400 dark:hover:bg-blue-900/20 transition-colors"
|
|
title="Configure">
|
|
<span x-html="$icon('cog', 'w-4 h-4')"></span>
|
|
</a>
|
|
|
|
<!-- Status Badge -->
|
|
<span x-show="module.is_enabled"
|
|
class="px-2 py-1 text-xs font-medium rounded-full bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200">
|
|
Enabled
|
|
</span>
|
|
<span x-show="!module.is_enabled"
|
|
class="px-2 py-1 text-xs font-medium rounded-full bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400">
|
|
Disabled
|
|
</span>
|
|
|
|
<!-- Toggle Switch -->
|
|
<button
|
|
@click="toggleModule(module)"
|
|
:disabled="saving"
|
|
:class="{
|
|
'bg-purple-600': module.is_enabled,
|
|
'bg-gray-200 dark:bg-gray-600': !module.is_enabled
|
|
}"
|
|
class="relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-purple-500 focus:ring-offset-2"
|
|
role="switch"
|
|
:aria-checked="module.is_enabled"
|
|
>
|
|
<span
|
|
:class="{
|
|
'translate-x-5': module.is_enabled,
|
|
'translate-x-0': !module.is_enabled
|
|
}"
|
|
class="pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"
|
|
></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Empty State -->
|
|
<div x-show="moduleConfig?.modules?.length === 0" class="bg-white rounded-lg shadow-xs dark:bg-gray-800 p-8 text-center">
|
|
<span x-html="$icon('puzzle', 'w-12 h-12 mx-auto text-gray-400')"></span>
|
|
<p class="mt-4 text-gray-500 dark:text-gray-400">No modules available.</p>
|
|
</div>
|
|
</div>
|
|
|
|
{% endblock %}
|
|
|
|
{% block extra_scripts %}
|
|
<script src="{{ url_for('static', path='admin/js/platform-modules.js') }}"></script>
|
|
{% endblock %}
|