lamost fully fixed umfold template
This commit is contained in:
@@ -2,11 +2,12 @@
|
||||
Admin configuration for AI models
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.ai.models import AITaskLog
|
||||
|
||||
|
||||
@admin.register(AITaskLog)
|
||||
class AITaskLogAdmin(admin.ModelAdmin):
|
||||
class AITaskLogAdmin(ModelAdmin):
|
||||
"""Admin interface for AI task logs"""
|
||||
list_display = [
|
||||
'function_name',
|
||||
|
||||
@@ -4,6 +4,7 @@ Admin interface for auth models
|
||||
from django import forms
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from .models import User, Account, Plan, Subscription, Site, Sector, SiteUserAccess, Industry, IndustrySector, SeedKeyword, PasswordResetToken
|
||||
|
||||
@@ -109,7 +110,7 @@ class AccountAdminForm(forms.ModelForm):
|
||||
|
||||
|
||||
@admin.register(Plan)
|
||||
class PlanAdmin(admin.ModelAdmin):
|
||||
class PlanAdmin(ModelAdmin):
|
||||
"""Plan admin - Global, no account filtering needed"""
|
||||
list_display = ['name', 'slug', 'price', 'billing_cycle', 'max_sites', 'max_users', 'max_keywords', 'max_content_words', 'included_credits', 'is_active', 'is_featured']
|
||||
list_filter = ['is_active', 'billing_cycle', 'is_internal', 'is_featured']
|
||||
@@ -143,7 +144,7 @@ class PlanAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Account)
|
||||
class AccountAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class AccountAdmin(AccountAdminMixin, ModelAdmin):
|
||||
form = AccountAdminForm
|
||||
list_display = ['name', 'slug', 'owner', 'plan', 'status', 'health_indicator', 'credits', 'created_at']
|
||||
list_filter = ['status', 'plan']
|
||||
@@ -306,7 +307,7 @@ class AccountAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Subscription)
|
||||
class SubscriptionAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class SubscriptionAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['account', 'status', 'current_period_start', 'current_period_end']
|
||||
list_filter = ['status']
|
||||
search_fields = ['account__name', 'stripe_subscription_id']
|
||||
@@ -314,7 +315,7 @@ class SubscriptionAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(PasswordResetToken)
|
||||
class PasswordResetTokenAdmin(admin.ModelAdmin):
|
||||
class PasswordResetTokenAdmin(ModelAdmin):
|
||||
list_display = ['user', 'token', 'used', 'expires_at', 'created_at']
|
||||
list_filter = ['used', 'expires_at', 'created_at']
|
||||
search_fields = ['user__email', 'token']
|
||||
@@ -352,7 +353,7 @@ class SectorInline(admin.TabularInline):
|
||||
|
||||
|
||||
@admin.register(Site)
|
||||
class SiteAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class SiteAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['name', 'slug', 'account', 'industry', 'domain', 'status', 'is_active', 'get_api_key_status', 'get_sectors_count']
|
||||
list_filter = ['status', 'is_active', 'account', 'industry', 'hosting_type']
|
||||
search_fields = ['name', 'slug', 'domain', 'industry__name']
|
||||
@@ -431,7 +432,7 @@ class SiteAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Sector)
|
||||
class SectorAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class SectorAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['name', 'slug', 'site', 'industry_sector', 'get_industry', 'status', 'is_active', 'get_keywords_count', 'get_clusters_count']
|
||||
list_filter = ['status', 'is_active', 'site', 'industry_sector__industry']
|
||||
search_fields = ['name', 'slug', 'site__name', 'industry_sector__name']
|
||||
@@ -469,7 +470,7 @@ class SectorAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(SiteUserAccess)
|
||||
class SiteUserAccessAdmin(admin.ModelAdmin):
|
||||
class SiteUserAccessAdmin(ModelAdmin):
|
||||
list_display = ['user', 'site', 'granted_at', 'granted_by']
|
||||
list_filter = ['granted_at']
|
||||
search_fields = ['user__email', 'site__name']
|
||||
@@ -485,14 +486,13 @@ class IndustrySectorInline(admin.TabularInline):
|
||||
|
||||
|
||||
@admin.register(Industry)
|
||||
class IndustryAdmin(admin.ModelAdmin):
|
||||
class IndustryAdmin(ModelAdmin):
|
||||
list_display = ['name', 'slug', 'is_active', 'get_sectors_count', 'created_at']
|
||||
list_filter = ['is_active']
|
||||
search_fields = ['name', 'slug', 'description']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
inlines = [IndustrySectorInline]
|
||||
actions = ['delete_selected'] # Enable bulk delete
|
||||
change_list_template = 'admin/igny8_core_auth/industry/change_list.html'
|
||||
|
||||
def get_sectors_count(self, obj):
|
||||
return obj.sectors.filter(is_active=True).count()
|
||||
@@ -504,13 +504,12 @@ class IndustryAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(IndustrySector)
|
||||
class IndustrySectorAdmin(admin.ModelAdmin):
|
||||
class IndustrySectorAdmin(ModelAdmin):
|
||||
list_display = ['name', 'slug', 'industry', 'is_active']
|
||||
list_filter = ['is_active', 'industry']
|
||||
search_fields = ['name', 'slug', 'description']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = ['delete_selected'] # Enable bulk delete
|
||||
change_list_template = 'admin/igny8_core_auth/industrysector/change_list.html'
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
"""Allow deletion for superusers and developers"""
|
||||
@@ -518,14 +517,13 @@ class IndustrySectorAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(SeedKeyword)
|
||||
class SeedKeywordAdmin(admin.ModelAdmin):
|
||||
class SeedKeywordAdmin(ModelAdmin):
|
||||
"""SeedKeyword admin - Global reference data, no account filtering"""
|
||||
list_display = ['keyword', 'industry', 'sector', 'volume', 'difficulty', 'intent', 'is_active', 'created_at']
|
||||
list_filter = ['is_active', 'industry', 'sector', 'intent']
|
||||
search_fields = ['keyword']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
actions = ['delete_selected'] # Enable bulk delete
|
||||
change_list_template = 'admin/igny8_core_auth/seedkeyword/change_list.html'
|
||||
|
||||
fieldsets = (
|
||||
('Keyword Info', {
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
Admin registration for Automation models
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from .models import AutomationConfig, AutomationRun
|
||||
|
||||
|
||||
@admin.register(AutomationConfig)
|
||||
class AutomationConfigAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class AutomationConfigAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ('site', 'is_enabled', 'frequency', 'scheduled_time', 'within_stage_delay', 'between_stage_delay', 'last_run_at')
|
||||
list_filter = ('is_enabled', 'frequency')
|
||||
search_fields = ('site__domain',)
|
||||
|
||||
|
||||
@admin.register(AutomationRun)
|
||||
class AutomationRunAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class AutomationRunAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ('run_id', 'site', 'status', 'current_stage', 'started_at', 'completed_at')
|
||||
list_filter = ('status', 'current_stage')
|
||||
search_fields = ('run_id', 'site__domain')
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from django.contrib import admin
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from .models import SiteIntegration, SyncEvent
|
||||
|
||||
|
||||
@admin.register(SiteIntegration)
|
||||
class SiteIntegrationAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class SiteIntegrationAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = [
|
||||
'site',
|
||||
'platform',
|
||||
@@ -20,7 +21,7 @@ class SiteIntegrationAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(SyncEvent)
|
||||
class SyncEventAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class SyncEventAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = [
|
||||
'integration',
|
||||
'site',
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from django.contrib import admin
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from .models import OptimizationTask
|
||||
|
||||
|
||||
@admin.register(OptimizationTask)
|
||||
class OptimizationTaskAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class OptimizationTaskAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['content', 'account', 'status', 'credits_used', 'created_at']
|
||||
list_filter = ['status', 'created_at']
|
||||
search_fields = ['content__title', 'account__name']
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
from django.contrib import admin
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import SiteSectorAdminMixin
|
||||
from .models import PublishingRecord, DeploymentRecord
|
||||
|
||||
|
||||
@admin.register(PublishingRecord)
|
||||
class PublishingRecordAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
class PublishingRecordAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
list_display = [
|
||||
'content',
|
||||
'site',
|
||||
@@ -20,7 +21,7 @@ class PublishingRecordAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(DeploymentRecord)
|
||||
class DeploymentRecordAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
class DeploymentRecordAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
list_display = [
|
||||
'site',
|
||||
'sector',
|
||||
|
||||
@@ -4,6 +4,7 @@ Billing Module Admin
|
||||
from django.contrib import admin
|
||||
from django.utils.html import format_html
|
||||
from django.contrib import messages
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from igny8_core.business.billing.models import (
|
||||
CreditCostConfig,
|
||||
@@ -28,7 +29,7 @@ class CreditTransactionResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(CreditTransaction)
|
||||
class CreditTransactionAdmin(ExportMixin, AccountAdminMixin, admin.ModelAdmin):
|
||||
class CreditTransactionAdmin(ExportMixin, AccountAdminMixin, ModelAdmin):
|
||||
resource_class = CreditTransactionResource
|
||||
list_display = ['id', 'account', 'transaction_type', 'amount', 'balance_after', 'description', 'created_at']
|
||||
list_filter = ['transaction_type', ('created_at', DateRangeFilter), 'account']
|
||||
@@ -47,7 +48,7 @@ class CreditTransactionAdmin(ExportMixin, AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(CreditUsageLog)
|
||||
class CreditUsageLogAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class CreditUsageLogAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['id', 'account', 'operation_type', 'credits_used', 'cost_usd', 'model_used', 'created_at']
|
||||
list_filter = ['operation_type', 'created_at', 'account', 'model_used']
|
||||
search_fields = ['account__name', 'model_used']
|
||||
@@ -65,7 +66,7 @@ class CreditUsageLogAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Invoice)
|
||||
class InvoiceAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class InvoiceAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = [
|
||||
'invoice_number',
|
||||
'account',
|
||||
@@ -91,7 +92,7 @@ class PaymentResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(Payment)
|
||||
class PaymentAdmin(ExportMixin, AccountAdminMixin, admin.ModelAdmin):
|
||||
class PaymentAdmin(ExportMixin, AccountAdminMixin, ModelAdmin):
|
||||
"""
|
||||
Main Payment Admin with approval workflow.
|
||||
When you change status to 'succeeded', it automatically:
|
||||
@@ -373,7 +374,7 @@ class PaymentAdmin(ExportMixin, AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(CreditPackage)
|
||||
class CreditPackageAdmin(admin.ModelAdmin):
|
||||
class CreditPackageAdmin(ModelAdmin):
|
||||
list_display = ['name', 'slug', 'credits', 'price', 'discount_percentage', 'is_active', 'is_featured', 'sort_order']
|
||||
list_filter = ['is_active', 'is_featured']
|
||||
search_fields = ['name', 'slug']
|
||||
@@ -381,7 +382,7 @@ class CreditPackageAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(PaymentMethodConfig)
|
||||
class PaymentMethodConfigAdmin(admin.ModelAdmin):
|
||||
class PaymentMethodConfigAdmin(ModelAdmin):
|
||||
list_display = ['country_code', 'payment_method', 'display_name', 'is_enabled', 'sort_order', 'updated_at']
|
||||
list_filter = ['payment_method', 'is_enabled', 'country_code']
|
||||
search_fields = ['country_code', 'display_name', 'payment_method']
|
||||
@@ -390,7 +391,7 @@ class PaymentMethodConfigAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(AccountPaymentMethod)
|
||||
class AccountPaymentMethodAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class AccountPaymentMethodAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = [
|
||||
'display_name',
|
||||
'type',
|
||||
@@ -419,7 +420,7 @@ class AccountPaymentMethodAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(CreditCostConfig)
|
||||
class CreditCostConfigAdmin(admin.ModelAdmin):
|
||||
class CreditCostConfigAdmin(ModelAdmin):
|
||||
list_display = [
|
||||
'operation_type',
|
||||
'display_name',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib import messages
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import SiteSectorAdminMixin
|
||||
from .models import Keywords, Clusters, ContentIdeas
|
||||
from import_export.admin import ExportMixin
|
||||
@@ -16,7 +17,7 @@ class KeywordsResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(Clusters)
|
||||
class ClustersAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
class ClustersAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
list_display = ['name', 'site', 'sector', 'keywords_count', 'volume', 'status', 'created_at']
|
||||
list_filter = ['status', 'site', 'sector']
|
||||
search_fields = ['name']
|
||||
@@ -40,7 +41,7 @@ class ClustersAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Keywords)
|
||||
class KeywordsAdmin(ExportMixin, SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
class KeywordsAdmin(ExportMixin, SiteSectorAdminMixin, ModelAdmin):
|
||||
resource_class = KeywordsResource
|
||||
list_display = ['keyword', 'seed_keyword', 'site', 'sector', 'cluster', 'volume', 'difficulty', 'intent', 'status', 'created_at']
|
||||
list_filter = ['status', 'seed_keyword__intent', 'site', 'sector', 'seed_keyword__industry', 'seed_keyword__sector']
|
||||
@@ -130,7 +131,7 @@ class KeywordsAdmin(ExportMixin, SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ContentIdeas)
|
||||
class ContentIdeasAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
class ContentIdeasAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
list_display = ['idea_title', 'site', 'sector', 'description_preview', 'content_type', 'content_structure', 'status', 'keyword_cluster', 'estimated_word_count', 'created_at']
|
||||
list_filter = ['status', 'content_type', 'content_structure', 'site', 'sector']
|
||||
search_fields = ['idea_title', 'target_keywords', 'description']
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
System Module Admin
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from .models import AIPrompt, IntegrationSettings, AuthorProfile, Strategy
|
||||
|
||||
@@ -15,7 +16,7 @@ try:
|
||||
from .models import SystemLog, SystemStatus
|
||||
|
||||
@admin.register(SystemLog)
|
||||
class SystemLogAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class SystemLogAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['id', 'account', 'module', 'level', 'action', 'message', 'created_at']
|
||||
list_filter = ['module', 'level', 'created_at', 'account']
|
||||
search_fields = ['message', 'action']
|
||||
@@ -24,7 +25,7 @@ try:
|
||||
|
||||
|
||||
@admin.register(SystemStatus)
|
||||
class SystemStatusAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class SystemStatusAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['component', 'account', 'status', 'message', 'last_check']
|
||||
list_filter = ['status', 'component', 'account']
|
||||
search_fields = ['component', 'message']
|
||||
@@ -34,7 +35,7 @@ except ImportError:
|
||||
|
||||
|
||||
@admin.register(AIPrompt)
|
||||
class AIPromptAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class AIPromptAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['id', 'prompt_type', 'account', 'is_active', 'updated_at']
|
||||
list_filter = ['prompt_type', 'is_active', 'account']
|
||||
search_fields = ['prompt_type']
|
||||
@@ -63,7 +64,7 @@ class AIPromptAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(IntegrationSettings)
|
||||
class IntegrationSettingsAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class IntegrationSettingsAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['id', 'integration_type', 'account', 'is_active', 'updated_at']
|
||||
list_filter = ['integration_type', 'is_active', 'account']
|
||||
search_fields = ['integration_type']
|
||||
@@ -99,7 +100,7 @@ class IntegrationSettingsAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(AuthorProfile)
|
||||
class AuthorProfileAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class AuthorProfileAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['name', 'account', 'tone', 'language', 'is_active', 'created_at']
|
||||
list_filter = ['is_active', 'tone', 'language', 'account']
|
||||
search_fields = ['name', 'description', 'tone']
|
||||
@@ -128,7 +129,7 @@ class AuthorProfileAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Strategy)
|
||||
class StrategyAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class StrategyAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['name', 'account', 'sector', 'is_active', 'created_at']
|
||||
list_filter = ['is_active', 'account']
|
||||
search_fields = ['name', 'description']
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
Settings Models Admin
|
||||
"""
|
||||
from django.contrib import admin
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import AccountAdminMixin
|
||||
from .settings_models import SystemSettings, AccountSettings, UserSettings, ModuleSettings, ModuleEnableSettings, AISettings
|
||||
|
||||
|
||||
@admin.register(SystemSettings)
|
||||
class SystemSettingsAdmin(admin.ModelAdmin):
|
||||
class SystemSettingsAdmin(ModelAdmin):
|
||||
"""SystemSettings - Global, no account filtering"""
|
||||
list_display = ['key', 'description', 'updated_at']
|
||||
search_fields = ['key', 'description']
|
||||
@@ -15,7 +16,7 @@ class SystemSettingsAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(AccountSettings)
|
||||
class AccountSettingsAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class AccountSettingsAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['account', 'key', 'is_active', 'updated_at']
|
||||
list_filter = ['is_active', 'account']
|
||||
search_fields = ['key', 'account__name']
|
||||
@@ -32,7 +33,7 @@ class AccountSettingsAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(UserSettings)
|
||||
class UserSettingsAdmin(admin.ModelAdmin):
|
||||
class UserSettingsAdmin(ModelAdmin):
|
||||
list_display = ['user', 'account', 'key', 'updated_at']
|
||||
list_filter = ['account']
|
||||
search_fields = ['key', 'user__email', 'account__name']
|
||||
@@ -59,7 +60,7 @@ class UserSettingsAdmin(admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ModuleSettings)
|
||||
class ModuleSettingsAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class ModuleSettingsAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['account', 'module_name', 'key', 'is_active', 'updated_at']
|
||||
list_filter = ['module_name', 'is_active', 'account']
|
||||
search_fields = ['key', 'module_name', 'account__name']
|
||||
@@ -76,7 +77,7 @@ class ModuleSettingsAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(AISettings)
|
||||
class AISettingsAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class AISettingsAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = ['account', 'integration_type', 'is_active', 'updated_at']
|
||||
list_filter = ['integration_type', 'is_active', 'account']
|
||||
search_fields = ['integration_type', 'account__name']
|
||||
@@ -93,7 +94,7 @@ class AISettingsAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ModuleEnableSettings)
|
||||
class ModuleEnableSettingsAdmin(AccountAdminMixin, admin.ModelAdmin):
|
||||
class ModuleEnableSettingsAdmin(AccountAdminMixin, ModelAdmin):
|
||||
list_display = [
|
||||
'account',
|
||||
'planner_enabled',
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from django.contrib import admin
|
||||
from django.contrib import messages
|
||||
from unfold.admin import ModelAdmin
|
||||
from igny8_core.admin.base import SiteSectorAdminMixin
|
||||
from .models import Tasks, Images, Content
|
||||
from igny8_core.business.content.models import ContentTaxonomy, ContentAttribute, ContentTaxonomyRelation, ContentClusterMap
|
||||
@@ -26,7 +27,7 @@ class TaskResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(Tasks)
|
||||
class TasksAdmin(ExportMixin, SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
class TasksAdmin(ExportMixin, SiteSectorAdminMixin, ModelAdmin):
|
||||
resource_class = TaskResource
|
||||
list_display = ['title', 'content_type', 'content_structure', 'site', 'sector', 'status', 'cluster', 'created_at']
|
||||
list_filter = ['status', 'content_type', 'content_structure', 'site', 'sector', 'cluster']
|
||||
@@ -141,7 +142,7 @@ class TasksAdmin(ExportMixin, SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(Images)
|
||||
class ImagesAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
class ImagesAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
list_display = ['get_content_title', 'site', 'sector', 'image_type', 'status', 'position', 'created_at']
|
||||
list_filter = ['image_type', 'status', 'site', 'sector']
|
||||
search_fields = ['content__title']
|
||||
@@ -183,7 +184,7 @@ class ContentResource(resources.ModelResource):
|
||||
|
||||
|
||||
@admin.register(Content)
|
||||
class ContentAdmin(ExportMixin, SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
class ContentAdmin(ExportMixin, SiteSectorAdminMixin, ModelAdmin):
|
||||
resource_class = ContentResource
|
||||
list_display = ['title', 'content_type', 'content_structure', 'site', 'sector', 'source', 'status', 'get_taxonomy_count', 'created_at']
|
||||
list_filter = ['content_type', 'content_structure', 'source', 'status', 'site', 'sector', 'created_at']
|
||||
@@ -326,7 +327,7 @@ class ContentAdmin(ExportMixin, SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ContentTaxonomy)
|
||||
class ContentTaxonomyAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
class ContentTaxonomyAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
list_display = ['name', 'taxonomy_type', 'slug', 'count', 'external_id', 'external_taxonomy', 'site', 'sector']
|
||||
list_filter = ['taxonomy_type', 'site', 'sector']
|
||||
search_fields = ['name', 'slug', 'external_taxonomy']
|
||||
@@ -357,7 +358,7 @@ class ContentTaxonomyAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ContentAttribute)
|
||||
class ContentAttributeAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
class ContentAttributeAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
list_display = ['name', 'value', 'attribute_type', 'content', 'cluster', 'external_id', 'source', 'site', 'sector']
|
||||
list_filter = ['attribute_type', 'source', 'site', 'sector']
|
||||
search_fields = ['name', 'value', 'external_attribute_name', 'content__title']
|
||||
@@ -382,14 +383,14 @@ class ContentAttributeAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
|
||||
|
||||
@admin.register(ContentTaxonomyRelation)
|
||||
class ContentTaxonomyRelationAdmin(admin.ModelAdmin):
|
||||
class ContentTaxonomyRelationAdmin(ModelAdmin):
|
||||
list_display = ['content', 'taxonomy', 'created_at']
|
||||
search_fields = ['content__title', 'taxonomy__name']
|
||||
readonly_fields = ['created_at', 'updated_at']
|
||||
|
||||
|
||||
@admin.register(ContentClusterMap)
|
||||
class ContentClusterMapAdmin(SiteSectorAdminMixin, admin.ModelAdmin):
|
||||
class ContentClusterMapAdmin(SiteSectorAdminMixin, ModelAdmin):
|
||||
list_display = ['content', 'task', 'cluster', 'role', 'source', 'site', 'sector', 'created_at']
|
||||
list_filter = ['role', 'source', 'site', 'sector']
|
||||
search_fields = ['content__title', 'task__title', 'cluster__name']
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,70 +1,11 @@
|
||||
{% extends "admin/base.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}{{ title }} | IGNY8 Admin{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
<!-- FontAwesome Icons -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
{% endblock %}
|
||||
|
||||
{% block branding %}
|
||||
<h1 id="site-name">
|
||||
<a href="{% url 'admin:index' %}">
|
||||
<i class="fas fa-rocket"></i> IGNY8 Administration
|
||||
🚀 IGNY8 Administration
|
||||
</a>
|
||||
</h1>
|
||||
{% endblock %}
|
||||
|
||||
{% block userlinks %}
|
||||
<a href="{% url 'admin:dashboard' %}" class="dashboard-link">
|
||||
<i class="fas fa-chart-line"></i>
|
||||
<span>Dashboard</span>
|
||||
</a>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block extrastyle %}
|
||||
{{ block.super }}
|
||||
<link rel="stylesheet" href="{% static 'admin/css/igny8_admin.css' %}">
|
||||
<style>
|
||||
/* Dashboard link in header */
|
||||
.dashboard-link {
|
||||
display: inline-flex !important;
|
||||
align-items: center !important;
|
||||
gap: 8px !important;
|
||||
padding: 10px 20px !important;
|
||||
margin-right: 20px !important;
|
||||
background: rgba(255, 255, 255, 0.15) !important;
|
||||
color: white !important;
|
||||
text-decoration: none !important;
|
||||
border-radius: 8px !important;
|
||||
font-weight: 600 !important;
|
||||
font-size: 14px !important;
|
||||
transition: all 0.2s ease !important;
|
||||
backdrop-filter: blur(10px) !important;
|
||||
border: 1px solid rgba(255, 255, 255, 0.2) !important;
|
||||
}
|
||||
|
||||
.dashboard-link:hover {
|
||||
background: rgba(255, 255, 255, 0.25) !important;
|
||||
transform: translateY(-1px) !important;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15) !important;
|
||||
}
|
||||
|
||||
.dashboard-link i {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
|
||||
/* User tools spacing */
|
||||
#user-tools {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
gap: 15px !important;
|
||||
padding: 10px 20px !important;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block nav-global %}{% endblock %}
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block content %}
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
<form method="post">
|
||||
{% csrf_token %}
|
||||
|
||||
<div class="form-group" style="margin: 20px 0;">
|
||||
{{ form.as_p }}
|
||||
</div>
|
||||
|
||||
<div class="submit-row" style="margin-top: 20px;">
|
||||
<input type="hidden" name="action" value="{{ action }}">
|
||||
<input type="hidden" name="_selected_action" value="{{ queryset|join:',' }}">
|
||||
<input type="submit" name="apply" value="Apply" class="button" style="margin-right: 10px;">
|
||||
<a href="javascript:history.back()" class="button">Cancel</a>
|
||||
</div>
|
||||
|
||||
<fieldset class="module aligned" style="margin-top: 20px;">
|
||||
<h2>Selected Items ({{ queryset.count }})</h2>
|
||||
<ul style="list-style: none; padding: 10px;">
|
||||
{% for item in queryset|slice:":10" %}
|
||||
<li style="padding: 5px 0; border-bottom: 1px solid var(--igny8-stroke);">
|
||||
{{ item }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% if queryset.count > 10 %}
|
||||
<li style="padding: 10px 0; font-style: italic; color: var(--igny8-text-dim);">
|
||||
...and {{ queryset.count|add:"-10" }} more
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
.form-group label {
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
color: var(--igny8-text);
|
||||
}
|
||||
|
||||
.form-group select,
|
||||
.form-group input {
|
||||
padding: 8px;
|
||||
border: 1px solid var(--igny8-stroke);
|
||||
border-radius: 4px;
|
||||
min-width: 300px;
|
||||
}
|
||||
|
||||
.form-group .helptext {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
color: var(--igny8-text-dim);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.form-group ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.form-group ul li {
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.form-group ul li label {
|
||||
font-weight: normal;
|
||||
margin-left: 5px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -1,419 +0,0 @@
|
||||
{% extends "admin/base_site.html" %}
|
||||
{% load static %}
|
||||
|
||||
{% block title %}IGNY8 Dashboard{% endblock %}
|
||||
|
||||
{% block extrahead %}
|
||||
{{ block.super }}
|
||||
<style>
|
||||
.dashboard-container {
|
||||
padding: 20px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.dashboard-header {
|
||||
background: linear-gradient(135deg, #0d1b2a 0%, #1a2e44 100%);
|
||||
color: white;
|
||||
padding: 30px;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 30px;
|
||||
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.dashboard-header-content h1 {
|
||||
margin: 0 0 10px 0;
|
||||
font-size: 32px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.dashboard-header-content p {
|
||||
margin: 0;
|
||||
opacity: 0.9;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.dashboard-nav {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.dashboard-nav a {
|
||||
padding: 10px 20px;
|
||||
background: rgba(255, 255, 255, 0.15);
|
||||
color: white !important;
|
||||
text-decoration: none;
|
||||
border-radius: 6px;
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
transition: background 0.2s;
|
||||
backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
.dashboard-nav a:hover {
|
||||
background: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.alerts-section {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.alert-card {
|
||||
padding: 16px 20px;
|
||||
border-radius: 8px;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.alert-card.error {
|
||||
background: #fef2f2;
|
||||
border-left: 4px solid #ef4444;
|
||||
}
|
||||
|
||||
.alert-card.warning {
|
||||
background: #fff7ed;
|
||||
border-left: 4px solid #ff7a00;
|
||||
}
|
||||
|
||||
.alert-card.info {
|
||||
background: #eff6ff;
|
||||
border-left: 4px solid #0693e3;
|
||||
}
|
||||
|
||||
.alert-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.alert-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.alert-message {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.alert-action {
|
||||
padding: 8px 16px;
|
||||
background: white;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
color: #374151;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.alert-action:hover {
|
||||
background: #f9fafb;
|
||||
border-color: #9ca3af;
|
||||
}
|
||||
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
border-top: 4px solid #0693e3;
|
||||
}
|
||||
|
||||
.metric-card h3 {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #0d1b2a;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.metric-card .icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.metric-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
}
|
||||
|
||||
.metric-row:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1f2937;
|
||||
}
|
||||
|
||||
.metric-value.success {
|
||||
color: #0bbf87;
|
||||
}
|
||||
|
||||
.metric-value.warning {
|
||||
color: #ff7a00;
|
||||
}
|
||||
|
||||
.metric-value.error {
|
||||
color: #ef4444;
|
||||
}
|
||||
|
||||
.metric-value.info {
|
||||
color: #0693e3;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
padding: 24px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.quick-actions h3 {
|
||||
margin: 0 0 20px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #0d1b2a;
|
||||
}
|
||||
|
||||
.action-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 20px 16px;
|
||||
background: linear-gradient(135deg, #0693e3 0%, #0d82c8 100%);
|
||||
color: white !important;
|
||||
text-decoration: none;
|
||||
border-radius: 8px;
|
||||
text-align: center;
|
||||
font-weight: 600;
|
||||
font-size: 14px;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
box-shadow: 0 2px 4px rgba(6, 147, 227, 0.2);
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.action-button:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(6, 147, 227, 0.3);
|
||||
color: white !important;
|
||||
background: linear-gradient(135deg, #0780cb 0%, #0c75b5 100%);
|
||||
}
|
||||
|
||||
.action-button .icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.action-button .label {
|
||||
color: white !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.no-alerts {
|
||||
text-align: center;
|
||||
padding: 40px;
|
||||
color: #6b7280;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.no-alerts .icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="dashboard-container">
|
||||
<div class="dashboard-header">
|
||||
<div class="dashboard-header-content">
|
||||
<h1>🚀 IGNY8 Admin Dashboard</h1>
|
||||
<p>Real-time operational metrics and system health monitoring</p>
|
||||
</div>
|
||||
<div class="dashboard-nav">
|
||||
<a href="/admin/">← Back to Admin</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if alerts %}
|
||||
<div class="alerts-section">
|
||||
<h2 style="margin: 0 0 16px 0; font-size: 20px; font-weight: 600;">📢 Active Alerts</h2>
|
||||
{% for alert in alerts %}
|
||||
<div class="alert-card {{ alert.level }}">
|
||||
<div class="alert-content">
|
||||
<span class="alert-icon">{{ alert.icon }}</span>
|
||||
<span class="alert-message">{{ alert.message }}</span>
|
||||
</div>
|
||||
<a href="{{ alert.url }}" class="alert-action">{{ alert.action }}</a>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alerts-section">
|
||||
<div class="no-alerts">
|
||||
<div class="icon">✅</div>
|
||||
<h3 style="margin: 0 0 8px 0; color: #0bbf87;">All Systems Operational</h3>
|
||||
<p style="margin: 0;">No active alerts or issues detected</p>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="metrics-grid">
|
||||
<!-- Accounts Card -->
|
||||
<div class="metric-card">
|
||||
<h3><span class="icon">👥</span> Accounts</h3>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Total Accounts</span>
|
||||
<span class="metric-value info">{{ accounts.total }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Active Accounts</span>
|
||||
<span class="metric-value success">{{ accounts.active }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Low Credit Accounts</span>
|
||||
<span class="metric-value {% if accounts.low_credit > 0 %}warning{% else %}success{% endif %}">{{ accounts.low_credit }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Content Card -->
|
||||
<div class="metric-card">
|
||||
<h3><span class="icon">📚</span> Content</h3>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Created This Week</span>
|
||||
<span class="metric-value info">{{ content.this_week }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Created This Month</span>
|
||||
<span class="metric-value info">{{ content.this_month }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Pending Tasks</span>
|
||||
<span class="metric-value {% if content.tasks_pending > 50 %}warning{% else %}info{% endif %}">{{ content.tasks_pending }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">In Progress</span>
|
||||
<span class="metric-value info">{{ content.tasks_in_progress }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Billing Card -->
|
||||
<div class="metric-card">
|
||||
<h3><span class="icon">💰</span> Billing</h3>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Pending Payments</span>
|
||||
<span class="metric-value {% if billing.pending_payments > 0 %}warning{% else %}success{% endif %}">{{ billing.pending_payments }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Revenue This Month</span>
|
||||
<span class="metric-value success">${{ billing.payments_this_month|floatformat:2 }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Credits Used This Month</span>
|
||||
<span class="metric-value info">{{ billing.credit_usage_this_month }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Automation & Integration Card -->
|
||||
<div class="metric-card">
|
||||
<h3><span class="icon">🤖</span> Automation & Sync</h3>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Automations Running</span>
|
||||
<span class="metric-value info">{{ automation.running }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Failed This Week</span>
|
||||
<span class="metric-value {% if automation.failed_this_week > 0 %}error{% else %}success{% endif %}">{{ automation.failed_this_week }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Failed Syncs Today</span>
|
||||
<span class="metric-value {% if integration.sync_failed_today > 5 %}warning{% elif integration.sync_failed_today > 0 %}info{% else %}success{% endif %}">{{ integration.sync_failed_today }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Celery Tasks Card -->
|
||||
<div class="metric-card">
|
||||
<h3><span class="icon">⚙️</span> Celery Tasks</h3>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Failed Today</span>
|
||||
<span class="metric-value {% if celery.failed_today > 0 %}error{% else %}success{% endif %}">{{ celery.failed_today }}</span>
|
||||
</div>
|
||||
<div class="metric-row">
|
||||
<span class="metric-label">Pending Tasks</span>
|
||||
<span class="metric-value info">{{ celery.pending }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quick-actions">
|
||||
<h3>⚡ Quick Actions</h3>
|
||||
<div class="action-grid">
|
||||
<a href="/admin/" class="action-button" style="background: linear-gradient(135deg, #0d1b2a 0%, #1a2e44 100%);">
|
||||
<span class="icon">🏠</span>
|
||||
<span class="label">Admin Home</span>
|
||||
</a>
|
||||
<a href="/admin/igny8_core_auth/account/" class="action-button">
|
||||
<span class="icon">👥</span>
|
||||
<span class="label">Manage Accounts</span>
|
||||
</a>
|
||||
<a href="/admin/writer/content/" class="action-button">
|
||||
<span class="icon">📝</span>
|
||||
<span class="label">View Content</span>
|
||||
</a>
|
||||
<a href="/admin/writer/tasks/" class="action-button">
|
||||
<span class="icon">✅</span>
|
||||
<span class="label">Manage Tasks</span>
|
||||
</a>
|
||||
<a href="/admin/billing/payment/" class="action-button">
|
||||
<span class="icon">💳</span>
|
||||
<span class="label">Review Payments</span>
|
||||
</a>
|
||||
<a href="/admin/automation/automationrun/" class="action-button">
|
||||
<span class="icon">🤖</span>
|
||||
<span class="label">View Automations</span>
|
||||
</a>
|
||||
<a href="/admin/django_celery_results/taskresult/" class="action-button">
|
||||
<span class="icon">⚙️</span>
|
||||
<span class="label">Celery Monitor</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,72 +0,0 @@
|
||||
{% extends "admin/change_list.html" %}
|
||||
{% load i18n admin_urls static %}
|
||||
|
||||
{% block object-tools-items %}
|
||||
<li>
|
||||
<a href="#" class="addlink" onclick="document.getElementById('csv-import-form').style.display='block'; return false;">
|
||||
Import from CSV
|
||||
</a>
|
||||
</li>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="csv-import-form" style="display:none; background: #f8f8f8; padding: 20px; margin: 20px 0; border: 1px solid #ddd;">
|
||||
<h2>Import Industries from CSV</h2>
|
||||
<form method="post" enctype="multipart/form-data" action="{% url 'admin_industry_csv_import' %}" id="csv-upload-form">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
<label for="csv_file">Select CSV file:</label>
|
||||
<input type="file" name="csv_file" id="csv_file" accept=".csv" required>
|
||||
</p>
|
||||
<p>
|
||||
<strong>CSV Format:</strong> name, description, is_active<br>
|
||||
<em>Note: Slug will be auto-generated from name</em><br>
|
||||
<a href="{% url 'admin_industry_csv_template' %}" download>Download template with examples</a>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit" class="button">Upload and Import</button>
|
||||
<button type="button" class="button" onclick="document.getElementById('csv-import-form').style.display='none'">Cancel</button>
|
||||
</p>
|
||||
</form>
|
||||
<div id="import-results"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('csv-upload-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var formData = new FormData(this);
|
||||
var resultsDiv = document.getElementById('import-results');
|
||||
resultsDiv.innerHTML = '<p>Importing...</p>';
|
||||
|
||||
fetch('{% url "admin_industry_csv_import" %}', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
resultsDiv.innerHTML = '<p style="color: green;">✓ Import successful!<br>' +
|
||||
'Created: ' + data.created + '<br>' +
|
||||
'Updated: ' + data.updated + '<br>' +
|
||||
(data.errors.length > 0 ? 'Errors: ' + data.errors.join('<br>') : '') +
|
||||
'</p>';
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
resultsDiv.innerHTML = '<p style="color: red;">✗ Error: ' + data.error + '</p>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
resultsDiv.innerHTML = '<p style="color: red;">✗ Error: ' + error + '</p>';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
@@ -1,72 +0,0 @@
|
||||
{% extends "admin/change_list.html" %}
|
||||
{% load i18n admin_urls static %}
|
||||
|
||||
{% block object-tools-items %}
|
||||
<li>
|
||||
<a href="#" class="addlink" onclick="document.getElementById('csv-import-form').style.display='block'; return false;">
|
||||
Import from CSV
|
||||
</a>
|
||||
</li>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="csv-import-form" style="display:none; background: #f8f8f8; padding: 20px; margin: 20px 0; border: 1px solid #ddd;">
|
||||
<h2>Import Industry Sectors from CSV</h2>
|
||||
<form method="post" enctype="multipart/form-data" action="{% url 'admin_industrysector_csv_import' %}" id="csv-upload-form">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
<label for="csv_file">Select CSV file:</label>
|
||||
<input type="file" name="csv_file" id="csv_file" accept=".csv" required>
|
||||
</p>
|
||||
<p>
|
||||
<strong>CSV Format:</strong> name, industry, description, is_active<br>
|
||||
<em>Note: Use industry name (not slug). Slug will be auto-generated from name</em><br>
|
||||
<a href="{% url 'admin_industrysector_csv_template' %}" download>Download template with examples</a>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit" class="button">Upload and Import</button>
|
||||
<button type="button" class="button" onclick="document.getElementById('csv-import-form').style.display='none'">Cancel</button>
|
||||
</p>
|
||||
</form>
|
||||
<div id="import-results"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('csv-upload-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var formData = new FormData(this);
|
||||
var resultsDiv = document.getElementById('import-results');
|
||||
resultsDiv.innerHTML = '<p>Importing...</p>';
|
||||
|
||||
fetch('{% url "admin_industrysector_csv_import" %}', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
resultsDiv.innerHTML = '<p style="color: green;">✓ Import successful!<br>' +
|
||||
'Created: ' + data.created + '<br>' +
|
||||
'Updated: ' + data.updated + '<br>' +
|
||||
(data.errors.length > 0 ? 'Errors: ' + data.errors.join('<br>') : '') +
|
||||
'</p>';
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
resultsDiv.innerHTML = '<p style="color: red;">✗ Error: ' + data.error + '</p>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
resultsDiv.innerHTML = '<p style="color: red;">✗ Error: ' + error + '</p>';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
@@ -1,73 +0,0 @@
|
||||
{% extends "admin/change_list.html" %}
|
||||
{% load i18n admin_urls static %}
|
||||
|
||||
{% block object-tools-items %}
|
||||
<li>
|
||||
<a href="#" class="addlink" onclick="document.getElementById('csv-import-form').style.display='block'; return false;">
|
||||
Import from CSV
|
||||
</a>
|
||||
</li>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div id="csv-import-form" style="display:none; background: #f8f8f8; padding: 20px; margin: 20px 0; border: 1px solid #ddd;">
|
||||
<h2>Import Global Keywords from CSV</h2>
|
||||
<form method="post" enctype="multipart/form-data" action="{% url 'admin_seedkeyword_csv_import' %}" id="csv-upload-form">
|
||||
{% csrf_token %}
|
||||
<p>
|
||||
<label for="csv_file">Select CSV file:</label>
|
||||
<input type="file" name="csv_file" id="csv_file" accept=".csv" required>
|
||||
</p>
|
||||
<p>
|
||||
<strong>CSV Format:</strong> keyword, industry, sector, volume, difficulty, intent, is_active<br>
|
||||
<em>Note: Use industry and sector names (not slugs)</em><br>
|
||||
<strong>Intent values:</strong> Informational, Commercial, Transactional, Navigational<br>
|
||||
<a href="{% url 'admin_seedkeyword_csv_template' %}" download>Download template with examples</a>
|
||||
</p>
|
||||
<p>
|
||||
<button type="submit" class="button">Upload and Import</button>
|
||||
<button type="button" class="button" onclick="document.getElementById('csv-import-form').style.display='none'">Cancel</button>
|
||||
</p>
|
||||
</form>
|
||||
<div id="import-results"></div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('csv-upload-form').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
var formData = new FormData(this);
|
||||
var resultsDiv = document.getElementById('import-results');
|
||||
resultsDiv.innerHTML = '<p>Importing...</p>';
|
||||
|
||||
fetch('{% url "admin_seedkeyword_csv_import" %}', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
resultsDiv.innerHTML = '<p style="color: green;">✓ Import successful!<br>' +
|
||||
'Created: ' + data.created + '<br>' +
|
||||
'Updated: ' + data.updated + '<br>' +
|
||||
(data.errors.length > 0 ? 'Errors: ' + data.errors.join('<br>') : '') +
|
||||
'</p>';
|
||||
setTimeout(function() {
|
||||
window.location.reload();
|
||||
}, 2000);
|
||||
} else {
|
||||
resultsDiv.innerHTML = '<p style="color: red;">✗ Error: ' + data.error + '</p>';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
resultsDiv.innerHTML = '<p style="color: red;">✗ Error: ' + error + '</p>';
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user