Merge remote-tracking branch 'origin/master'
7
.gitignore
vendored
@ -23,4 +23,9 @@ ENV/
|
|||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
Thumbs.db
|
Thumbs.db
|
||||||
|
|
||||||
|
# Project specific
|
||||||
|
*.log
|
||||||
|
*.pot
|
||||||
|
*.py[co]
|
||||||
@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
ASGI config for OneCprogsite project.
|
|
||||||
|
|
||||||
It exposes the ASGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'OneCprogsite.settings')
|
|
||||||
|
|
||||||
application = get_asgi_application()
|
|
||||||
@ -1,212 +0,0 @@
|
|||||||
"""
|
|
||||||
Django settings for OneCprogsite project.
|
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 4.2.7.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/4.2/topics/settings/
|
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
|
||||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
|
||||||
"""
|
|
||||||
import os.path
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
||||||
|
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
|
||||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
|
||||||
SECRET_KEY = 'django-insecure-5rs2a1*8cxjkv*%6k1-88biv&1#nep%@i+%1^dk=5j$s&e&hwm'
|
|
||||||
|
|
||||||
# Безопасность cookies для HTTPS
|
|
||||||
SESSION_COOKIE_SECURE = True
|
|
||||||
CSRF_COOKIE_SECURE = True
|
|
||||||
SESSION_COOKIE_HTTPONLY = True
|
|
||||||
CSRF_COOKIE_HTTPONLY = False # Django требует доступ к CSRF cookie через JS
|
|
||||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
|
||||||
CSRF_COOKIE_SAMESITE = 'Lax'
|
|
||||||
|
|
||||||
# Если используете другие cookies
|
|
||||||
LANGUAGE_COOKIE_SECURE = True
|
|
||||||
LANGUAGE_COOKIE_HTTPONLY = True
|
|
||||||
LANGUAGE_COOKIE_SAMESITE = 'Lax'
|
|
||||||
|
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = False
|
|
||||||
|
|
||||||
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
|
||||||
# Или разрешить конкретные домены (Django 4.0+)
|
|
||||||
X_FRAME_OPTIONS = 'ALLOW-FROM https://metrika.yandex.ru'
|
|
||||||
|
|
||||||
# ОБЯЗАТЕЛЬНО укажите ваши домены
|
|
||||||
ALLOWED_HOSTS = [
|
|
||||||
'nikdizell.ru',
|
|
||||||
'www.nikdizell.ru',
|
|
||||||
'localhost',
|
|
||||||
'127.0.0.1',
|
|
||||||
'192.168.31.88' # Добавьте IP сервера
|
|
||||||
]
|
|
||||||
|
|
||||||
# Важно для работы за прокси
|
|
||||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
|
||||||
SECURE_SSL_REDIRECT = True
|
|
||||||
|
|
||||||
# Дополнительная безопасность
|
|
||||||
SECURE_BROWSER_XSS_FILTER = True
|
|
||||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
|
||||||
SECURE_HSTS_SECONDS = 31536000
|
|
||||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
|
||||||
SECURE_HSTS_PRELOAD = True
|
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [
|
|
||||||
'https://nikdizell.ru',
|
|
||||||
'https://www.nikdizell.ru',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Application definition
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
'django.contrib.admin',
|
|
||||||
'django.contrib.auth',
|
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
|
||||||
'django.contrib.messages',
|
|
||||||
'django.contrib.staticfiles',
|
|
||||||
'programmer.apps.ProgrammerConfig',
|
|
||||||
'django_bootstrap5',
|
|
||||||
'django_extensions',
|
|
||||||
'django.contrib.sites',
|
|
||||||
'django.contrib.sitemaps',
|
|
||||||
]
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
'django.middleware.security.SecurityMiddleware',
|
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
|
||||||
'django.middleware.common.CommonMiddleware',
|
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
|
||||||
'programmer.middleware.PageViewMiddleware',
|
|
||||||
]
|
|
||||||
|
|
||||||
ROOT_URLCONF = 'OneCprogsite.urls'
|
|
||||||
|
|
||||||
# Кастомный middleware для CSP
|
|
||||||
class CSPMiddleware:
|
|
||||||
def __init__(self, get_response):
|
|
||||||
self.get_response = get_response
|
|
||||||
|
|
||||||
def __call__(self, request):
|
|
||||||
response = self.get_response(request)
|
|
||||||
response['Content-Security-Policy'] = "frame-ancestors 'self' https://metrika.yandex.ru https://metrika.yandex.by https://metrica.yandex.com https://metrica.yandex.com.tr https://*.webvisor.com"
|
|
||||||
return response
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
|
||||||
'DIRS': [],
|
|
||||||
'APP_DIRS': True,
|
|
||||||
'OPTIONS': {
|
|
||||||
'context_processors': [
|
|
||||||
'django.template.context_processors.debug',
|
|
||||||
'django.template.context_processors.request',
|
|
||||||
'django.contrib.auth.context_processors.auth',
|
|
||||||
'django.contrib.messages.context_processors.messages',
|
|
||||||
'programmer.context_processors.menu_processor',
|
|
||||||
'programmer.context_processors.contact_info',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
WSGI_APPLICATION = 'OneCprogsite.wsgi.application'
|
|
||||||
|
|
||||||
|
|
||||||
# Database
|
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
'default': {
|
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
|
||||||
'NAME': 'App',
|
|
||||||
'USER': 'postgres',
|
|
||||||
'PASSWORD': 'NikDi94Zell',
|
|
||||||
'HOST': 'postgres',
|
|
||||||
'PORT': 5432,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Password validation
|
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# Internationalization
|
|
||||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
|
||||||
|
|
||||||
LANGUAGE_CODE = 'ru'
|
|
||||||
|
|
||||||
TIME_ZONE = 'Europe/Moscow'
|
|
||||||
USE_I18N = True
|
|
||||||
USE_I18N = True
|
|
||||||
USE_TZ = True
|
|
||||||
|
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
|
||||||
|
|
||||||
STATIC_URL = 'static/'
|
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
|
||||||
STATICFILES_DIRS = []
|
|
||||||
|
|
||||||
# Default primary key field type
|
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
||||||
|
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
|
||||||
MEDIA_URL = '/media/'
|
|
||||||
|
|
||||||
# Настройки email
|
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|
||||||
# EMAIL_HOST = 'smtp.yandex.ru' # или smtp.gmail.com, smtp.mail.ru
|
|
||||||
# EMAIL_PORT = 587
|
|
||||||
# EMAIL_USE_TLS = True
|
|
||||||
# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'it@yandex.ru')
|
|
||||||
# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'tifdctkrcjcqwxyc')
|
|
||||||
# DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
|
||||||
# SERVER_EMAIL = EMAIL_HOST_USER
|
|
||||||
|
|
||||||
EMAIL_HOST = 'smtp.gmail.com' # или smtp.gmail.com, smtp.mail.ru
|
|
||||||
EMAIL_PORT = 587
|
|
||||||
EMAIL_USE_TLS = True
|
|
||||||
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'nikdizell@gmail.com')
|
|
||||||
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'qvmw yccb msqv mmpj')
|
|
||||||
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
|
||||||
SERVER_EMAIL = EMAIL_HOST_USER
|
|
||||||
|
|
||||||
# Email для уведомлений (можно указать несколько через запятую)
|
|
||||||
# ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'nikdizell@gmail.com').split(',')
|
|
||||||
ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'it@nserdyuk.ru').split(',')
|
|
||||||
|
|
||||||
|
|
||||||
@ -1,39 +0,0 @@
|
|||||||
"""
|
|
||||||
URL configuration for OneCprogsite project.
|
|
||||||
|
|
||||||
The `urlpatterns` list routes URLs to views. For more information please see:
|
|
||||||
https://docs.djangoproject.com/en/4.2/topics/http/urls/
|
|
||||||
Examples:
|
|
||||||
Function views
|
|
||||||
1. Add an import: from my_app import views
|
|
||||||
2. Add a URL to urlpatterns: path('', views.home, name='home')
|
|
||||||
Class-based views
|
|
||||||
1. Add an import: from other_app.views import Home
|
|
||||||
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
|
|
||||||
Including another URLconf
|
|
||||||
1. Import the include() function: from django.urls import include, path
|
|
||||||
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
|
|
||||||
"""
|
|
||||||
from django.conf.urls.static import static
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.urls import path
|
|
||||||
|
|
||||||
from OneCprogsite import settings
|
|
||||||
from programmer.views import *
|
|
||||||
from django.urls import path, include
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('admin/', admin.site.urls),
|
|
||||||
path('', include('programmer.urls')),
|
|
||||||
# path('', index, name='home'),
|
|
||||||
# path('about/', about, name='about'),
|
|
||||||
# path('solution/', solution, name='solution'),
|
|
||||||
# path('ability/', ability, name='ability'),
|
|
||||||
# path('recall/', recall, name='recall'),
|
|
||||||
# path('post/<int:post_id>', show_post, name='post'),
|
|
||||||
]
|
|
||||||
|
|
||||||
if settings.DEBUG:
|
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
||||||
|
|
||||||
handler404 = pageNotFound
|
|
||||||
@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
WSGI config for OneCprogsite project.
|
|
||||||
|
|
||||||
It exposes the WSGI callable as a module-level variable named ``application``.
|
|
||||||
|
|
||||||
For more information on this file, see
|
|
||||||
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'OneCprogsite.settings')
|
|
||||||
|
|
||||||
application = get_wsgi_application()
|
|
||||||
@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""Django's command-line utility for administrative tasks."""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Run administrative tasks."""
|
|
||||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'OneCprogsite.settings')
|
|
||||||
try:
|
|
||||||
from django.core.management import execute_from_command_line
|
|
||||||
except ImportError as exc:
|
|
||||||
raise ImportError(
|
|
||||||
"Couldn't import Django. Are you sure it's installed and "
|
|
||||||
"available on your PYTHONPATH environment variable? Did you "
|
|
||||||
"forget to activate a virtual environment?"
|
|
||||||
) from exc
|
|
||||||
execute_from_command_line(sys.argv)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
@ -1,169 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.utils.html import format_html
|
|
||||||
from django.urls import path
|
|
||||||
from django.shortcuts import render
|
|
||||||
from django.http import HttpResponseRedirect
|
|
||||||
from django.contrib import messages
|
|
||||||
from .models import *
|
|
||||||
|
|
||||||
|
|
||||||
class ProgrammerAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'title', 'time_create', 'photo', 'is_published')
|
|
||||||
list_display_links = ('id', 'title')
|
|
||||||
search_fields = ('title', 'content')
|
|
||||||
list_editable = ('is_published',)
|
|
||||||
list_filter = ('time_create', 'is_published')
|
|
||||||
|
|
||||||
|
|
||||||
class RecallAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'title', 'time_create', 'scan', 'is_published')
|
|
||||||
list_display_links = ('id', 'title')
|
|
||||||
search_fields = ('title', 'content')
|
|
||||||
list_editable = ('is_published',)
|
|
||||||
list_filter = ('time_create', 'is_published')
|
|
||||||
|
|
||||||
|
|
||||||
class SolutionAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'title', 'time_create', 'is_published')
|
|
||||||
list_display_links = ('id', 'title')
|
|
||||||
search_fields = ('title', 'description', 'implementation')
|
|
||||||
list_editable = ('is_published',)
|
|
||||||
list_filter = ('time_create', 'is_published')
|
|
||||||
|
|
||||||
|
|
||||||
class HomeAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'title', 'time_create', 'is_published')
|
|
||||||
list_display_links = ('id', 'title')
|
|
||||||
search_fields = ('title', 'content')
|
|
||||||
list_editable = ('is_published',)
|
|
||||||
list_filter = ('time_create', 'is_published')
|
|
||||||
|
|
||||||
|
|
||||||
@admin.register(CallbackRequest)
|
|
||||||
class CallbackAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('name', 'phone', 'email', 'time_create', 'is_processed', 'is_read', 'new_badge')
|
|
||||||
list_display_links = ('name', 'phone')
|
|
||||||
list_editable = ('is_processed', 'is_read')
|
|
||||||
list_filter = ('time_create', 'is_processed', 'is_read')
|
|
||||||
search_fields = ('name', 'phone', 'email')
|
|
||||||
readonly_fields = ('time_create',)
|
|
||||||
actions = ['mark_as_read', 'mark_as_unread', 'mark_as_processed']
|
|
||||||
actions = ['mark_as_read', 'mark_as_unread', 'mark_as_processed', 'resend_notification']
|
|
||||||
|
|
||||||
def resend_notification(self, request, queryset):
|
|
||||||
from .utils.email_notifications import send_callback_notification
|
|
||||||
count = 0
|
|
||||||
for callback in queryset:
|
|
||||||
success = send_callback_notification(callback)
|
|
||||||
if success:
|
|
||||||
count += 1
|
|
||||||
self.message_user(request, f'Уведомления отправлены для {count} заявок')
|
|
||||||
|
|
||||||
resend_notification.short_description = "Переотправить email уведомления"
|
|
||||||
|
|
||||||
def new_badge(self, obj):
|
|
||||||
if not obj.is_read:
|
|
||||||
return format_html('<span style="color: red; font-weight: bold;">🆕 НОВАЯ</span>')
|
|
||||||
return ""
|
|
||||||
|
|
||||||
new_badge.short_description = 'Статус'
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
# Показываем количество непрочитанных в заголовке
|
|
||||||
unread_count = CallbackRequest.objects.filter(is_read=False).count()
|
|
||||||
if unread_count > 0:
|
|
||||||
self.message_user(
|
|
||||||
request,
|
|
||||||
f'У вас {unread_count} непрочитанных заявок!',
|
|
||||||
messages.WARNING
|
|
||||||
)
|
|
||||||
return super().get_queryset(request)
|
|
||||||
|
|
||||||
def mark_as_read(self, request, queryset):
|
|
||||||
updated = queryset.update(is_read=True)
|
|
||||||
self.message_user(request, f'{updated} заявок отмечены как прочитанные')
|
|
||||||
|
|
||||||
mark_as_read.short_description = "Отметить как прочитанные"
|
|
||||||
|
|
||||||
def mark_as_unread(self, request, queryset):
|
|
||||||
updated = queryset.update(is_read=False)
|
|
||||||
self.message_user(request, f'{updated} заявок отмечены как непрочитанные')
|
|
||||||
|
|
||||||
mark_as_unread.short_description = "Отметить как непрочитанные"
|
|
||||||
|
|
||||||
def mark_as_processed(self, request, queryset):
|
|
||||||
updated = queryset.update(is_processed=True)
|
|
||||||
self.message_user(request, f'{updated} заявок отмечены как обработанные')
|
|
||||||
|
|
||||||
mark_as_processed.short_description = "Отметить как обработанные"
|
|
||||||
|
|
||||||
# Добавляем кастомное представление для статистики
|
|
||||||
def get_urls(self):
|
|
||||||
urls = super().get_urls()
|
|
||||||
custom_urls = [
|
|
||||||
path('callback-stats/', self.admin_site.admin_view(self.callback_stats), name='callback_stats'),
|
|
||||||
]
|
|
||||||
return custom_urls + urls
|
|
||||||
|
|
||||||
def callback_stats(self, request):
|
|
||||||
today = timezone.now().date()
|
|
||||||
week_ago = today - timezone.timedelta(days=7)
|
|
||||||
|
|
||||||
stats = {
|
|
||||||
'total': CallbackRequest.objects.count(),
|
|
||||||
'today': CallbackRequest.objects.filter(time_create__date=today).count(),
|
|
||||||
'week': CallbackRequest.objects.filter(time_create__date__gte=week_ago).count(),
|
|
||||||
'unread': CallbackRequest.objects.filter(is_read=False).count(),
|
|
||||||
'unprocessed': CallbackRequest.objects.filter(is_processed=False).count(),
|
|
||||||
}
|
|
||||||
|
|
||||||
context = {
|
|
||||||
**self.admin_site.each_context(request),
|
|
||||||
'title': 'Статистика заявок',
|
|
||||||
'stats': stats,
|
|
||||||
}
|
|
||||||
return render(request, 'admin/callback_stats.html', context)
|
|
||||||
|
|
||||||
class ProgrammerAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'title', 'time_create', 'photo', 'is_published')
|
|
||||||
list_display_links = ('id', 'title')
|
|
||||||
search_fields = ('title', 'content')
|
|
||||||
list_editable = ('is_published',)
|
|
||||||
list_filter = ('time_create', 'is_published')
|
|
||||||
|
|
||||||
class RecallAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'title', 'time_create', 'scan', 'is_published')
|
|
||||||
list_display_links = ('id', 'title')
|
|
||||||
search_fields = ('title', 'content')
|
|
||||||
list_editable = ('is_published',)
|
|
||||||
list_filter = ('time_create', 'is_published')
|
|
||||||
|
|
||||||
class SolutionAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'title', 'time_create', 'is_published')
|
|
||||||
list_display_links = ('id', 'title')
|
|
||||||
search_fields = ('title', 'description', 'implementation')
|
|
||||||
list_editable = ('is_published',)
|
|
||||||
list_filter = ('time_create', 'is_published')
|
|
||||||
|
|
||||||
class HomeAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ('id', 'title', 'time_create', 'is_published')
|
|
||||||
list_display_links = ('id', 'title')
|
|
||||||
search_fields = ('title', 'content')
|
|
||||||
list_editable = ('is_published',)
|
|
||||||
list_filter = ('time_create', 'is_published')
|
|
||||||
|
|
||||||
@admin.register(PageView)
|
|
||||||
class PageViewAdmin(admin.ModelAdmin):
|
|
||||||
list_display = ['url', 'timestamp', 'ip_address']
|
|
||||||
list_filter = ['timestamp', 'url']
|
|
||||||
search_fields = ['url', 'ip_address']
|
|
||||||
date_hierarchy = 'timestamp'
|
|
||||||
|
|
||||||
def get_queryset(self, request):
|
|
||||||
return super().get_queryset(request).order_by('-timestamp')
|
|
||||||
|
|
||||||
admin.site.register(Competence, ProgrammerAdmin)
|
|
||||||
admin.site.register(Recall, RecallAdmin)
|
|
||||||
admin.site.register(Solution, SolutionAdmin)
|
|
||||||
admin.site.register(Home, HomeAdmin)
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
from django.apps import AppConfig
|
|
||||||
|
|
||||||
|
|
||||||
class ProgrammerConfig(AppConfig):
|
|
||||||
default_auto_field = 'django.db.models.BigAutoField'
|
|
||||||
name = 'programmer'
|
|
||||||
verbose_name = 'Программисты'
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
from .views import menu
|
|
||||||
from django.conf import settings
|
|
||||||
|
|
||||||
|
|
||||||
def menu_processor(request):
|
|
||||||
return {'menu': menu}
|
|
||||||
|
|
||||||
def contact_info(request):
|
|
||||||
return {
|
|
||||||
'CONTACT_EMAIL': getattr(settings, 'CONTACT_EMAIL', 'it@nserdyuk.ru'),
|
|
||||||
'CONTACT_PHONE': getattr(settings, 'CONTACT_PHONE', '+7 (960) 469-40-88'),
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
from django import forms
|
|
||||||
from .models import CallbackRequest
|
|
||||||
|
|
||||||
|
|
||||||
class CallbackForm(forms.ModelForm):
|
|
||||||
class Meta:
|
|
||||||
model = CallbackRequest
|
|
||||||
fields = ['name', 'phone', 'email', 'question']
|
|
||||||
widgets = {
|
|
||||||
'name': forms.TextInput(attrs={
|
|
||||||
'class': 'form-input',
|
|
||||||
'placeholder': 'Ваше имя'
|
|
||||||
}),
|
|
||||||
'phone': forms.TextInput(attrs={
|
|
||||||
'class': 'form-input',
|
|
||||||
'placeholder': '+7 (___) ___-__-__'
|
|
||||||
}),
|
|
||||||
'email': forms.EmailInput(attrs={
|
|
||||||
'class': 'form-input',
|
|
||||||
'placeholder': 'your@email.com'
|
|
||||||
}),
|
|
||||||
'question': forms.Textarea(attrs={
|
|
||||||
'class': 'form-textarea',
|
|
||||||
'placeholder': 'Опишите ваш вопрос или задачу...',
|
|
||||||
'rows': 4
|
|
||||||
}),
|
|
||||||
}
|
|
||||||
labels = {
|
|
||||||
'name': 'Имя',
|
|
||||||
'phone': 'Телефон',
|
|
||||||
'email': 'Электронная почта',
|
|
||||||
'question': 'Ваш вопрос'
|
|
||||||
}
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
# programmer/management/commands/send_daily_summary.py
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.utils import timezone
|
|
||||||
from programmer.utils.email_notifications import send_daily_summary
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = 'Отправляет ежедневную сводку по заявкам на email'
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument(
|
|
||||||
'--test',
|
|
||||||
action='store_true',
|
|
||||||
help='Тестовая отправка (не учитывает реальные данные)',
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
if options['test']:
|
|
||||||
self.stdout.write(self.style.WARNING('Тестовая отправка ежедневной сводки...'))
|
|
||||||
# Здесь можно добавить тестовые данные
|
|
||||||
else:
|
|
||||||
self.stdout.write('Отправка ежедневной сводки...')
|
|
||||||
|
|
||||||
success = send_daily_summary()
|
|
||||||
|
|
||||||
if success:
|
|
||||||
self.stdout.write(
|
|
||||||
self.style.SUCCESS(f'Ежедневная сводка отправлена успешно! Время: {timezone.now()}')
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.stdout.write(
|
|
||||||
self.style.WARNING('Ежедневная сводка не отправлена (нет данных или ошибка)')
|
|
||||||
)
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
# programmer/management/commands/test_email.py
|
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.conf import settings
|
|
||||||
from programmer.utils.email_notifications import send_test_email
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = 'Test email configuration'
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
self.stdout.write("Testing email configuration...")
|
|
||||||
|
|
||||||
# Проверяем настройки
|
|
||||||
self.stdout.write(f"EMAIL_HOST: {settings.EMAIL_HOST}")
|
|
||||||
self.stdout.write(f"EMAIL_PORT: {settings.EMAIL_PORT}")
|
|
||||||
self.stdout.write(f"EMAIL_HOST_USER: {settings.EMAIL_HOST_USER}")
|
|
||||||
self.stdout.write(f"ADMIN_EMAILS: {settings.ADMIN_EMAILS}")
|
|
||||||
|
|
||||||
# Тестируем отправку
|
|
||||||
success = send_test_email()
|
|
||||||
|
|
||||||
if success:
|
|
||||||
self.stdout.write(
|
|
||||||
self.style.SUCCESS('✅ Test email sent successfully!')
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
self.stdout.write(
|
|
||||||
self.style.ERROR('❌ Failed to send test email. Check your email settings.')
|
|
||||||
)
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.contrib.sitemaps import Sitemap
|
|
||||||
from django.urls import reverse
|
|
||||||
from programmer.models import Home, Solution, Competence, Recall
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
help = 'Test sitemap generation'
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
from programmer.sitemaps import sitemaps
|
|
||||||
|
|
||||||
self.stdout.write('Testing sitemap generation...')
|
|
||||||
|
|
||||||
for name, sitemap in sitemaps.items():
|
|
||||||
self.stdout.write(f'\n{name}:')
|
|
||||||
items = sitemap().items()
|
|
||||||
self.stdout.write(f' Items found: {len(items)}')
|
|
||||||
|
|
||||||
for item in items[:3]: # Показываем первые 3 элемента
|
|
||||||
try:
|
|
||||||
url = sitemap().location(item)
|
|
||||||
self.stdout.write(f' - {url}')
|
|
||||||
except Exception as e:
|
|
||||||
self.stdout.write(f' - Error: {e}')
|
|
||||||
|
|
||||||
self.stdout.write('\nSitemap test completed!')
|
|
||||||
@ -1,54 +0,0 @@
|
|||||||
from .models import PageView, Visitor
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.db import transaction
|
|
||||||
|
|
||||||
|
|
||||||
class PageViewMiddleware:
|
|
||||||
def __init__(self, get_response):
|
|
||||||
self.get_response = get_response
|
|
||||||
|
|
||||||
def __call__(self, request):
|
|
||||||
# Игнорируем статические файлы и админку
|
|
||||||
if not request.path.startswith('/static/') and not request.path.startswith('/admin/'):
|
|
||||||
self.track_page_view(request)
|
|
||||||
|
|
||||||
response = self.get_response(request)
|
|
||||||
return response
|
|
||||||
|
|
||||||
def track_page_view(self, request):
|
|
||||||
try:
|
|
||||||
with transaction.atomic():
|
|
||||||
# Сохраняем просмотр страницы
|
|
||||||
PageView.objects.create(
|
|
||||||
url=request.path,
|
|
||||||
ip_address=self.get_client_ip(request),
|
|
||||||
user_agent=request.META.get('HTTP_USER_AGENT', ''),
|
|
||||||
referer=request.META.get('HTTP_REFERER', '')
|
|
||||||
)
|
|
||||||
|
|
||||||
# Обновляем статистику посетителя
|
|
||||||
ip = self.get_client_ip(request)
|
|
||||||
visitor, created = Visitor.objects.get_or_create(
|
|
||||||
ip_address=ip,
|
|
||||||
defaults={
|
|
||||||
'first_visit': timezone.now(),
|
|
||||||
'last_visit': timezone.now()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if not created:
|
|
||||||
visitor.last_visit = timezone.now()
|
|
||||||
visitor.visit_count += 1
|
|
||||||
visitor.save()
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
# Логируем ошибку, но не прерываем выполнение
|
|
||||||
print(f"Error tracking page view: {e}")
|
|
||||||
|
|
||||||
def get_client_ip(self, request):
|
|
||||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
|
||||||
if x_forwarded_for:
|
|
||||||
ip = x_forwarded_for.split(',')[0]
|
|
||||||
else:
|
|
||||||
ip = request.META.get('REMOTE_ADDR')
|
|
||||||
return ip
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2023-11-23 12:47
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
initial = True
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Competence',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('title', models.CharField(max_length=255)),
|
|
||||||
('content', models.TextField(blank=True)),
|
|
||||||
('photo', models.ImageField(upload_to='photos/%Y/%m/%d/')),
|
|
||||||
('time_create', models.DateTimeField(auto_now_add=True)),
|
|
||||||
('time_update', models.DateTimeField(auto_now=True)),
|
|
||||||
('is_publiched', models.BooleanField(default=True)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,47 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2023-11-24 08:03
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('programmer', '0001_initial'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='competence',
|
|
||||||
options={'ordering': ['time_create', 'title'], 'verbose_name': 'Компитенция', 'verbose_name_plural': 'Компитенции'},
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='competence',
|
|
||||||
name='content',
|
|
||||||
field=models.TextField(blank=True, verbose_name='Компетенция'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='competence',
|
|
||||||
name='is_publiched',
|
|
||||||
field=models.BooleanField(default=True, verbose_name='Опубликован'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='competence',
|
|
||||||
name='photo',
|
|
||||||
field=models.ImageField(upload_to='photos/%Y/%m/%d/', verbose_name='Фото'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='competence',
|
|
||||||
name='time_create',
|
|
||||||
field=models.DateTimeField(auto_now_add=True, verbose_name='Дата создания'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='competence',
|
|
||||||
name='time_update',
|
|
||||||
field=models.DateTimeField(auto_now=True, verbose_name='Дата изменения'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='competence',
|
|
||||||
name='title',
|
|
||||||
field=models.CharField(max_length=255, verbose_name='Программист'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2023-11-24 11:54
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('programmer', '0002_alter_competence_options_alter_competence_content_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Recall',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('title', models.CharField(max_length=255, verbose_name='Организация')),
|
|
||||||
('content', models.TextField(blank=True, verbose_name='Отзыв')),
|
|
||||||
('photo', models.ImageField(upload_to='photos/%Y/%m/%d/', verbose_name='Фото')),
|
|
||||||
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
|
||||||
('time_update', models.DateTimeField(auto_now=True, verbose_name='Дата изменения')),
|
|
||||||
('is_published', models.BooleanField(default=True, verbose_name='Опубликован')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Отзыв',
|
|
||||||
'verbose_name_plural': 'Отзывы',
|
|
||||||
'ordering': ['time_create', 'title'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='competence',
|
|
||||||
old_name='is_publiched',
|
|
||||||
new_name='is_published',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2023-11-24 12:02
|
|
||||||
|
|
||||||
from django.db import migrations
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('programmer', '0003_recall_rename_is_publiched_competence_is_published'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.RenameField(
|
|
||||||
model_name='recall',
|
|
||||||
old_name='photo',
|
|
||||||
new_name='scan',
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2023-11-24 12:19
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('programmer', '0004_rename_photo_recall_scan'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Recall',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('title', models.CharField(max_length=255, verbose_name='Организация')),
|
|
||||||
('content', models.TextField(blank=True, verbose_name='Отзыв')),
|
|
||||||
('scan', models.ImageField(upload_to='photos/%Y/%m/%d/', verbose_name='Скан')),
|
|
||||||
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
|
||||||
('time_update', models.DateTimeField(auto_now=True, verbose_name='Дата изменения')),
|
|
||||||
('is_published', models.BooleanField(default=True, verbose_name='Опубликован')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Отзыв',
|
|
||||||
'verbose_name_plural': 'Отзывы',
|
|
||||||
'ordering': ['time_create', 'title'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2023-11-25 09:18
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('programmer', '0005_auto_20231124_1519'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='recall',
|
|
||||||
name='scan',
|
|
||||||
field=models.ImageField(upload_to='scan/%Y/%m/%d/', verbose_name='Фото'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2023-11-25 09:51
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('programmer', '0006_alter_recall_scan'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Solution',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('title', models.CharField(max_length=255, verbose_name='Наименование')),
|
|
||||||
('description', models.TextField(blank=True, verbose_name='Описание')),
|
|
||||||
('implementation', models.TextField(blank=True, verbose_name='Реализация')),
|
|
||||||
('closing', models.TextField(blank=True, verbose_name='Заключение')),
|
|
||||||
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
|
||||||
('time_update', models.DateTimeField(auto_now=True, verbose_name='Дата изменения')),
|
|
||||||
('is_published', models.BooleanField(default=True, verbose_name='Опубликован')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Проекты',
|
|
||||||
'verbose_name_plural': 'Проекты',
|
|
||||||
'ordering': ['time_create', 'title'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,30 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2023-11-25 10:45
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('programmer', '0007_solution'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Home',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('title', models.CharField(max_length=255, verbose_name='Наименование')),
|
|
||||||
('content', models.TextField(blank=True, verbose_name='Статья')),
|
|
||||||
('home_image', models.ImageField(upload_to='home_image/%Y/%m/%d/', verbose_name='Фото')),
|
|
||||||
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
|
||||||
('time_update', models.DateTimeField(auto_now=True, verbose_name='Дата изменения')),
|
|
||||||
('is_published', models.BooleanField(default=True, verbose_name='Опубликован')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Главная страница',
|
|
||||||
'verbose_name_plural': 'Главная страница',
|
|
||||||
'ordering': ['time_create', 'title'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,34 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2025-11-09 12:00
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('programmer', '0008_home'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='CallbackRequest',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('name', models.CharField(max_length=100, verbose_name='Имя')),
|
|
||||||
('phone', models.CharField(max_length=20, verbose_name='Телефон')),
|
|
||||||
('email', models.EmailField(max_length=254, verbose_name='Электронная почта')),
|
|
||||||
('question', models.TextField(verbose_name='Ваш вопрос')),
|
|
||||||
('time_create', models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')),
|
|
||||||
('is_processed', models.BooleanField(default=False, verbose_name='Обработано')),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'verbose_name': 'Заявка на звонок',
|
|
||||||
'verbose_name_plural': 'Заявки на звонок',
|
|
||||||
'ordering': ['-time_create'],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.AlterModelOptions(
|
|
||||||
name='competence',
|
|
||||||
options={'ordering': ['time_create', 'title'], 'verbose_name': 'Компетенция', 'verbose_name_plural': 'Компетенции'},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,23 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2025-11-09 12:09
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('programmer', '0009_callbackrequest_alter_competence_options'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='callbackrequest',
|
|
||||||
name='email',
|
|
||||||
field=models.EmailField(blank=True, max_length=254, null=True, verbose_name='Электронная почта'),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
|
||||||
model_name='callbackrequest',
|
|
||||||
name='question',
|
|
||||||
field=models.TextField(blank=True, verbose_name='Ваш вопрос'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2025-11-12 11:43
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
import django.utils.timezone
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('programmer', '0010_alter_callbackrequest_email_and_more'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='Visitor',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('ip_address', models.GenericIPAddressField()),
|
|
||||||
('first_visit', models.DateTimeField(default=django.utils.timezone.now)),
|
|
||||||
('last_visit', models.DateTimeField(default=django.utils.timezone.now)),
|
|
||||||
('visit_count', models.IntegerField(default=1)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'indexes': [models.Index(fields=['ip_address'], name='programmer__ip_addr_2c6dca_idx')],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
migrations.CreateModel(
|
|
||||||
name='PageView',
|
|
||||||
fields=[
|
|
||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
||||||
('url', models.CharField(max_length=500)),
|
|
||||||
('timestamp', models.DateTimeField(default=django.utils.timezone.now)),
|
|
||||||
('ip_address', models.GenericIPAddressField()),
|
|
||||||
('user_agent', models.TextField(blank=True)),
|
|
||||||
('referer', models.CharField(blank=True, max_length=500)),
|
|
||||||
],
|
|
||||||
options={
|
|
||||||
'indexes': [models.Index(fields=['url', 'timestamp'], name='programmer__url_9a41b2_idx'), models.Index(fields=['timestamp'], name='programmer__timesta_070072_idx')],
|
|
||||||
},
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2025-11-14 09:37
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('programmer', '0011_visitor_pageview'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='callbackrequest',
|
|
||||||
name='is_read',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Прочитано'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
# Generated by Django 4.2.7 on 2025-11-14 10:03
|
|
||||||
|
|
||||||
from django.db import migrations, models
|
|
||||||
|
|
||||||
|
|
||||||
class Migration(migrations.Migration):
|
|
||||||
|
|
||||||
dependencies = [
|
|
||||||
('programmer', '0012_callbackrequest_is_read'),
|
|
||||||
]
|
|
||||||
|
|
||||||
operations = [
|
|
||||||
migrations.AddField(
|
|
||||||
model_name='callbackrequest',
|
|
||||||
name='notification_sent',
|
|
||||||
field=models.BooleanField(default=False, verbose_name='Уведомление отправлено'),
|
|
||||||
),
|
|
||||||
]
|
|
||||||
@ -1,180 +0,0 @@
|
|||||||
from django.db import models
|
|
||||||
from django.urls import reverse
|
|
||||||
from django.utils import timezone
|
|
||||||
from django.db.models.signals import post_save, post_delete
|
|
||||||
from django.dispatch import receiver
|
|
||||||
from django.core.cache import cache
|
|
||||||
from .utils.email_notifications import send_callback_notification
|
|
||||||
|
|
||||||
|
|
||||||
class Recall(models.Model):
|
|
||||||
title = models.CharField(max_length=255, verbose_name='Организация')
|
|
||||||
content = models.TextField(blank=True, verbose_name='Отзыв')
|
|
||||||
scan = models.ImageField(upload_to="scan/%Y/%m/%d/", verbose_name='Фото')
|
|
||||||
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
|
|
||||||
time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения')
|
|
||||||
is_published = models.BooleanField(default=True, verbose_name='Опубликован')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.title
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('post', kwargs={'post_id': self.pk})
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = 'Отзыв'
|
|
||||||
verbose_name_plural = 'Отзывы'
|
|
||||||
ordering = ['time_create', 'title']
|
|
||||||
|
|
||||||
def get_seo_title(self):
|
|
||||||
return f"Отзыв от {self.title} | Программист 1С"
|
|
||||||
|
|
||||||
def get_seo_description(self):
|
|
||||||
if self.content:
|
|
||||||
clean_content = self.content[:160].replace('\n', ' ').strip()
|
|
||||||
return f"Отзыв о работе программиста 1С от {self.title}. {clean_content}..."
|
|
||||||
return f"Отзыв клиента {self.title} о работе программиста 1С Николая Сердюк"
|
|
||||||
|
|
||||||
|
|
||||||
class Competence(models.Model):
|
|
||||||
title = models.CharField(max_length=255, verbose_name='Программист')
|
|
||||||
content = models.TextField(blank=True, verbose_name='Компетенция')
|
|
||||||
photo = models.ImageField(upload_to="photos/%Y/%m/%d/", verbose_name='Фото')
|
|
||||||
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
|
|
||||||
time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения')
|
|
||||||
is_published = models.BooleanField(default=True, verbose_name='Опубликован')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.title
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('post', kwargs={'post_id': self.pk})
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = 'Компетенция'
|
|
||||||
verbose_name_plural = 'Компетенции'
|
|
||||||
ordering = ['time_create', 'title']
|
|
||||||
|
|
||||||
|
|
||||||
class Solution(models.Model):
|
|
||||||
title = models.CharField(max_length=255, verbose_name='Наименование')
|
|
||||||
description = models.TextField(blank=True, verbose_name='Описание')
|
|
||||||
implementation = models.TextField(blank=True, verbose_name='Реализация')
|
|
||||||
closing = models.TextField(blank=True, verbose_name='Заключение')
|
|
||||||
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
|
|
||||||
time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения')
|
|
||||||
is_published = models.BooleanField(default=True, verbose_name='Опубликован')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.title
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('post', kwargs={'post_id': self.pk})
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = 'Проекты'
|
|
||||||
verbose_name_plural = 'Проекты'
|
|
||||||
ordering = ['time_create', 'title']
|
|
||||||
|
|
||||||
def get_seo_title(self):
|
|
||||||
"""Генерирует SEO-заголовок для проекта"""
|
|
||||||
return f"Проект: {self.title} | Автоматизация 1С"
|
|
||||||
|
|
||||||
def get_seo_description(self):
|
|
||||||
"""Генерирует SEO-описание для проекта"""
|
|
||||||
if self.description:
|
|
||||||
clean_desc = self.description[:160].replace('\n', ' ').strip()
|
|
||||||
return f"Проект автоматизации: {self.title}. {clean_desc}..."
|
|
||||||
return f"Реализация проекта {self.title} - программист 1С Николай Сердюк"
|
|
||||||
|
|
||||||
def get_meta_keywords(self):
|
|
||||||
"""Автоматические ключевые слова для проекта"""
|
|
||||||
base_keywords = ["проект 1С", "автоматизация 1С", "внедрение 1С"]
|
|
||||||
title_words = self.title.lower().split()
|
|
||||||
return base_keywords + title_words
|
|
||||||
|
|
||||||
|
|
||||||
class Home(models.Model):
|
|
||||||
title = models.CharField(max_length=255, verbose_name='Наименование')
|
|
||||||
content = models.TextField(blank=True, verbose_name='Статья')
|
|
||||||
home_image = models.ImageField(upload_to="home_image/%Y/%m/%d/", verbose_name='Фото')
|
|
||||||
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
|
|
||||||
time_update = models.DateTimeField(auto_now=True, verbose_name='Дата изменения')
|
|
||||||
is_published = models.BooleanField(default=True, verbose_name='Опубликован')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.title
|
|
||||||
|
|
||||||
def get_absolute_url(self):
|
|
||||||
return reverse('post', kwargs={'post_id': self.pk})
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = 'Главная страница'
|
|
||||||
verbose_name_plural = 'Главная страница'
|
|
||||||
ordering = ['time_create', 'title']
|
|
||||||
|
|
||||||
|
|
||||||
class CallbackRequest(models.Model):
|
|
||||||
name = models.CharField(max_length=100, verbose_name='Имя')
|
|
||||||
phone = models.CharField(max_length=20, verbose_name='Телефон')
|
|
||||||
email = models.EmailField(blank=True, null=True, verbose_name='Электронная почта') # Сделать необязательным
|
|
||||||
question = models.TextField(blank=True, verbose_name='Ваш вопрос') # Сделать необязательным
|
|
||||||
time_create = models.DateTimeField(auto_now_add=True, verbose_name='Дата создания')
|
|
||||||
is_processed = models.BooleanField(default=False, verbose_name='Обработано')
|
|
||||||
is_read = models.BooleanField(default=False, verbose_name='Прочитано')
|
|
||||||
notification_sent = models.BooleanField(default=False, verbose_name='Уведомление отправлено')
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f"{self.name} - {self.phone}"
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
verbose_name = 'Заявка на звонок'
|
|
||||||
verbose_name_plural = 'Заявки на звонок'
|
|
||||||
ordering = ['-time_create']
|
|
||||||
|
|
||||||
|
|
||||||
# Сигнал для отправки уведомления при создании заявки
|
|
||||||
@receiver(post_save, sender=CallbackRequest)
|
|
||||||
def send_callback_email_notification(sender, instance, created, **kwargs):
|
|
||||||
if created and not instance.notification_sent:
|
|
||||||
# Отправляем email уведомление
|
|
||||||
success = send_callback_notification(instance)
|
|
||||||
if success:
|
|
||||||
instance.notification_sent = True
|
|
||||||
# Сохраняем без повторного вызова сигнала
|
|
||||||
sender.objects.filter(pk=instance.pk).update(notification_sent=True)
|
|
||||||
|
|
||||||
|
|
||||||
class PageView(models.Model):
|
|
||||||
url = models.CharField(max_length=500)
|
|
||||||
timestamp = models.DateTimeField(default=timezone.now)
|
|
||||||
ip_address = models.GenericIPAddressField()
|
|
||||||
user_agent = models.TextField(blank=True)
|
|
||||||
referer = models.CharField(max_length=500, blank=True)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [
|
|
||||||
models.Index(fields=['url', 'timestamp']),
|
|
||||||
models.Index(fields=['timestamp']),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class Visitor(models.Model):
|
|
||||||
ip_address = models.GenericIPAddressField()
|
|
||||||
first_visit = models.DateTimeField(default=timezone.now)
|
|
||||||
last_visit = models.DateTimeField(default=timezone.now)
|
|
||||||
visit_count = models.IntegerField(default=1)
|
|
||||||
|
|
||||||
class Meta:
|
|
||||||
indexes = [
|
|
||||||
models.Index(fields=['ip_address']),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@receiver([post_save, post_delete], sender=Home)
|
|
||||||
@receiver([post_save, post_delete], sender=Solution)
|
|
||||||
@receiver([post_save, post_delete], sender=Competence)
|
|
||||||
@receiver([post_save, post_delete], sender=Recall)
|
|
||||||
def clear_sitemap_cache(sender, **kwargs):
|
|
||||||
"""Очищаем кэш sitemap при изменении контента"""
|
|
||||||
cache.delete('sitemap_cache')
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
from django.contrib.sitemaps import Sitemap
|
|
||||||
from django.urls import reverse
|
|
||||||
from .models import Home, Solution, Competence, Recall
|
|
||||||
|
|
||||||
class StaticViewSitemap(Sitemap):
|
|
||||||
priority = 1.0
|
|
||||||
changefreq = 'monthly'
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return ['home', 'about', 'solution', 'ability', 'recall']
|
|
||||||
|
|
||||||
def location(self, item):
|
|
||||||
return reverse(item)
|
|
||||||
|
|
||||||
class HomeSitemap(Sitemap):
|
|
||||||
changefreq = 'weekly'
|
|
||||||
priority = 1.0
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return Home.objects.filter(is_published=True)
|
|
||||||
|
|
||||||
def lastmod(self, obj):
|
|
||||||
return obj.time_update
|
|
||||||
|
|
||||||
# УБИРАЕМ метод location - используем default
|
|
||||||
# Django автоматически сгенерирует правильные URL
|
|
||||||
|
|
||||||
class SolutionSitemap(Sitemap):
|
|
||||||
changefreq = 'weekly'
|
|
||||||
priority = 0.9
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return Solution.objects.filter(is_published=True)
|
|
||||||
|
|
||||||
def lastmod(self, obj):
|
|
||||||
return obj.time_update
|
|
||||||
|
|
||||||
class CompetenceSitemap(Sitemap):
|
|
||||||
changefreq = 'monthly'
|
|
||||||
priority = 0.8
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return Competence.objects.filter(is_published=True)
|
|
||||||
|
|
||||||
def lastmod(self, obj):
|
|
||||||
return obj.time_update
|
|
||||||
|
|
||||||
class RecallSitemap(Sitemap):
|
|
||||||
changefreq = 'monthly'
|
|
||||||
priority = 0.7
|
|
||||||
|
|
||||||
def items(self):
|
|
||||||
return Recall.objects.filter(is_published=True)
|
|
||||||
|
|
||||||
def lastmod(self, obj):
|
|
||||||
return obj.time_update
|
|
||||||
|
|
||||||
# Упрощаем sitemaps - убираем HomeSitemap если он дублирует главную
|
|
||||||
sitemaps = {
|
|
||||||
'static': StaticViewSitemap,
|
|
||||||
'solutions': SolutionSitemap,
|
|
||||||
'competence': CompetenceSitemap,
|
|
||||||
'recall': RecallSitemap,
|
|
||||||
}
|
|
||||||
@ -1,299 +0,0 @@
|
|||||||
/* competence.css - Стили для страницы компетенций */
|
|
||||||
|
|
||||||
/* Основные стили для страницы компетенций */
|
|
||||||
.competence-item {
|
|
||||||
display: flex;
|
|
||||||
gap: 2rem;
|
|
||||||
align-items: flex-start;
|
|
||||||
padding: 2rem;
|
|
||||||
background: var(--bg-card);
|
|
||||||
border-radius: var(--radius-xl);
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
border-left: 4px solid var(--secondary);
|
|
||||||
transition: var(--transition);
|
|
||||||
border: 1px solid var(--border-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-item:hover {
|
|
||||||
transform: translateX(8px);
|
|
||||||
box-shadow: var(--shadow-xl);
|
|
||||||
border-color: var(--primary-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-scan-wrapper {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-scan-container {
|
|
||||||
width: 280px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
overflow: hidden;
|
|
||||||
border: 2px solid var(--border-light);
|
|
||||||
transition: var(--transition);
|
|
||||||
position: relative;
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-scan-container:hover {
|
|
||||||
transform: translateY(-4px) scale(1.02);
|
|
||||||
box-shadow: var(--shadow-xl);
|
|
||||||
border-color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-scan {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
transition: var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-content {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-title {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
font-weight: 700;
|
|
||||||
color: var(--text-primary);
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
background: var(--gradient-primary);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-description {
|
|
||||||
line-height: 1.7;
|
|
||||||
font-size: 1.05rem;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-description p {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-description p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-hint {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: linear-gradient(transparent, rgba(0,0,0,0.8));
|
|
||||||
color: white;
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
opacity: 0;
|
|
||||||
transition: var(--transition);
|
|
||||||
transform: translateY(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-scan-container:hover .scan-hint {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для модального окна с изображением компетенций */
|
|
||||||
.modal.competence-modal {
|
|
||||||
background-color: rgba(15, 19, 31, 0.95);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal .modal-content {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
max-width: 95vw;
|
|
||||||
max-height: 95vh;
|
|
||||||
margin: 2% auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal .modal-header {
|
|
||||||
background: var(--bg-card);
|
|
||||||
border-bottom: 2px solid var(--border-light);
|
|
||||||
padding: 1.5rem 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal .modal-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
background: var(--gradient-primary);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal .modal-body {
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-image {
|
|
||||||
max-width: 90vw;
|
|
||||||
max-height: 80vh;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
box-shadow: var(--shadow-xl);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Анимации для модального окна */
|
|
||||||
.modal.competence-modal {
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal.active {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal .modal-content {
|
|
||||||
transform: scale(0.7);
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal.active .modal-content {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Улучшенные тени и границы */
|
|
||||||
.competence-scan-container {
|
|
||||||
border: 2px solid var(--border-light);
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-scan-container:hover {
|
|
||||||
border-color: var(--primary-light);
|
|
||||||
box-shadow: var(--shadow-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Адаптивность для мобильных устройств */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.competence-item {
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 1.5rem;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-scan-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-title {
|
|
||||||
font-size: 1.375rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-image {
|
|
||||||
max-width: 95vw;
|
|
||||||
max-height: 70vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal .modal-content {
|
|
||||||
margin: 10% auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal .modal-header {
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.competence-item {
|
|
||||||
padding: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-scan-container {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-title {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal .modal-content {
|
|
||||||
margin: 5% auto;
|
|
||||||
max-width: 98vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal .modal-body {
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal .modal-header {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal .modal-header h3 {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для светлой темы */
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
.modal.competence-modal {
|
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.competence-modal .modal-header {
|
|
||||||
background: var(--bg-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-hint {
|
|
||||||
background: linear-gradient(transparent, rgba(0,0,0,0.7));
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-item {
|
|
||||||
background: var(--bg-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-description {
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Улучшенные стили для сетки компетенций */
|
|
||||||
.competence-grid {
|
|
||||||
display: grid;
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-grid .modern-card {
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-grid .modern-card::before {
|
|
||||||
height: 4px;
|
|
||||||
background: var(--gradient-secondary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Анимации появления */
|
|
||||||
.fade-in {
|
|
||||||
animation: fadeInUp 0.8s ease-out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes fadeInUp {
|
|
||||||
from {
|
|
||||||
opacity: 0;
|
|
||||||
transform: translateY(40px);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,228 +0,0 @@
|
|||||||
/* recall.css - Стили для страницы отзывов */
|
|
||||||
|
|
||||||
/* Основные стили для страницы отзывов */
|
|
||||||
.recall-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-content {
|
|
||||||
display: flex;
|
|
||||||
gap: 2rem;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-scan-wrapper {
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-scan-container {
|
|
||||||
width: 280px;
|
|
||||||
cursor: pointer;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
overflow: hidden;
|
|
||||||
border: 2px solid var(--border-light);
|
|
||||||
transition: var(--transition);
|
|
||||||
position: relative;
|
|
||||||
box-shadow: var(--shadow-md);
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-scan-container:hover {
|
|
||||||
transform: translateY(-4px) scale(1.02);
|
|
||||||
box-shadow: var(--shadow-xl);
|
|
||||||
border-color: var(--primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-scan {
|
|
||||||
width: 100%;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
transition: var(--transition);
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-text {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
line-height: 1.7;
|
|
||||||
font-size: 1.05rem;
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-text p {
|
|
||||||
margin-bottom: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-text p:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-hint {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
background: linear-gradient(transparent, rgba(0,0,0,0.8));
|
|
||||||
color: white;
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: center;
|
|
||||||
opacity: 0;
|
|
||||||
transition: var(--transition);
|
|
||||||
transform: translateY(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-scan-container:hover .scan-hint {
|
|
||||||
opacity: 1;
|
|
||||||
transform: translateY(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для модального окна с изображением */
|
|
||||||
.modal.image-modal {
|
|
||||||
background-color: rgba(15, 19, 31, 0.95);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal .modal-content {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
box-shadow: none;
|
|
||||||
max-width: 95vw;
|
|
||||||
max-height: 95vh;
|
|
||||||
margin: 2% auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal .modal-header {
|
|
||||||
background: var(--bg-card);
|
|
||||||
border-bottom: 2px solid var(--border-light);
|
|
||||||
padding: 1.5rem 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal .modal-header h3 {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 1.25rem;
|
|
||||||
font-weight: 600;
|
|
||||||
background: var(--gradient-primary);
|
|
||||||
-webkit-background-clip: text;
|
|
||||||
-webkit-text-fill-color: transparent;
|
|
||||||
background-clip: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal .modal-body {
|
|
||||||
padding: 1rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-image {
|
|
||||||
max-width: 90vw;
|
|
||||||
max-height: 80vh;
|
|
||||||
width: auto;
|
|
||||||
height: auto;
|
|
||||||
display: block;
|
|
||||||
margin: 0 auto;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
box-shadow: var(--shadow-xl);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Анимации для модального окна */
|
|
||||||
.modal.image-modal {
|
|
||||||
transition: opacity 0.3s ease;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal.active {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal .modal-content {
|
|
||||||
transform: scale(0.7);
|
|
||||||
transition: transform 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal.active .modal-content {
|
|
||||||
transform: scale(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Улучшенные тени и границы */
|
|
||||||
.recall-scan-container {
|
|
||||||
border: 2px solid var(--border-light);
|
|
||||||
box-shadow: var(--shadow-lg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-scan-container:hover {
|
|
||||||
border-color: var(--primary-light);
|
|
||||||
box-shadow: var(--shadow-xl);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Адаптивность для мобильных устройств */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.recall-content {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-scan-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-scan-wrapper {
|
|
||||||
order: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-image {
|
|
||||||
max-width: 95vw;
|
|
||||||
max-height: 70vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal .modal-content {
|
|
||||||
margin: 10% auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal .modal-header {
|
|
||||||
padding: 1rem 1.5rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.recall-scan-container {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal .modal-content {
|
|
||||||
margin: 5% auto;
|
|
||||||
max-width: 98vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal .modal-body {
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal .modal-header {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal .modal-header h3 {
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Стили для светлой темы */
|
|
||||||
@media (prefers-color-scheme: light) {
|
|
||||||
.modal.image-modal {
|
|
||||||
background-color: rgba(0, 0, 0, 0.8);
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal.image-modal .modal-header {
|
|
||||||
background: var(--bg-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scan-hint {
|
|
||||||
background: linear-gradient(transparent, rgba(0,0,0,0.7));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 9.7 KiB |
|
Before Width: | Height: | Size: 14 KiB |
|
Before Width: | Height: | Size: 329 B |
@ -1,74 +0,0 @@
|
|||||||
// recall.js - Скрипты для страницы отзывов
|
|
||||||
function openModal(imageUrl, title) {
|
|
||||||
console.log('Opening modal with:', imageUrl);
|
|
||||||
const modal = document.getElementById('imageModal');
|
|
||||||
const modalImg = document.getElementById('modalImage');
|
|
||||||
const modalTitle = document.getElementById('modalTitle');
|
|
||||||
|
|
||||||
if (modal && modalImg) {
|
|
||||||
modal.style.display = "block";
|
|
||||||
modalImg.src = imageUrl;
|
|
||||||
if (title && modalTitle) {
|
|
||||||
modalTitle.textContent = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавляем класс для анимации
|
|
||||||
setTimeout(() => {
|
|
||||||
modal.classList.add('active');
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
// Подстраиваем размер изображения
|
|
||||||
adjustModalImageSize();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
const modal = document.getElementById('imageModal');
|
|
||||||
if (modal) {
|
|
||||||
modal.classList.remove('active');
|
|
||||||
setTimeout(() => {
|
|
||||||
modal.style.display = "none";
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function adjustModalImageSize() {
|
|
||||||
const modalImg = document.getElementById('modalImage');
|
|
||||||
const modalContent = document.querySelector('.modal-content');
|
|
||||||
|
|
||||||
if (modalImg && modalContent) {
|
|
||||||
const maxWidth = window.innerWidth * 0.9;
|
|
||||||
const maxHeight = window.innerHeight * 0.8;
|
|
||||||
|
|
||||||
modalImg.style.maxWidth = `${maxWidth}px`;
|
|
||||||
modalImg.style.maxHeight = `${maxHeight}px`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Инициализация после загрузки DOM
|
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
|
||||||
// Закрытие модального окна при клике вне изображения
|
|
||||||
document.addEventListener('click', function(event) {
|
|
||||||
const modal = document.getElementById('imageModal');
|
|
||||||
if (event.target === modal) {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Закрытие по ESC
|
|
||||||
document.addEventListener('keydown', function(event) {
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Адаптация размера изображения при изменении размера окна
|
|
||||||
window.addEventListener('resize', function() {
|
|
||||||
const modalImg = document.getElementById('modalImage');
|
|
||||||
if (modalImg && modalImg.src) {
|
|
||||||
adjustModalImageSize();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('Recall page scripts initialized');
|
|
||||||
});
|
|
||||||
@ -1,79 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ru">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<title>{% block title %}Админ-панель - Статистика{% endblock %}</title>
|
|
||||||
{% load django_bootstrap5 %}
|
|
||||||
{% bootstrap_css %}
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background: #f8f9fa;
|
|
||||||
font-family: 'Inter', sans-serif;
|
|
||||||
}
|
|
||||||
.admin-header {
|
|
||||||
background: #343a40;
|
|
||||||
color: white;
|
|
||||||
padding: 1rem 0;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
.stats-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
gap: 1.5rem;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
.stat-card {
|
|
||||||
background: white;
|
|
||||||
padding: 1.5rem;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.stat-number {
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #007bff;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
table {
|
|
||||||
width: 100%;
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
||||||
}
|
|
||||||
th, td {
|
|
||||||
padding: 1rem;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
th {
|
|
||||||
background: #f8f9fa;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="admin-header">
|
|
||||||
<div class="container">
|
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
|
||||||
<h1>{% block page_title %}Админ-панель{% endblock %}</h1>
|
|
||||||
<div>
|
|
||||||
<a href="{% url 'home' %}" class="btn btn-outline-light btn-sm">На сайт</a>
|
|
||||||
<a href="{% url 'admin:index' %}" class="btn btn-outline-light btn-sm">Django Admin</a>
|
|
||||||
<a href="{% url 'admin:logout' %}" class="btn btn-outline-light btn-sm">Выйти</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="container">
|
|
||||||
{% bootstrap_messages %}
|
|
||||||
{% block content %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% bootstrap_javascript %}
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,26 +0,0 @@
|
|||||||
{% extends "admin/base.html" %}
|
|
||||||
|
|
||||||
{% block title %}{{ title }} | {{ site_title|default:_('Django site admin') }}{% endblock %}
|
|
||||||
|
|
||||||
{% block branding %}
|
|
||||||
<h1 id="site-name"><a href="{% url 'admin:index' %}">{{ site_header|default:_('Django administration') }}</a></h1>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block nav-global %}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block userlinks %}
|
|
||||||
{% load programmer_tags %}
|
|
||||||
|
|
||||||
<!-- Уведомление о новых заявках -->
|
|
||||||
{% get_unread_callbacks as unread_callbacks %}
|
|
||||||
{% if unread_callbacks %}
|
|
||||||
<a href="{% url 'admin:programmer_callbackrequest_changelist' %}" style="color: #dc3545; font-weight: bold;">
|
|
||||||
🚨 {{ unread_callbacks }} новых заявок
|
|
||||||
</a> /
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<a href="{% url 'callback_stats' %}">📊 Статистика заявок</a> /
|
|
||||||
<a href="{% url 'statistics' %}">📈 Посещения</a> /
|
|
||||||
{{ block.super }}
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
{% extends "admin/base_site.html" %}
|
|
||||||
|
|
||||||
{% block title %}Статистика заявок{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="module">
|
|
||||||
<h1>📊 Статистика заявок на обратный звонок</h1>
|
|
||||||
|
|
||||||
<div class="stats-grid" style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin: 2rem 0;">
|
|
||||||
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
|
|
||||||
<h3>📋 Всего заявок</h3>
|
|
||||||
<p style="font-size: 2rem; font-weight: bold; color: #007bff; margin: 0;">{{ stats.total }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
|
|
||||||
<h3>📅 Сегодня</h3>
|
|
||||||
<p style="font-size: 2rem; font-weight: bold; color: #28a745; margin: 0;">{{ stats.today }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
|
|
||||||
<h3>📈 За неделю</h3>
|
|
||||||
<p style="font-size: 2rem; font-weight: bold; color: #17a2b8; margin: 0;">{{ stats.week }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
|
|
||||||
<h3>🆕 Непрочитанные</h3>
|
|
||||||
<p style="font-size: 2rem; font-weight: bold; color: #dc3545; margin: 0;">{{ stats.unread }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat-card" style="background: white; padding: 1.5rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center;">
|
|
||||||
<h3>⏳ В обработке</h3>
|
|
||||||
<p style="font-size: 2rem; font-weight: bold; color: #ffc107; margin: 0;">{{ stats.unprocessed }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-top: 2rem;">
|
|
||||||
<a href="{% url 'admin:programmer_callbackrequest_changelist' %}" class="button" style="background: #007bff; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">
|
|
||||||
📋 Перейти к списку заявок
|
|
||||||
</a>
|
|
||||||
<a href="{% url 'admin:index' %}" class="button" style="background: #6c757d; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px; margin-left: 10px;">
|
|
||||||
🏠 На главную админки
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,131 +0,0 @@
|
|||||||
{% extends 'admin/base.html' %}
|
|
||||||
{% load programmer_tags %}
|
|
||||||
|
|
||||||
{% block title %}Статистика посещений{% endblock %}
|
|
||||||
{% block page_title %}Статистика посещений{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<!-- Уведомления о заявках -->
|
|
||||||
{% get_unread_callbacks as unread_callbacks %}
|
|
||||||
{% get_today_callbacks as today_callbacks %}
|
|
||||||
{% if unread_callbacks > 0 %}
|
|
||||||
<div class="alert alert-warning alert-dismissible fade show" role="alert">
|
|
||||||
<h4>🚨 Внимание!</h4>
|
|
||||||
<p>
|
|
||||||
У вас <strong>{{ unread_callbacks }}</strong> непрочитанных заявок на обратный звонок
|
|
||||||
{% if today_callbacks > 0 %}
|
|
||||||
({{ today_callbacks }} из них сегодня)
|
|
||||||
{% endif %}
|
|
||||||
</p>
|
|
||||||
<a href="{% url 'admin:programmer_callbackrequest_changelist' %}" class="btn btn-warning btn-sm">
|
|
||||||
📋 Перейти к заявкам
|
|
||||||
</a>
|
|
||||||
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="row mb-4">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="stats-grid">
|
|
||||||
<div class="stat-card">
|
|
||||||
<h3>📊 Просмотров сегодня</h3>
|
|
||||||
<p class="stat-number">{{ today_views }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<h3>📈 Просмотров за неделю</h3>
|
|
||||||
<p class="stat-number">{{ weekly_views }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<h3>👥 Уникальных посетителей</h3>
|
|
||||||
<p class="stat-number">{{ unique_visitors }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<h3>🕒 Всего просмотров</h3>
|
|
||||||
<p class="stat-number">{{ total_views }}</p>
|
|
||||||
</div>
|
|
||||||
<!-- Добавляем статистику по заявкам -->
|
|
||||||
<div class="stat-card">
|
|
||||||
<h3>📞 Заявок сегодня</h3>
|
|
||||||
<p class="stat-number">{{ today_callbacks }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="stat-card">
|
|
||||||
<h3>📋 Всего заявок</h3>
|
|
||||||
<p class="stat-number">{% get_unread_callbacks %}/{{ total_callbacks }}</p>
|
|
||||||
<small>(непрочитанные/всего)</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Остальной код статистики остается без изменений -->
|
|
||||||
<div class="row">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3>🔥 Популярные страницы (за неделю)</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body p-0">
|
|
||||||
<table class="table table-striped mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Страница</th>
|
|
||||||
<th>Просмотров</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for page in popular_pages %}
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<code>{{ page.url }}</code>
|
|
||||||
{% if page.url == '/' %}
|
|
||||||
<span class="badge bg-primary">Главная</span>
|
|
||||||
{% endif %}
|
|
||||||
</td>
|
|
||||||
<td><strong>{{ page.views }}</strong></td>
|
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="2" class="text-center text-muted">Нет данных за выбранный период</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="row mt-4">
|
|
||||||
<div class="col-12">
|
|
||||||
<div class="card">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3>📋 Последние посещения</h3>
|
|
||||||
</div>
|
|
||||||
<div class="card-body p-0">
|
|
||||||
<table class="table table-striped mb-0">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Время</th>
|
|
||||||
<th>Страница</th>
|
|
||||||
<th>IP-адрес</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{% for view in recent_views %}
|
|
||||||
<tr>
|
|
||||||
<td>{{ view.timestamp|date:"d.m.Y H:i" }}</td>
|
|
||||||
<td><code>{{ view.url }}</code></td>
|
|
||||||
<td><small>{{ view.ip_address }}</small></td>
|
|
||||||
</tr>
|
|
||||||
{% empty %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="3" class="text-center text-muted">Нет данных</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,57 +0,0 @@
|
|||||||
<!-- templates/emails/callback_notification.html -->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
|
||||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
||||||
.header { background: linear-gradient(135deg, #FF6B00 0%, #0055A5 100%); color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0; }
|
|
||||||
.content { background: #f9f9f9; padding: 20px; border-radius: 0 0 10px 10px; }
|
|
||||||
.alert { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 15px 0; }
|
|
||||||
.info-box { background: white; padding: 15px; border-radius: 5px; margin: 10px 0; border-left: 4px solid #007bff; }
|
|
||||||
.btn { display: inline-block; background: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin: 10px 0; }
|
|
||||||
.footer { text-align: center; margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 12px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>🚨 Новая заявка на сайте</h1>
|
|
||||||
<p>Требуется ваше внимание!</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<div class="alert">
|
|
||||||
<strong>⚠️ Срочно!</strong> Пользователь оставил заявку на обратный звонок.
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="info-box">
|
|
||||||
<h3>📋 Информация о заявке:</h3>
|
|
||||||
<p><strong>👤 Имя:</strong> {{ callback.name }}</p>
|
|
||||||
<p><strong>📞 Телефон:</strong> {{ callback.phone }}</p>
|
|
||||||
{% if callback.email %}
|
|
||||||
<p><strong>📧 Email:</strong> {{ callback.email }}</p>
|
|
||||||
{% endif %}
|
|
||||||
{% if callback.question %}
|
|
||||||
<p><strong>❓ Вопрос:</strong><br>{{ callback.question }}</p>
|
|
||||||
{% endif %}
|
|
||||||
<p><strong>🕒 Время отправки:</strong> {{ callback.time_create|date:"d.m.Y H:i" }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="text-align: center; margin: 20px 0;">
|
|
||||||
<a href="http://{{ site_url }}/admin/programmer/callbackrequest/" class="btn">
|
|
||||||
📋 Перейти к заявкам в админке
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p><em>Не забудьте отметить заявку как обработанную после связи с клиентом!</em></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<p>Это автоматическое уведомление от системы сайта.</p>
|
|
||||||
<p>Если вы получили это письмо по ошибке, пожалуйста, свяжитесь с администратором.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,60 +0,0 @@
|
|||||||
<!-- templates/emails/daily_summary.html -->
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8">
|
|
||||||
<style>
|
|
||||||
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
|
|
||||||
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
||||||
.header { background: linear-gradient(135deg, #28a745 0%, #20c997 100%); color: white; padding: 20px; text-align: center; border-radius: 10px 10px 0 0; }
|
|
||||||
.content { background: #f9f9f9; padding: 20px; border-radius: 0 0 10px 10px; }
|
|
||||||
.stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 15px; margin: 20px 0; }
|
|
||||||
.stat-card { background: white; padding: 15px; border-radius: 8px; text-align: center; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
|
|
||||||
.stat-number { font-size: 2rem; font-weight: bold; margin: 0; }
|
|
||||||
.alert { background: #fff3cd; border: 1px solid #ffeaa7; padding: 15px; border-radius: 5px; margin: 15px 0; }
|
|
||||||
.btn { display: inline-block; background: #007bff; color: white; padding: 12px 24px; text-decoration: none; border-radius: 5px; margin: 10px 0; }
|
|
||||||
.footer { text-align: center; margin-top: 20px; padding-top: 20px; border-top: 1px solid #ddd; color: #666; font-size: 12px; }
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="container">
|
|
||||||
<div class="header">
|
|
||||||
<h1>📊 Ежедневная сводка</h1>
|
|
||||||
<p>Статистика заявок за {{ yesterday|date:"d.m.Y" }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content">
|
|
||||||
<div class="stats-grid">
|
|
||||||
<div class="stat-card">
|
|
||||||
<h3>📅 Вчерашние заявки</h3>
|
|
||||||
<p class="stat-number" style="color: #28a745;">{{ yesterday_callbacks }}</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="stat-card">
|
|
||||||
<h3>⏳ Ожидают обработки</h3>
|
|
||||||
<p class="stat-number" style="color: #ffc107;">{{ unprocessed_callbacks }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if unprocessed_callbacks > 0 %}
|
|
||||||
<div class="alert">
|
|
||||||
<strong>⚠️ Внимание!</strong> У вас есть {{ unprocessed_callbacks }} необработанных заявок.
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div style="text-align: center; margin: 20px 0;">
|
|
||||||
<a href="http://{{ site_url }}/admin/programmer/callbackrequest/" class="btn">
|
|
||||||
📋 Управление заявками
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p><em>Не забудьте обработать все pending заявки!</em></p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="footer">
|
|
||||||
<p>Ежедневная автоматическая сводка от системы сайта.</p>
|
|
||||||
<p>Дата формирования: {{ today|date:"d.m.Y H:i" }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,154 +0,0 @@
|
|||||||
{% extends 'programmer/base.html' %}
|
|
||||||
{% load django_bootstrap5 %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="page-header">
|
|
||||||
<h1 class="page-title">{{title}}</h1>
|
|
||||||
<p class="page-subtitle">Профессиональный программист 1С с более чем 10-летним опытом</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="content-card">
|
|
||||||
<div class="about-header">
|
|
||||||
<h2>Николай Сердюк</h2>
|
|
||||||
<p class="subtitle">Разработчик 1С</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="about-section">
|
|
||||||
<h3>🚀 Опыт работы</h3>
|
|
||||||
<p class="card-subtitle">Более 10 лет успешной работы в разработке и сопровождении систем на платформе 1С</p>
|
|
||||||
|
|
||||||
<div class="experience-item mt-3">
|
|
||||||
<h4>Основные направления:</h4>
|
|
||||||
<div class="skills-grid">
|
|
||||||
<div class="skill-category">
|
|
||||||
<h4>💻 Разработка</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Разработка и доработка конфигураций 1С</li>
|
|
||||||
<li>Создание внешних обработок и отчетов</li>
|
|
||||||
<li>Кастомизация под бизнес-процессы</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="skill-category">
|
|
||||||
<h4>🔗 Интеграция</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Интеграция 1С с веб-сервисами</li>
|
|
||||||
<li>Связь с сайтами и мобильными приложениями</li>
|
|
||||||
<li>API и веб-сервисы</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="skill-category">
|
|
||||||
<h4>⚡ Оптимизация</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Оптимизация бизнес-процессов</li>
|
|
||||||
<li>Ускорение работы баз данных</li>
|
|
||||||
<li>Автоматизация рутинных операций</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="about-section">
|
|
||||||
<h3>🛠 Технологии и навыки</h3>
|
|
||||||
<div class="skills-grid">
|
|
||||||
<div class="skill-category">
|
|
||||||
<h4>🎯 1С Разработка</h4>
|
|
||||||
<ul>
|
|
||||||
<li>1С:Предприятие 8.3</li>
|
|
||||||
<li>Управление торговлей</li>
|
|
||||||
<li>Бухгалтерия предприятия</li>
|
|
||||||
<li>Зарплата и управление персоналом</li>
|
|
||||||
<li>Внешние обработки и отчеты</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="skill-category">
|
|
||||||
<h4>🔧 Дополнительные технологии</h4>
|
|
||||||
<ul>
|
|
||||||
<li>SQL и оптимизация запросов</li>
|
|
||||||
<li>Веб-сервисы и API</li>
|
|
||||||
<li>XML, JSON, REST</li>
|
|
||||||
<li>Системное администрирование</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="about-section">
|
|
||||||
<h3>📈 Проекты и достижения</h3>
|
|
||||||
<p class="card-subtitle">Успешно реализовал более 50 проектов различной сложности</p>
|
|
||||||
|
|
||||||
<div class="skills-grid mt-3">
|
|
||||||
<div class="skill-category">
|
|
||||||
<h4>🏆 Ключевые проекты</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Автоматизация учетных систем для предприятий</li>
|
|
||||||
<li>Интеграция 1С с сайтами и мобильными приложениями</li>
|
|
||||||
<li>Разработка кастомизированных отчетов и дашбордов</li>
|
|
||||||
<li>Оптимизация производительности баз данных</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="about-section">
|
|
||||||
<h3>📞 Контакты</h3>
|
|
||||||
<div class="contacts">
|
|
||||||
<div class="skills-grid">
|
|
||||||
<div class="skill-category">
|
|
||||||
<h4>📧 Электронная почта</h4>
|
|
||||||
<p><strong>{{ CONTACT_EMAIL }}</strong></p>
|
|
||||||
</div>
|
|
||||||
<div class="skill-category">
|
|
||||||
<h4>📱 Телефон</h4>
|
|
||||||
<p><strong>{{ CONTACT_PHONE }}</strong></p>
|
|
||||||
</div>
|
|
||||||
<div class="skill-category">
|
|
||||||
<h4>💬 Telegram</h4>
|
|
||||||
<p><strong><a href="https://t.me/odinesina_prog" target="_blank" class="btn btn-primary" style="display: inline-flex; padding: 0.5rem 1rem;">@odinesina_prog</a></strong></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="text-center mt-4">
|
|
||||||
<div class="card-actions">
|
|
||||||
<a href="{% url 'solution' %}" class="btn btn-primary">📂 Посмотреть мои проекты</a>
|
|
||||||
<a href="{% url 'recall' %}" class="btn btn-secondary">⭐ Отзывы клиентов</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "Service",
|
|
||||||
"serviceType": "1С программирование",
|
|
||||||
"provider": {
|
|
||||||
"@type": "Person",
|
|
||||||
"name": "Николай Сердюк"
|
|
||||||
},
|
|
||||||
"areaServed": "Россия",
|
|
||||||
"hasOfferCatalog": {
|
|
||||||
"@type": "OfferCatalog",
|
|
||||||
"name": "Услуги программиста 1С",
|
|
||||||
"itemListElement": [
|
|
||||||
{
|
|
||||||
"@type": "Offer",
|
|
||||||
"itemOffered": {
|
|
||||||
"@type": "Service",
|
|
||||||
"name": "Разработка конфигураций 1С"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"@type": "Offer",
|
|
||||||
"itemOffered": {
|
|
||||||
"@type": "Service",
|
|
||||||
"name": "Интеграция 1С с веб-сервисами"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,578 +0,0 @@
|
|||||||
{% load static %}
|
|
||||||
{% load programmer_tags %}
|
|
||||||
{% load django_bootstrap5 %}
|
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="ru">
|
|
||||||
<head>
|
|
||||||
<title>{{title}}</title>
|
|
||||||
<!-- Основные мета-теги -->
|
|
||||||
<meta name="description" content="{% block meta_description %}{{ meta_description|default:'Профессиональный программист 1С с более чем 10-летним опытом. Разработка, интеграция и оптимизация систем 1С.' }}{% endblock %}">
|
|
||||||
<meta name="keywords" content="{% block meta_keywords %}{{ meta_keywords|default:'программист 1С, разработка 1С, интеграция 1С, оптимизация 1С, 1С предприятие' }}{% endblock %}">
|
|
||||||
<meta name="author" content="Николай Сердюк">
|
|
||||||
|
|
||||||
<!-- Open Graph для соцсетей -->
|
|
||||||
<meta property="og:title" content="{{title}}">
|
|
||||||
<meta property="og:description" content="{% block og_description %}{{ meta_description|default:'Профессиональный программист 1С с более чем 10-летним опытом' }}{% endblock %}">
|
|
||||||
<meta property="og:type" content="website">
|
|
||||||
<meta property="og:url" content="{{ request.build_absolute_uri }}">
|
|
||||||
<meta property="og:image" content="{% static 'programmer/images/og-image.jpg' %}">
|
|
||||||
<meta property="og:site_name" content="Программист 1С - Николай Сердюк">
|
|
||||||
|
|
||||||
<!-- Twitter Card -->
|
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
|
||||||
<meta name="twitter:title" content="{{title}}">
|
|
||||||
<meta name="twitter:description" content="{% block twitter_description %}{{ meta_description|default:'Профессиональный программист 1С с более чем 10-летним опытом' }}{% endblock %}">
|
|
||||||
<meta name="twitter:image" content="{% static 'programmer/images/og-image.jpg' %}">
|
|
||||||
|
|
||||||
<!-- Дополнительные SEO-теги -->
|
|
||||||
<meta name="robots" content="index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1">
|
|
||||||
<link rel="canonical" href="{{ request.build_absolute_uri }}">
|
|
||||||
{% bootstrap_css %}
|
|
||||||
|
|
||||||
<!-- Основной CSS файл (темная тема по умолчанию) -->
|
|
||||||
<link type="text/css" href="{% static 'programmer/css/styles_w.css' %}" rel="stylesheet" id="theme-css" />
|
|
||||||
|
|
||||||
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
|
||||||
<link rel="shortcut icon" href="{% static 'programmer/images/main.ico' %}" type="image/x-icon">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
|
||||||
|
|
||||||
<style>
|
|
||||||
/* Временные стили для тумблера и мобильного меню */
|
|
||||||
.theme-switcher {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle-checkbox {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle-label {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
width: 60px;
|
|
||||||
height: 30px;
|
|
||||||
background: #222738;
|
|
||||||
border: 2px solid #2D3447;
|
|
||||||
border-radius: 25px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle-slider {
|
|
||||||
position: absolute;
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
background: #FF6B00;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
left: 2px;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle-checkbox:checked + .theme-toggle-label .theme-toggle-slider {
|
|
||||||
transform: translateX(30px);
|
|
||||||
background: #0055A5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-icon {
|
|
||||||
position: absolute;
|
|
||||||
font-size: 12px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-icon.sun {
|
|
||||||
left: 8px;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-icon.moon {
|
|
||||||
right: 8px;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle-checkbox:checked + .theme-toggle-label .theme-icon.sun {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle-checkbox:checked + .theme-toggle-label .theme-icon.moon {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.nav-actions {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Мобильное меню */
|
|
||||||
.mobile-menu-btn {
|
|
||||||
display: none;
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
color: var(--text-primary);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-menu-btn:hover {
|
|
||||||
background: var(--border-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-menu-overlay {
|
|
||||||
display: none;
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
background: rgba(0, 0, 0, 0.5);
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-menu {
|
|
||||||
position: fixed;
|
|
||||||
top: 0;
|
|
||||||
right: -100%;
|
|
||||||
width: 300px;
|
|
||||||
height: 100%;
|
|
||||||
background: var(--bg-card);
|
|
||||||
box-shadow: -5px 0 15px rgba(0, 0, 0, 0.3);
|
|
||||||
transition: right 0.3s ease;
|
|
||||||
z-index: 1000;
|
|
||||||
overflow-y: auto;
|
|
||||||
padding: 2rem 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-menu.active {
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-menu-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
padding-bottom: 1rem;
|
|
||||||
border-bottom: 1px solid var(--border-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-menu-close {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: var(--text-primary);
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 0.5rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-menu-close:hover {
|
|
||||||
background: var(--border-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-nav-menu {
|
|
||||||
list-style: none;
|
|
||||||
margin-bottom: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-nav-item {
|
|
||||||
margin-bottom: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-nav-link {
|
|
||||||
display: block;
|
|
||||||
padding: 1rem;
|
|
||||||
color: var(--text-primary);
|
|
||||||
text-decoration: none;
|
|
||||||
border-radius: 8px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-nav-link:hover,
|
|
||||||
.mobile-nav-link.active {
|
|
||||||
background: var(--primary);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mobile-nav-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
padding-top: 1rem;
|
|
||||||
border-top: 1px solid var(--border-light);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ===== MOBILE RESPONSIVE STYLES ===== */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.hero-title {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-subtitle {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
font-size: 2.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-subtitle {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
padding: 0 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-2 {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modern-card {
|
|
||||||
padding: 1.5rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
font-size: 1.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-card {
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-section {
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.skills-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-item {
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 1.5rem;
|
|
||||||
gap: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-scan-container {
|
|
||||||
width: 100%;
|
|
||||||
max-width: 280px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-header {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.recall-meta {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-content {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: 2rem;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer-contacts p {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-actions {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
width: 100%;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-content {
|
|
||||||
margin: 5% auto;
|
|
||||||
max-width: 95%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-header {
|
|
||||||
padding: 1.5rem 1.5rem 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modal-body {
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-content {
|
|
||||||
padding: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.breadcrumbs {
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 480px) {
|
|
||||||
.hero-title {
|
|
||||||
font-size: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero-subtitle {
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-title {
|
|
||||||
font-size: 1.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modern-card {
|
|
||||||
padding: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-card {
|
|
||||||
padding: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.about-section {
|
|
||||||
padding: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card-title {
|
|
||||||
font-size: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.competence-scan-container {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<!-- Дополнительные CSS файлы для конкретных страниц -->
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<!-- Google Analytics -->
|
|
||||||
<script async src="https://www.googletagmanager.com/gtag/js?id=G-X3W9YSQHRM"></script>
|
|
||||||
<script>
|
|
||||||
window.dataLayer = window.dataLayer || [];
|
|
||||||
function gtag(){dataLayer.push(arguments);}
|
|
||||||
gtag('js', new Date());
|
|
||||||
|
|
||||||
gtag('config', 'G-X3W9YSQHRM');
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- Яндекс.Метрика -->
|
|
||||||
<script type="text/javascript">
|
|
||||||
(function(m,e,t,r,i,k,a){
|
|
||||||
m[i]=m[i]||function(){(m[i].a=m[i].a||[]).push(arguments)};
|
|
||||||
m[i].l=1*new Date();
|
|
||||||
for (var j = 0; j < document.scripts.length; j++) {if (document.scripts[j].src === r) { return; }}
|
|
||||||
k=e.createElement(t),a=e.getElementsByTagName(t)[0],k.async=1,k.src=r,a.parentNode.insertBefore(k,a)
|
|
||||||
})(window, document,'script','https://mc.yandex.ru/metrika/tag.js?id=105278924', 'ym');
|
|
||||||
|
|
||||||
ym(105278924, 'init', {ssr:true, webvisor:true, clickmap:true, ecommerce:"dataLayer", accurateTrackBounce:true, trackLinks:true});
|
|
||||||
</script>
|
|
||||||
<noscript><div><img src="https://mc.yandex.ru/watch/105278924" style="position:absolute; left:-9999px;" alt="" /></div></noscript>
|
|
||||||
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "Person",
|
|
||||||
"name": "Николай Сердюк",
|
|
||||||
"jobTitle": "Программист 1С",
|
|
||||||
"description": "Профессиональный программист 1С с более чем 10-летним опытом",
|
|
||||||
"url": "https://nikdizell.ru",
|
|
||||||
"email": "{{ CONTACT_EMAIL }}",
|
|
||||||
"telephone": "{{ CONTACT_PHONE }}",
|
|
||||||
"knowsAbout": [
|
|
||||||
"1С:Предприятие 8.3",
|
|
||||||
"Управление торговлей",
|
|
||||||
"Бухгалтерия предприятия",
|
|
||||||
"Зарплата и управление персоналом",
|
|
||||||
"SQL",
|
|
||||||
"Веб-сервисы",
|
|
||||||
"API интеграция"
|
|
||||||
],
|
|
||||||
"hasOccupation": {
|
|
||||||
"@type": "Occupation",
|
|
||||||
"name": "Программист 1С",
|
|
||||||
"description": "Разработка и сопровождение систем на платформе 1С",
|
|
||||||
"occupationLocation": "Россия"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<!-- Header -->
|
|
||||||
{% block mainmenu %}
|
|
||||||
<header class="header">
|
|
||||||
<div class="container">
|
|
||||||
<nav class="nav">
|
|
||||||
<a href="{% url 'home' %}" class="logo">
|
|
||||||
<img src="{% static 'programmer/images/main.ico' %}" alt="Logo" class="logo-img">
|
|
||||||
<span class="logo-text">Программист 1С</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<!-- Десктопное меню -->
|
|
||||||
<ul class="nav-menu">
|
|
||||||
{% for m in menu %}
|
|
||||||
<li class="nav-item">
|
|
||||||
<a href="{% url m.url_name %}" class="nav-link {% if request.resolver_match.url_name == m.url_name %}active{% endif %}">
|
|
||||||
{{m.title}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="nav-actions">
|
|
||||||
<a href="https://t.me/odinesina_prog" target="_blank" class="telegram-btn">
|
|
||||||
<span class="telegram-icon">
|
|
||||||
<img src="{% static 'programmer/images/share_tg.png' %}" alt="Telegram" width="20" height="20">
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<!-- Theme Toggle Switch -->
|
|
||||||
<div class="theme-switcher">
|
|
||||||
<input type="checkbox" id="theme-toggle" class="theme-toggle-checkbox" checked>
|
|
||||||
<label for="theme-toggle" class="theme-toggle-label">
|
|
||||||
<span class="theme-toggle-slider"></span>
|
|
||||||
<span class="theme-icon sun">☀️</span>
|
|
||||||
<span class="theme-icon moon">🌙</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Кнопка мобильного меню -->
|
|
||||||
<button class="mobile-menu-btn" id="mobileMenuBtn">
|
|
||||||
☰
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<!-- Мобильное меню -->
|
|
||||||
<div class="mobile-menu-overlay" id="mobileMenuOverlay"></div>
|
|
||||||
<div class="mobile-menu" id="mobileMenu">
|
|
||||||
<div class="mobile-menu-header">
|
|
||||||
<h3>Меню</h3>
|
|
||||||
<button class="mobile-menu-close" id="mobileMenuClose">
|
|
||||||
✕
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul class="mobile-nav-menu">
|
|
||||||
{% for m in menu %}
|
|
||||||
<li class="mobile-nav-item">
|
|
||||||
<a href="{% url m.url_name %}" class="mobile-nav-link {% if request.resolver_match.url_name == m.url_name %}active{% endif %}">
|
|
||||||
{{m.title}}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<div class="mobile-nav-actions">
|
|
||||||
<a href="https://t.me/odinesina_prog" target="_blank" class="btn btn-primary" style="width: 100%; text-align: center;">
|
|
||||||
<span class="telegram-icon">
|
|
||||||
<img src="{% static 'programmer/images/share_tg.png' %}" alt="Telegram" width="20" height="20">
|
|
||||||
</span>
|
|
||||||
</a>
|
|
||||||
<div class="theme-switcher" style="justify-content: center;">
|
|
||||||
<input type="checkbox" id="mobile-theme-toggle" class="theme-toggle-checkbox" checked>
|
|
||||||
<label for="mobile-theme-toggle" class="theme-toggle-label">
|
|
||||||
<span class="theme-toggle-slider"></span>
|
|
||||||
<span class="theme-icon sun">☀️</span>
|
|
||||||
<span class="theme-icon moon">🌙</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock mainmenu %}
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
|
||||||
<main class="main">
|
|
||||||
<div class="container">
|
|
||||||
<section class="content">
|
|
||||||
<!-- Breadcrumbs -->
|
|
||||||
{% block breadcrumbs %}
|
|
||||||
<nav class="breadcrumbs">
|
|
||||||
<a href="{% url 'home' %}" class="breadcrumb-link">Главная</a>
|
|
||||||
<span class="breadcrumb-separator">/</span>
|
|
||||||
<span class="breadcrumb-current">{{title}}</span>
|
|
||||||
</nav>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<!-- Messages -->
|
|
||||||
{% bootstrap_messages %}
|
|
||||||
|
|
||||||
<!-- Page Content -->
|
|
||||||
<div class="page-content">
|
|
||||||
{% block content %}
|
|
||||||
{% endblock %}
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</main>
|
|
||||||
|
|
||||||
<!-- Footer -->
|
|
||||||
<footer class="footer">
|
|
||||||
<div class="container">
|
|
||||||
<div class="footer-content">
|
|
||||||
<div class="footer-info">
|
|
||||||
<h3>Николай Сердюк</h3>
|
|
||||||
<p>Программист 1С</p>
|
|
||||||
</div>
|
|
||||||
<div class="footer-contacts">
|
|
||||||
<p>📧 {{ CONTACT_EMAIL }}</p>
|
|
||||||
<p>📱 {{ CONTACT_PHONE }}</p>
|
|
||||||
</div>
|
|
||||||
<div class="footer-copyright">
|
|
||||||
<p>© 2025 ИП Сердюк Николай Александрович. Все права защищены.</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</footer>
|
|
||||||
|
|
||||||
{% bootstrap_javascript %}
|
|
||||||
<script src="{% static 'programmer/js/theme-switcher.js' %}"></script>
|
|
||||||
<script src="{% static 'programmer/js/mobile-menu.js' %}"></script>
|
|
||||||
|
|
||||||
{% block extra_js %}
|
|
||||||
<!-- Дополнительные JS файлы для конкретных страниц -->
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
<!-- В recall.html после основного контента -->
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "Review",
|
|
||||||
"itemReviewed": {
|
|
||||||
"@type": "Service",
|
|
||||||
"name": "Услуги программиста 1С"
|
|
||||||
},
|
|
||||||
"author": {
|
|
||||||
"@type": "Organization",
|
|
||||||
"name": "ООО «РОВЕН-Регионы»"
|
|
||||||
},
|
|
||||||
"reviewRating": {
|
|
||||||
"@type": "Rating",
|
|
||||||
"ratingValue": "5",
|
|
||||||
"bestRating": "5"
|
|
||||||
},
|
|
||||||
"datePublished": "2025-11-13",
|
|
||||||
"description": "Выражаю благодарность программисту 1С Николаю Сердюк за профессиональную работу и качественное решение поставленных задач..."
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
{% extends 'programmer/base.html' %}
|
|
||||||
{% load django_bootstrap5 %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,72 +0,0 @@
|
|||||||
{% extends 'programmer/base.html' %}
|
|
||||||
{% load django_bootstrap5 %}
|
|
||||||
{% load static %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<link rel="stylesheet" href="{% static 'programmer/css/competence.css' %}">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="page-header">
|
|
||||||
<h1 class="page-title">Компетенции</h1>
|
|
||||||
<p class="page-subtitle">Профессиональные навыки и опыт</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="competence-grid">
|
|
||||||
{% for p in posts %}
|
|
||||||
<div class="modern-card fade-in">
|
|
||||||
<div class="competence-item">
|
|
||||||
{% if p.photo %}
|
|
||||||
<div class="competence-scan-wrapper">
|
|
||||||
<div class="competence-scan-container">
|
|
||||||
<img src="{{ p.photo.url }}"
|
|
||||||
alt="Сертификат 1С: {{ p.title }} - {{ p.content|striptags }}"
|
|
||||||
class="competence-scan"
|
|
||||||
onclick="openCompetenceModal('{{ p.photo.url }}', '{{ p.title }}')">
|
|
||||||
<div class="scan-hint">
|
|
||||||
<span class="scan-zoom-icon">🔍</span>
|
|
||||||
<span class="scan-text">Нажмите для увеличения</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="competence-content">
|
|
||||||
<h2 class="competence-title">{{ p.title }}</h2>
|
|
||||||
<div class="competence-description">
|
|
||||||
{{ p.content|linebreaks }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if not posts %}
|
|
||||||
<div class="modern-card text-center fade-in">
|
|
||||||
<h3>📚 Информация о компетенциях</h3>
|
|
||||||
<p class="card-subtitle">Раздел находится в разработке</p>
|
|
||||||
<div class="card-actions justify-center">
|
|
||||||
<a href="{% url 'solution' %}" class="btn btn-primary">Посмотреть проекты</a>
|
|
||||||
<a href="{% url 'about' %}" class="btn btn-secondary">Обо мне</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Модальное окно для увеличенного просмотра -->
|
|
||||||
<div id="competenceModal" class="modal competence-modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3 id="competenceModalTitle">Компетенция</h3>
|
|
||||||
<button class="modal-close" onclick="closeCompetenceModal()">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<img class="modal-image" id="competenceModalImage" alt="Увеличенное изображение компетенции">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_js %}
|
|
||||||
<script src="{% static 'programmer/js/competence.js' %}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
/* TEAM */
|
|
||||||
Developer: Николай Сердюк
|
|
||||||
Site: https://nikdizell.ru
|
|
||||||
Email: {{ CONTACT_EMAIL }}
|
|
||||||
|
|
||||||
/* THANKS */
|
|
||||||
Django Framework
|
|
||||||
Bootstrap
|
|
||||||
|
|
||||||
/* SITE */
|
|
||||||
Last update: 2025
|
|
||||||
Language: Russian
|
|
||||||
Doctype: HTML5
|
|
||||||
@ -1,103 +0,0 @@
|
|||||||
{% extends 'programmer/base.html' %}
|
|
||||||
{% load django_bootstrap5 %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="hero-section fade-in">
|
|
||||||
<h1 class="hero-title">🚀 Добро пожаловать!</h1>
|
|
||||||
<p class="hero-subtitle">Я профессиональный программист 1С с опытом создания эффективных бизнес-решений</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-2">
|
|
||||||
{% autoescape off %}
|
|
||||||
{% for p in posts %}
|
|
||||||
<div class="modern-card fade-in {% cycle '' 'secondary' %}">
|
|
||||||
<div class="card-header">
|
|
||||||
<h2 class="card-title">{{p.title}}</h2>
|
|
||||||
</div>
|
|
||||||
<div class="card-content">
|
|
||||||
{{p.content}}
|
|
||||||
</div>
|
|
||||||
<div class="card-actions">
|
|
||||||
<button onclick="openModal()" class="btn btn-primary">🎯 Получить консультацию</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
{% endautoescape %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Модальное окно формы -->
|
|
||||||
<div id="callbackModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3>📞 Заявка на консультацию</h3>
|
|
||||||
<button class="modal-close" onclick="closeModal()">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<form method="post" action="{% url 'callback' %}" id="callbackForm">
|
|
||||||
{% csrf_token %}
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="id_name">Имя *</label>
|
|
||||||
{{ form.name }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="id_phone">Телефон *</label>
|
|
||||||
{{ form.phone }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="id_email">Электронная почта</label>
|
|
||||||
{{ form.email }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="id_question">Ваш вопрос</label>
|
|
||||||
{{ form.question }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-actions">
|
|
||||||
<button type="submit" class="btn btn-primary" style="width: 100%;">
|
|
||||||
📨 Отправить заявку
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if not posts %}
|
|
||||||
<div class="modern-card text-center fade-in">
|
|
||||||
<h3>🚀 Контент скоро появится</h3>
|
|
||||||
<p class="card-subtitle">Мы готовим для вас интересные материалы и кейсы</p>
|
|
||||||
<div class="card-actions justify-center">
|
|
||||||
<button onclick="openModal()" class="btn btn-primary">🎯 Получить консультацию</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function openModal() {
|
|
||||||
document.getElementById('callbackModal').style.display = 'block';
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
document.getElementById('callbackModal').style.display = 'none';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Закрытие модального окна при клике вне его
|
|
||||||
window.onclick = function(event) {
|
|
||||||
const modal = document.getElementById('callbackModal');
|
|
||||||
if (event.target === modal) {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Закрытие по ESC
|
|
||||||
document.addEventListener('keydown', function(event) {
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,174 +0,0 @@
|
|||||||
{% extends 'programmer/base.html' %}
|
|
||||||
{% load django_bootstrap5 %}
|
|
||||||
{% load static %}
|
|
||||||
{% load seo_tags %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<link rel="stylesheet" href="{% static 'programmer/css/recall.css' %}">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="page-header">
|
|
||||||
<h1 class="page-title">Отзывы клиентов</h1>
|
|
||||||
<p class="page-subtitle">Реальные отзывы о работе программиста 1С</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="recall-grid">
|
|
||||||
{% for p in posts %}
|
|
||||||
<div class="modern-card fade-in">
|
|
||||||
|
|
||||||
<!-- Добавляем микроразметку для отзыва -->
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "Review",
|
|
||||||
"itemReviewed": {
|
|
||||||
"@type": "Service",
|
|
||||||
"name": "Услуги программиста 1С"
|
|
||||||
},
|
|
||||||
"author": {
|
|
||||||
"@type": "Organization",
|
|
||||||
"name": "{{ p.title }}"
|
|
||||||
},
|
|
||||||
"reviewRating": {
|
|
||||||
"@type": "Rating",
|
|
||||||
"ratingValue": "5",
|
|
||||||
"bestRating": "5"
|
|
||||||
},
|
|
||||||
"datePublished": "{{ p.time_create|date:'Y-m-d' }}",
|
|
||||||
"description": "{{ p.content|striptags|truncatewords:50 }}"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="recall-item">
|
|
||||||
<div class="recall-header">
|
|
||||||
<div class="recall-info">
|
|
||||||
<h2 class="recall-title">{{ p.title }}</h2>
|
|
||||||
{% if p.time_create %}
|
|
||||||
<!-- <div class="recall-meta">-->
|
|
||||||
<!-- <span class="recall-date">{{ p.time_create|date:"d.m.Y" }}</span>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
{% endif %}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="recall-content">
|
|
||||||
{% if p.scan %}
|
|
||||||
<div class="recall-scan-wrapper">
|
|
||||||
<div class="recall-scan-container">
|
|
||||||
<img src="{{ p.scan.url }}"
|
|
||||||
alt="Отзыв от {{ p.title }}"
|
|
||||||
class="recall-scan"
|
|
||||||
onclick="openModal('{{ p.scan.url }}', '{{ p.title }}')">
|
|
||||||
<div class="scan-hint">
|
|
||||||
<span class="scan-zoom-icon">🔍</span>
|
|
||||||
<span class="scan-text">Нажмите для увеличения</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="recall-text">
|
|
||||||
{{ p.content|linebreaks }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endfor %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if not posts %}
|
|
||||||
<div class="modern-card text-center fade-in">
|
|
||||||
<h3>💬 Отзывы клиентов</h3>
|
|
||||||
<p class="card-subtitle">Здесь будут отображаться отзывы от довольных клиентов</p>
|
|
||||||
<div class="card-actions justify-center">
|
|
||||||
<a href="{% url 'solution' %}" class="btn btn-primary">Посмотреть проекты</a>
|
|
||||||
<a href="{% url 'about' %}" class="btn btn-secondary">Связаться со мной</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Модальное окно для увеличенного просмотра -->
|
|
||||||
<div id="imageModal" class="modal">
|
|
||||||
<div class="modal-content">
|
|
||||||
<div class="modal-header">
|
|
||||||
<h3 id="modalTitle">Отзыв</h3>
|
|
||||||
<button class="modal-close" onclick="closeModal()">×</button>
|
|
||||||
</div>
|
|
||||||
<div class="modal-body">
|
|
||||||
<img class="modal-image" id="modalImage">
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
function openModal(imageUrl, title) {
|
|
||||||
console.log('Opening modal with:', imageUrl);
|
|
||||||
const modal = document.getElementById('imageModal');
|
|
||||||
const modalImg = document.getElementById('modalImage');
|
|
||||||
const modalTitle = document.getElementById('modalTitle');
|
|
||||||
|
|
||||||
if (modal && modalImg) {
|
|
||||||
modal.style.display = "block";
|
|
||||||
modalImg.src = imageUrl;
|
|
||||||
if (title && modalTitle) {
|
|
||||||
modalTitle.textContent = title;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Добавляем класс для анимации
|
|
||||||
setTimeout(() => {
|
|
||||||
modal.classList.add('active');
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function closeModal() {
|
|
||||||
const modal = document.getElementById('imageModal');
|
|
||||||
if (modal) {
|
|
||||||
modal.classList.remove('active');
|
|
||||||
setTimeout(() => {
|
|
||||||
modal.style.display = "none";
|
|
||||||
}, 300);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Закрытие модального окна при клике вне изображения
|
|
||||||
document.addEventListener('click', function(event) {
|
|
||||||
const modal = document.getElementById('imageModal');
|
|
||||||
if (event.target === modal) {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Закрытие по ESC
|
|
||||||
document.addEventListener('keydown', function(event) {
|
|
||||||
if (event.key === 'Escape') {
|
|
||||||
closeModal();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Адаптация размера изображения в модальном окне
|
|
||||||
window.addEventListener('resize', function() {
|
|
||||||
const modalImg = document.getElementById('modalImage');
|
|
||||||
if (modalImg && modalImg.src) {
|
|
||||||
adjustModalImageSize();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
function adjustModalImageSize() {
|
|
||||||
const modalImg = document.getElementById('modalImage');
|
|
||||||
const modalContent = document.querySelector('.modal-content');
|
|
||||||
|
|
||||||
if (modalImg && modalContent) {
|
|
||||||
const maxWidth = window.innerWidth * 0.9;
|
|
||||||
const maxHeight = window.innerHeight * 0.8;
|
|
||||||
|
|
||||||
modalImg.style.maxWidth = `${maxWidth}px`;
|
|
||||||
modalImg.style.maxHeight = `${maxHeight}px`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block extra_js %}
|
|
||||||
<script src="{% static 'programmer/js/recall.js' %}"></script>
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,19 +0,0 @@
|
|||||||
User-agent: *
|
|
||||||
Allow: /
|
|
||||||
|
|
||||||
# Основной сайт
|
|
||||||
Sitemap: {{ request.scheme }}://{{ request.get_host }}/sitemap.xml
|
|
||||||
|
|
||||||
# Запрещаем служебные разделы
|
|
||||||
Disallow: /admin/
|
|
||||||
Disallow: /media/cache/
|
|
||||||
Disallow: /static/admin/
|
|
||||||
Disallow: /callback/
|
|
||||||
Disallow: /api/
|
|
||||||
|
|
||||||
# Разрешаем индексацию статических файлов
|
|
||||||
Allow: /static/
|
|
||||||
Allow: /media/
|
|
||||||
|
|
||||||
# Указываем главное зеркало
|
|
||||||
Host: {{ request.scheme }}://{{ request.get_host }}
|
|
||||||
@ -1,12 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
||||||
xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
|
||||||
{% for url in urlset %}
|
|
||||||
<url>
|
|
||||||
<loc>{{ url.location }}</loc>
|
|
||||||
{% if url.lastmod %}<lastmod>{{ url.lastmod|date:"Y-m-d" }}</lastmod>{% endif %}
|
|
||||||
{% if url.changefreq %}<changefreq>{{ url.changefreq }}</changefreq>{% endif %}
|
|
||||||
{% if url.priority %}<priority>{{ url.priority }}</priority>{% endif %}
|
|
||||||
</url>
|
|
||||||
{% endfor %}
|
|
||||||
</urlset>
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
{% extends 'programmer/base.html' %}
|
|
||||||
{% load django_bootstrap5 %}
|
|
||||||
{% load static %}
|
|
||||||
{% load seo_tags %}
|
|
||||||
|
|
||||||
{% block extra_css %}
|
|
||||||
<link rel="stylesheet" href="{% static 'programmer/css/solution-accordion.css' %}">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block content %}
|
|
||||||
<div class="page-header">
|
|
||||||
<h1 class="page-title">Проекты автоматизации 1С</h1>
|
|
||||||
<p class="page-subtitle">Реализованные решения и кейсы по автоматизации бизнес-процессов</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="improved-list">
|
|
||||||
{% autoescape off %}
|
|
||||||
{% for p in posts %}
|
|
||||||
<li class="modern-card fade-in">
|
|
||||||
<div class="content-card">
|
|
||||||
<h2>{{p.title}}</h2>
|
|
||||||
|
|
||||||
<!-- Добавляем микроразметку для проекта -->
|
|
||||||
<script type="application/ld+json">
|
|
||||||
{
|
|
||||||
"@context": "https://schema.org",
|
|
||||||
"@type": "CreativeWork",
|
|
||||||
"name": "{{ p.title }}",
|
|
||||||
"description": "{{ p.description|striptags|truncatewords:30 }}",
|
|
||||||
"author": {
|
|
||||||
"@type": "Person",
|
|
||||||
"name": "Николай Сердюк"
|
|
||||||
},
|
|
||||||
"datePublished": "{{ p.time_create|date:'Y-m-d' }}"
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="solution-accordion">
|
|
||||||
<div class="accordion-item">
|
|
||||||
<div class="accordion-header" onclick="toggleAccordion(this)">
|
|
||||||
<strong>📋 Описание задачи</strong>
|
|
||||||
<span class="accordion-icon">▼</span>
|
|
||||||
</div>
|
|
||||||
<div class="accordion-content">
|
|
||||||
{{p.description}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="accordion-item">
|
|
||||||
<div class="accordion-header" onclick="toggleAccordion(this)">
|
|
||||||
<strong>🔧 Описание решения</strong>
|
|
||||||
<span class="accordion-icon">▼</span>
|
|
||||||
</div>
|
|
||||||
<div class="accordion-content">
|
|
||||||
{{p.implementation}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="accordion-item">
|
|
||||||
<div class="accordion-header" onclick="toggleAccordion(this)">
|
|
||||||
<strong>✅ Результат</strong>
|
|
||||||
<span class="accordion-icon">▼</span>
|
|
||||||
</div>
|
|
||||||
<div class="accordion-content">
|
|
||||||
{{p.closing}}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="article-panel">
|
|
||||||
<p class="first">Опубликовано: {{p.time_create|date:"d.m.Y"}}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% empty %}
|
|
||||||
<div class="modern-card text-center fade-in">
|
|
||||||
<h3>🚀 Проекты в разработке</h3>
|
|
||||||
<p class="card-subtitle">Скоро здесь появятся новые кейсы автоматизации</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
{% endautoescape %}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if not posts %}
|
|
||||||
<div class="content-card text-center">
|
|
||||||
<h3>Примеры решений скоро появятся</h3>
|
|
||||||
<p>Мы готовим для вас интересные кейсы и решения</p>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<!-- Подключаем внешний скрипт -->
|
|
||||||
<script src="{% static 'programmer/js/solution-accordion.js' %}"></script>
|
|
||||||
|
|
||||||
{% endblock %}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
from django import template
|
|
||||||
from ..models import CallbackRequest
|
|
||||||
|
|
||||||
register = template.Library()
|
|
||||||
|
|
||||||
@register.simple_tag
|
|
||||||
def get_unread_callbacks():
|
|
||||||
return CallbackRequest.objects.filter(is_read=False).count()
|
|
||||||
|
|
||||||
@register.simple_tag
|
|
||||||
def get_today_callbacks():
|
|
||||||
from django.utils import timezone
|
|
||||||
today = timezone.now().date()
|
|
||||||
return CallbackRequest.objects.filter(time_create__date=today).count()
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
from django import template
|
|
||||||
from django.utils.html import strip_tags
|
|
||||||
|
|
||||||
register = template.Library()
|
|
||||||
|
|
||||||
@register.simple_tag
|
|
||||||
def generate_meta_description(obj, default=""):
|
|
||||||
"""Генерирует meta description для объектов"""
|
|
||||||
if hasattr(obj, 'get_seo_description'):
|
|
||||||
return obj.get_seo_description()
|
|
||||||
elif hasattr(obj, 'content'):
|
|
||||||
clean_content = strip_tags(obj.content)[:160]
|
|
||||||
return clean_content + '...' if len(clean_content) > 160 else clean_content
|
|
||||||
return default
|
|
||||||
|
|
||||||
@register.simple_tag
|
|
||||||
def generate_meta_keywords(obj, default=""):
|
|
||||||
"""Генерирует meta keywords для объектов"""
|
|
||||||
if hasattr(obj, 'get_meta_keywords'):
|
|
||||||
return ', '.join(obj.get_meta_keywords())
|
|
||||||
return default
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
from django.test import TestCase
|
|
||||||
|
|
||||||
# Create your tests here.
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
from django.contrib import admin
|
|
||||||
from django.urls import path, include
|
|
||||||
from django.conf import settings
|
|
||||||
from django.conf.urls.static import static
|
|
||||||
from .views import *
|
|
||||||
from django.contrib.sitemaps.views import sitemap
|
|
||||||
from .sitemaps import sitemaps
|
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = [
|
|
||||||
path('', index, name='home'),
|
|
||||||
path('about/', about, name='about'),
|
|
||||||
path('solutions/', solution, name='solution'),
|
|
||||||
path('competence/', ability, name='ability'),
|
|
||||||
path('recall/', recall, name='recall'),
|
|
||||||
path('post/<int:post_id>/', show_post, name='post'),
|
|
||||||
path('callback/', callback_request, name='callback'),
|
|
||||||
path('admin/statistics/', statistics_view, name='statistics'),
|
|
||||||
# Sitemap
|
|
||||||
path('sitemap.xml', sitemap, {'sitemaps': sitemaps},
|
|
||||||
name='django.contrib.sitemaps.views.sitemap'),
|
|
||||||
path('robots.txt', robots_txt, name='robots'),
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
if settings.DEBUG:
|
|
||||||
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
|
|
||||||
@ -1,65 +0,0 @@
|
|||||||
# programmer/utils/email_notifications.py
|
|
||||||
from django.core.mail import send_mail, EmailMultiAlternatives
|
|
||||||
from django.template.loader import render_to_string
|
|
||||||
from django.conf import settings
|
|
||||||
from django.utils.html import strip_tags
|
|
||||||
import logging
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
def send_callback_notification(callback_request):
|
|
||||||
"""
|
|
||||||
Отправляет уведомление о новой заявке на обратный звонок
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
subject = f'🚨 Новая заявка на обратный звонок от {callback_request.name}'
|
|
||||||
|
|
||||||
# HTML версия письма
|
|
||||||
html_message = render_to_string('emails/callback_notification.html', {
|
|
||||||
'callback': callback_request,
|
|
||||||
'site_url': settings.ALLOWED_HOSTS[0] if settings.ALLOWED_HOSTS else 'localhost',
|
|
||||||
})
|
|
||||||
|
|
||||||
# Текстовая версия письма
|
|
||||||
plain_message = strip_tags(html_message)
|
|
||||||
|
|
||||||
# Проверяем настройки email
|
|
||||||
if not all([settings.EMAIL_HOST_USER, settings.EMAIL_HOST_PASSWORD]):
|
|
||||||
logger.error("Email settings are not configured properly")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Отправляем email
|
|
||||||
send_mail(
|
|
||||||
subject=subject,
|
|
||||||
message=plain_message,
|
|
||||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
|
||||||
recipient_list=settings.ADMIN_EMAILS,
|
|
||||||
html_message=html_message,
|
|
||||||
fail_silently=False,
|
|
||||||
)
|
|
||||||
|
|
||||||
logger.info(f"Email notification sent successfully for callback #{callback_request.id}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Error sending email notification: {e}")
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def send_test_email():
|
|
||||||
"""
|
|
||||||
Функция для тестирования отправки email
|
|
||||||
"""
|
|
||||||
try:
|
|
||||||
send_mail(
|
|
||||||
subject='📧 Test Email from Django',
|
|
||||||
message='This is a test email from your Django application.',
|
|
||||||
from_email=settings.DEFAULT_FROM_EMAIL,
|
|
||||||
recipient_list=settings.ADMIN_EMAILS,
|
|
||||||
fail_silently=False,
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Test email failed: {e}")
|
|
||||||
return False
|
|
||||||
@ -1,259 +0,0 @@
|
|||||||
from django.http import HttpResponse, HttpResponseNotFound
|
|
||||||
from .models import *
|
|
||||||
from django.shortcuts import render, redirect
|
|
||||||
from django.contrib import messages
|
|
||||||
from .models import CallbackRequest # Импортируем из models, а не forms
|
|
||||||
from .forms import CallbackForm
|
|
||||||
from django.utils import timezone
|
|
||||||
from datetime import timedelta
|
|
||||||
from .models import PageView, Visitor
|
|
||||||
from django.db.models import Count
|
|
||||||
from django.contrib.auth.decorators import login_required, user_passes_test
|
|
||||||
from django.views.decorators.http import require_GET
|
|
||||||
|
|
||||||
|
|
||||||
menu = [
|
|
||||||
{'title': "Главная", 'url_name': 'home'},
|
|
||||||
{'title': "Проекты", 'url_name': 'solution'},
|
|
||||||
{'title': "Компетенции", 'url_name': 'ability'},
|
|
||||||
{'title': "Отзывы", 'url_name': 'recall'},
|
|
||||||
{'title': "Обо мне", 'url_name': 'about'}
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
# === ДОБАВЬТЕ ЭТИ ФУНКЦИИ ЗДЕСЬ ===
|
|
||||||
|
|
||||||
def get_client_ip(request):
|
|
||||||
"""Получаем реальный IP клиента"""
|
|
||||||
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
|
|
||||||
if x_forwarded_for:
|
|
||||||
ip = x_forwarded_for.split(',')[0]
|
|
||||||
else:
|
|
||||||
ip = request.META.get('REMOTE_ADDR')
|
|
||||||
return ip
|
|
||||||
|
|
||||||
|
|
||||||
def should_track_request(request):
|
|
||||||
"""Определяем, нужно ли отслеживать запрос"""
|
|
||||||
|
|
||||||
client_ip = get_client_ip(request)
|
|
||||||
path = request.path
|
|
||||||
|
|
||||||
# Игнорируемые пути (Nextcloud специфичные)
|
|
||||||
nextcloud_paths = [
|
|
||||||
'/index.php',
|
|
||||||
'/status.php',
|
|
||||||
'/cron',
|
|
||||||
'/remote.php',
|
|
||||||
'/ocs',
|
|
||||||
'/apps/',
|
|
||||||
'/custom_apps/',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Игнорируемые IP (Docker сети)
|
|
||||||
docker_ips = [
|
|
||||||
'192.168.64.1',
|
|
||||||
'192.168.65.1',
|
|
||||||
'172.17.0.1',
|
|
||||||
'172.18.0.1',
|
|
||||||
'172.19.0.1',
|
|
||||||
]
|
|
||||||
|
|
||||||
# Игнорируем статические файлы и админку
|
|
||||||
if path.startswith('/static/') or path.startswith('/admin/'):
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Не отслеживаем Nextcloud и Docker запросы
|
|
||||||
if any(path.startswith(p) for p in nextcloud_paths):
|
|
||||||
return False
|
|
||||||
if client_ip in docker_ips:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def track_page_view(request):
|
|
||||||
"""Основная функция отслеживания просмотров"""
|
|
||||||
if not should_track_request(request):
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
PageView.objects.create(
|
|
||||||
url=request.path,
|
|
||||||
ip_address=get_client_ip(request),
|
|
||||||
user_agent=request.META.get('HTTP_USER_AGENT', '')[:500],
|
|
||||||
referer=request.META.get('HTTP_REFERER', '')[:500],
|
|
||||||
)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error tracking page: {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def track_view(view_func):
|
|
||||||
"""Декоратор для отслеживания просмотров страниц"""
|
|
||||||
from functools import wraps
|
|
||||||
|
|
||||||
@wraps(view_func)
|
|
||||||
def _wrapped_view(request, *args, **kwargs):
|
|
||||||
# Отслеживаем просмотр перед выполнением view
|
|
||||||
track_page_view(request)
|
|
||||||
return view_func(request, *args, **kwargs)
|
|
||||||
|
|
||||||
return _wrapped_view
|
|
||||||
|
|
||||||
|
|
||||||
@track_view
|
|
||||||
def index(request):
|
|
||||||
posts = Home.objects.filter(is_published=True)
|
|
||||||
context = {
|
|
||||||
'posts': posts,
|
|
||||||
'menu': menu,
|
|
||||||
'title': "Программист 1С Николай Сердюк - разработка и сопровождение",
|
|
||||||
'meta_description': "Профессиональный программист 1С с более чем 10-летним опытом. Разработка, доработка, обновление и интеграция систем 1С. Сопровождение 1С.",
|
|
||||||
'meta_keywords': "программист 1С, разработка 1С, обновление 1С, сопровождение 1С, интеграция 1С, доработка 1С, 1С предприятие 8.3",
|
|
||||||
'form': CallbackForm()
|
|
||||||
}
|
|
||||||
return render(request, 'programmer/index.html', context=context)
|
|
||||||
|
|
||||||
|
|
||||||
@track_view
|
|
||||||
def about(request):
|
|
||||||
context = {
|
|
||||||
'menu': menu,
|
|
||||||
'title': "Программист 1С Николай Сердюк - 10+ лет опыта | Услуги 1С",
|
|
||||||
'meta_description': "Николай Сердюк - сертифицированный программист 1С с 10+ лет опыта. Специализация: обновление 1С, разработка под ключ, интеграция, миграция с 1С 7.7.",
|
|
||||||
'meta_keywords': "программист 1С Николай Сердюк, обновление 1С, разработка 1С под ключ, интеграция 1С, сертифицированный 1С, миграция 1С 7.7"
|
|
||||||
}
|
|
||||||
return render(request, 'programmer/about.html', context=context)
|
|
||||||
|
|
||||||
|
|
||||||
@track_view
|
|
||||||
def solution(request):
|
|
||||||
posts = Solution.objects.filter(is_published=True)
|
|
||||||
context = {
|
|
||||||
'posts': posts,
|
|
||||||
'menu': menu,
|
|
||||||
'meta_description': "Реализованные проекты по автоматизации 1С: складской учет с ТСД, интеграция с оборудованием, миграция с 1С 7.7. Примеры работ и кейсы.",
|
|
||||||
'meta_keywords': "проекты 1С, автоматизация склада 1С, интеграция ТСД 1С, миграция 1С 7.7, кейсы 1С, примеры работ 1С",
|
|
||||||
'title': "Проекты автоматизации 1С | Реализованные кейсы и решения",
|
|
||||||
}
|
|
||||||
return render(request, 'programmer/solution.html', context=context)
|
|
||||||
|
|
||||||
|
|
||||||
@track_view
|
|
||||||
def ability(request):
|
|
||||||
posts = Competence.objects.filter(is_published=True)
|
|
||||||
context = {
|
|
||||||
'posts': posts,
|
|
||||||
'menu': menu,
|
|
||||||
'title': "Сертификаты и компетенции 1С | Программист 1С Николай Сердюк",
|
|
||||||
'meta_description': "Сертификаты 1С: Профессионал по платформе 8.3 и БП 3.0. Подтвержденная квалификация программиста 1С с сертификатами фирмы 1С.",
|
|
||||||
'meta_keywords': "сертификаты 1С, 1С профессионал, компетенции 1С, квалификация программиста 1С, сертифицированный специалист 1С"
|
|
||||||
}
|
|
||||||
return render(request, 'programmer/competence.html', context=context)
|
|
||||||
|
|
||||||
|
|
||||||
@track_view
|
|
||||||
def recall(request):
|
|
||||||
posts = Recall.objects.filter(is_published=True)
|
|
||||||
context = {
|
|
||||||
'posts': posts,
|
|
||||||
'menu': menu,
|
|
||||||
'title': "Отзывы клиентов о работе программиста 1С | Реальные кейсы",
|
|
||||||
'meta_description': "Реальные отзывы клиентов о работе программиста 1С Николая Сердюка. Отзывы от ООО «РОВЕН-Регионы» и других компаний.",
|
|
||||||
'meta_keywords': "отзывы программист 1С, рекомендации 1С, отзывы клиентов 1С, реальные кейсы 1С, отзыв ООО РОВЕН"
|
|
||||||
}
|
|
||||||
return render(request, 'programmer/recall.html', context=context)
|
|
||||||
|
|
||||||
|
|
||||||
def show_post(request, post_id):
|
|
||||||
return HttpResponse(f"Отображение № {post_id}")
|
|
||||||
|
|
||||||
|
|
||||||
def pageNotFound(request, exception):
|
|
||||||
return HttpResponseNotFound('<h1>Страница не найдена</h1>')
|
|
||||||
|
|
||||||
|
|
||||||
def callback_request(request):
|
|
||||||
if request.method == 'POST':
|
|
||||||
form = CallbackForm(request.POST)
|
|
||||||
if form.is_valid():
|
|
||||||
# Сохраняем заявку через форму
|
|
||||||
form.save()
|
|
||||||
messages.success(request, '✅ Ваша заявка успешно отправлена! Я свяжусь с вами в ближайшее время.')
|
|
||||||
return redirect('home')
|
|
||||||
else:
|
|
||||||
# Если форма невалидна, показываем ошибки
|
|
||||||
for field, errors in form.errors.items():
|
|
||||||
for error in errors:
|
|
||||||
messages.error(request, f'❌ Ошибка в поле {form.fields[field].label}: {error}')
|
|
||||||
return redirect('home')
|
|
||||||
|
|
||||||
# Если GET запрос, просто показываем главную страницу
|
|
||||||
return redirect('home')
|
|
||||||
|
|
||||||
|
|
||||||
def is_admin(user):
|
|
||||||
return user.is_staff
|
|
||||||
|
|
||||||
|
|
||||||
def is_staff(user):
|
|
||||||
return user.is_staff
|
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
|
||||||
@user_passes_test(is_staff)
|
|
||||||
def statistics_view(request):
|
|
||||||
today = timezone.now().date()
|
|
||||||
week_ago = today - timedelta(days=7)
|
|
||||||
|
|
||||||
# Статистика за сегодня
|
|
||||||
today_views = PageView.objects.filter(
|
|
||||||
timestamp__date=today
|
|
||||||
).count()
|
|
||||||
|
|
||||||
# Статистика за неделю
|
|
||||||
weekly_views = PageView.objects.filter(
|
|
||||||
timestamp__date__gte=week_ago
|
|
||||||
).count()
|
|
||||||
|
|
||||||
# Всего просмотров
|
|
||||||
total_views = PageView.objects.count()
|
|
||||||
|
|
||||||
# Популярные страницы за неделю
|
|
||||||
popular_pages = PageView.objects.filter(
|
|
||||||
timestamp__date__gte=week_ago
|
|
||||||
).values('url').annotate(
|
|
||||||
views=Count('id')
|
|
||||||
).order_by('-views')[:10]
|
|
||||||
|
|
||||||
# Уникальные посетители за неделю
|
|
||||||
unique_visitors = Visitor.objects.filter(
|
|
||||||
last_visit__date__gte=week_ago
|
|
||||||
).count()
|
|
||||||
|
|
||||||
# Последние посещения
|
|
||||||
recent_views = PageView.objects.select_related().order_by('-timestamp')[:20]
|
|
||||||
|
|
||||||
today = timezone.now().date()
|
|
||||||
total_callbacks = CallbackRequest.objects.count()
|
|
||||||
today_callbacks = CallbackRequest.objects.filter(time_create__date=today).count()
|
|
||||||
unread_callbacks = CallbackRequest.objects.filter(is_read=False).count()
|
|
||||||
|
|
||||||
context = {
|
|
||||||
'today_views': today_views,
|
|
||||||
'weekly_views': weekly_views,
|
|
||||||
'total_views': total_views,
|
|
||||||
'unique_visitors': unique_visitors,
|
|
||||||
'popular_pages': popular_pages,
|
|
||||||
'recent_views': recent_views,
|
|
||||||
'total_callbacks': total_callbacks,
|
|
||||||
'today_callbacks': today_callbacks,
|
|
||||||
'unread_callbacks': unread_callbacks,
|
|
||||||
}
|
|
||||||
|
|
||||||
return render(request, 'admin/statistics.html', context)
|
|
||||||
|
|
||||||
|
|
||||||
@require_GET
|
|
||||||
def robots_txt(request):
|
|
||||||
return render(request, 'robots.txt', content_type='text/plain')
|
|
||||||
@ -1,212 +1,226 @@
|
|||||||
"""
|
"""
|
||||||
Django settings for OneCprogsite project.
|
Django settings for OneCprogsite project.
|
||||||
|
|
||||||
Generated by 'django-admin startproject' using Django 4.2.7.
|
Generated by 'django-admin startproject' using Django 4.2.7.
|
||||||
|
|
||||||
For more information on this file, see
|
For more information on this file, see
|
||||||
https://docs.djangoproject.com/en/4.2/topics/settings/
|
https://docs.djangoproject.com/en/4.2/topics/settings/
|
||||||
|
|
||||||
For the full list of settings and their values, see
|
For the full list of settings and their values, see
|
||||||
https://docs.djangoproject.com/en/4.2/ref/settings/
|
https://docs.djangoproject.com/en/4.2/ref/settings/
|
||||||
"""
|
"""
|
||||||
import os.path
|
import os.path
|
||||||
from pathlib import Path
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
|
||||||
BASE_DIR = Path(__file__).resolve().parent.parent
|
|
||||||
|
SITE_ID = 1
|
||||||
|
|
||||||
# Quick-start development settings - unsuitable for production
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
# SECURITY WARNING: keep the secret key used in production secret!
|
sys.path.insert(0, str(BASE_DIR)) # Добавляем корень проекта
|
||||||
SECRET_KEY = 'django-insecure-5rs2a1*8cxjkv*%6k1-88biv&1#nep%@i+%1^dk=5j$s&e&hwm'
|
sys.path.insert(0, str(BASE_DIR / "OneCprogsite")) # Добавляем папку OneCprogsite
|
||||||
|
|
||||||
# Безопасность cookies для HTTPS
|
# Quick-start development settings - unsuitable for production
|
||||||
SESSION_COOKIE_SECURE = True
|
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
|
||||||
CSRF_COOKIE_SECURE = True
|
|
||||||
SESSION_COOKIE_HTTPONLY = True
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
CSRF_COOKIE_HTTPONLY = False # Django требует доступ к CSRF cookie через JS
|
SECRET_KEY = 'django-insecure-5rs2a1*8cxjkv*%6k1-88biv&1#nep%@i+%1^dk=5j$s&e&hwm'
|
||||||
SESSION_COOKIE_SAMESITE = 'Lax'
|
|
||||||
CSRF_COOKIE_SAMESITE = 'Lax'
|
# Безопасность cookies для HTTPS
|
||||||
|
# SESSION_COOKIE_SECURE = True
|
||||||
# Если используете другие cookies
|
# CSRF_COOKIE_SECURE = True
|
||||||
LANGUAGE_COOKIE_SECURE = True
|
# SESSION_COOKIE_HTTPONLY = True
|
||||||
LANGUAGE_COOKIE_HTTPONLY = True
|
# CSRF_COOKIE_HTTPONLY = False # Django требует доступ к CSRF cookie через JS
|
||||||
LANGUAGE_COOKIE_SAMESITE = 'Lax'
|
# SESSION_COOKIE_SAMESITE = 'Lax'
|
||||||
|
# CSRF_COOKIE_SAMESITE = 'Lax'
|
||||||
# SECURITY WARNING: don't run with debug turned on in production!
|
|
||||||
DEBUG = False
|
# Если используете другие cookies
|
||||||
|
# LANGUAGE_COOKIE_SECURE = True
|
||||||
X_FRAME_OPTIONS = 'SAMEORIGIN'
|
# LANGUAGE_COOKIE_HTTPONLY = True
|
||||||
# Или разрешить конкретные домены (Django 4.0+)
|
# LANGUAGE_COOKIE_SAMESITE = 'Lax'
|
||||||
X_FRAME_OPTIONS = 'ALLOW-FROM https://metrika.yandex.ru'
|
|
||||||
|
# Для разработки (HTTP)
|
||||||
# ОБЯЗАТЕЛЬНО укажите ваши домены
|
SESSION_COOKIE_SECURE = False
|
||||||
ALLOWED_HOSTS = [
|
CSRF_COOKIE_SECURE = False
|
||||||
'nikdizell.ru',
|
SECURE_SSL_REDIRECT = False
|
||||||
'www.nikdizell.ru',
|
|
||||||
'localhost',
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
'127.0.0.1',
|
DEBUG = True
|
||||||
'192.168.31.88' # Добавьте IP сервера
|
|
||||||
]
|
# Или разрешить конкретные домены (Django 4.0+)
|
||||||
|
X_FRAME_OPTIONS = 'ALLOW-FROM https://metrika.yandex.ru'
|
||||||
# Важно для работы за прокси
|
|
||||||
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
# ОБЯЗАТЕЛЬНО укажите ваши домены
|
||||||
SECURE_SSL_REDIRECT = True
|
ALLOWED_HOSTS = [
|
||||||
|
'nikdizell.ru',
|
||||||
# Дополнительная безопасность
|
'www.nikdizell.ru',
|
||||||
SECURE_BROWSER_XSS_FILTER = True
|
'localhost',
|
||||||
SECURE_CONTENT_TYPE_NOSNIFF = True
|
'127.0.0.1',
|
||||||
SECURE_HSTS_SECONDS = 31536000
|
'192.168.31.88', # Добавьте IP сервера
|
||||||
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
'192.168.31.221',
|
||||||
SECURE_HSTS_PRELOAD = True
|
]
|
||||||
|
|
||||||
CSRF_TRUSTED_ORIGINS = [
|
# Важно для работы за прокси
|
||||||
'https://nikdizell.ru',
|
# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
|
||||||
'https://www.nikdizell.ru',
|
# SECURE_SSL_REDIRECT = True
|
||||||
]
|
#
|
||||||
|
# # Дополнительная безопасность
|
||||||
# Application definition
|
# SECURE_BROWSER_XSS_FILTER = True
|
||||||
|
# SECURE_CONTENT_TYPE_NOSNIFF = True
|
||||||
INSTALLED_APPS = [
|
# SECURE_HSTS_SECONDS = 31536000
|
||||||
'django.contrib.admin',
|
# SECURE_HSTS_INCLUDE_SUBDOMAINS = True
|
||||||
'django.contrib.auth',
|
# SECURE_HSTS_PRELOAD = True
|
||||||
'django.contrib.contenttypes',
|
|
||||||
'django.contrib.sessions',
|
CSRF_TRUSTED_ORIGINS = [
|
||||||
'django.contrib.messages',
|
'https://nikdizell.ru',
|
||||||
'django.contrib.staticfiles',
|
'https://www.nikdizell.ru',
|
||||||
'programmer.apps.ProgrammerConfig',
|
]
|
||||||
'django_bootstrap5',
|
|
||||||
'django_extensions',
|
# Application definition
|
||||||
'django.contrib.sites',
|
|
||||||
'django.contrib.sitemaps',
|
INSTALLED_APPS = [
|
||||||
]
|
'django.contrib.admin',
|
||||||
|
'django.contrib.auth',
|
||||||
MIDDLEWARE = [
|
'django.contrib.contenttypes',
|
||||||
'django.middleware.security.SecurityMiddleware',
|
'django.contrib.sessions',
|
||||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
'django.contrib.messages',
|
||||||
'django.middleware.common.CommonMiddleware',
|
'django.contrib.staticfiles',
|
||||||
'django.middleware.csrf.CsrfViewMiddleware',
|
'programmer.apps.ProgrammerConfig',
|
||||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
'django_bootstrap5',
|
||||||
'django.contrib.messages.middleware.MessageMiddleware',
|
'django_extensions',
|
||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.contrib.sites',
|
||||||
'programmer.middleware.PageViewMiddleware',
|
'django.contrib.sitemaps',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'OneCprogsite.urls'
|
MIDDLEWARE = [
|
||||||
|
'django.middleware.security.SecurityMiddleware',
|
||||||
# Кастомный middleware для CSP
|
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||||
class CSPMiddleware:
|
'django.middleware.common.CommonMiddleware',
|
||||||
def __init__(self, get_response):
|
'django.middleware.csrf.CsrfViewMiddleware',
|
||||||
self.get_response = get_response
|
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||||
|
'django.contrib.messages.middleware.MessageMiddleware',
|
||||||
def __call__(self, request):
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
response = self.get_response(request)
|
'programmer.middleware.CSPMiddleware',
|
||||||
response['Content-Security-Policy'] = "frame-ancestors 'self' https://metrika.yandex.ru https://metrika.yandex.by https://metrica.yandex.com https://metrica.yandex.com.tr https://*.webvisor.com"
|
'programmer.middleware.PageViewMiddleware',
|
||||||
return response
|
]
|
||||||
|
|
||||||
TEMPLATES = [
|
ROOT_URLCONF = 'OneCprogsite.urls'
|
||||||
{
|
|
||||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
# Кастомный middleware для CSP вынесен в отдельный файл
|
||||||
'DIRS': [],
|
# class CSPMiddleware:
|
||||||
'APP_DIRS': True,
|
# def __init__(self, get_response):
|
||||||
'OPTIONS': {
|
# self.get_response = get_response
|
||||||
'context_processors': [
|
#
|
||||||
'django.template.context_processors.debug',
|
# def __call__(self, request):
|
||||||
'django.template.context_processors.request',
|
# response = self.get_response(request)
|
||||||
'django.contrib.auth.context_processors.auth',
|
# response['Content-Security-Policy'] = "frame-ancestors 'self' https://metrika.yandex.ru https://metrika.yandex.by https://metrica.yandex.com https://metrica.yandex.com.tr https://*.webvisor.com"
|
||||||
'django.contrib.messages.context_processors.messages',
|
# return response
|
||||||
'programmer.context_processors.menu_processor',
|
|
||||||
'programmer.context_processors.contact_info',
|
TEMPLATES = [
|
||||||
],
|
{
|
||||||
},
|
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||||
},
|
'DIRS': [],
|
||||||
]
|
'APP_DIRS': True,
|
||||||
|
'OPTIONS': {
|
||||||
WSGI_APPLICATION = 'OneCprogsite.wsgi.application'
|
'context_processors': [
|
||||||
|
'django.template.context_processors.debug',
|
||||||
|
'django.template.context_processors.request',
|
||||||
# Database
|
'django.contrib.auth.context_processors.auth',
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
'django.contrib.messages.context_processors.messages',
|
||||||
|
'programmer.context_processors.menu_processor',
|
||||||
DATABASES = {
|
'programmer.context_processors.contact_info',
|
||||||
'default': {
|
],
|
||||||
'ENGINE': 'django.db.backends.postgresql',
|
},
|
||||||
'NAME': 'App',
|
},
|
||||||
'USER': 'postgres',
|
]
|
||||||
'PASSWORD': 'NikDi94Zell',
|
|
||||||
'HOST': 'postgres',
|
WSGI_APPLICATION = 'OneCprogsite.wsgi.application'
|
||||||
'PORT': 5432,
|
|
||||||
}
|
|
||||||
}
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
||||||
|
|
||||||
# Password validation
|
DATABASES = {
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
'default': {
|
||||||
|
'ENGINE': 'django.db.backends.postgresql',
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
'NAME': 'App',
|
||||||
{
|
'USER': 'postgres',
|
||||||
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
'PASSWORD': 'NikDi94Zell',
|
||||||
},
|
'HOST': 'postgres',
|
||||||
{
|
'PORT': 5432,
|
||||||
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
}
|
||||||
},
|
}
|
||||||
{
|
|
||||||
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
|
||||||
},
|
# Password validation
|
||||||
{
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
|
||||||
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
|
||||||
},
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
]
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
|
||||||
|
},
|
||||||
# Internationalization
|
{
|
||||||
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
|
||||||
|
},
|
||||||
LANGUAGE_CODE = 'ru'
|
{
|
||||||
|
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
|
||||||
TIME_ZONE = 'Europe/Moscow'
|
},
|
||||||
USE_I18N = True
|
{
|
||||||
USE_I18N = True
|
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
|
||||||
USE_TZ = True
|
},
|
||||||
|
]
|
||||||
|
|
||||||
# Static files (CSS, JavaScript, Images)
|
|
||||||
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/4.2/topics/i18n/
|
||||||
STATIC_URL = 'static/'
|
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
LANGUAGE_CODE = 'ru'
|
||||||
STATICFILES_DIRS = []
|
|
||||||
|
TIME_ZONE = 'Europe/Moscow'
|
||||||
# Default primary key field type
|
USE_I18N = True
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
USE_TZ = True
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
# https://docs.djangoproject.com/en/4.2/howto/static-files/
|
||||||
MEDIA_URL = '/media/'
|
|
||||||
|
STATIC_URL = '/static/'
|
||||||
# Настройки email
|
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
|
||||||
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
|
||||||
# EMAIL_HOST = 'smtp.yandex.ru' # или smtp.gmail.com, smtp.mail.ru
|
STATICFILES_DIRS = [
|
||||||
# EMAIL_PORT = 587
|
os.path.join(BASE_DIR, 'programmer', 'static'),
|
||||||
# EMAIL_USE_TLS = True
|
]
|
||||||
# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'it@yandex.ru')
|
|
||||||
# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'tifdctkrcjcqwxyc')
|
# Default primary key field type
|
||||||
# DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
|
||||||
# SERVER_EMAIL = EMAIL_HOST_USER
|
|
||||||
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
EMAIL_HOST = 'smtp.gmail.com' # или smtp.gmail.com, smtp.mail.ru
|
|
||||||
EMAIL_PORT = 587
|
MEDIA_URL = '/media/'
|
||||||
EMAIL_USE_TLS = True
|
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||||
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'nikdizell@gmail.com')
|
|
||||||
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'qvmw yccb msqv mmpj')
|
# Настройки email
|
||||||
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
|
||||||
SERVER_EMAIL = EMAIL_HOST_USER
|
# EMAIL_HOST = 'smtp.yandex.ru' # или smtp.gmail.com, smtp.mail.ru
|
||||||
|
# EMAIL_PORT = 587
|
||||||
# Email для уведомлений (можно указать несколько через запятую)
|
# EMAIL_USE_TLS = True
|
||||||
# ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'nikdizell@gmail.com').split(',')
|
# EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'it@yandex.ru')
|
||||||
ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'it@nserdyuk.ru').split(',')
|
# EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'tifdctkrcjcqwxyc')
|
||||||
|
# DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
||||||
|
# SERVER_EMAIL = EMAIL_HOST_USER
|
||||||
|
|
||||||
|
EMAIL_HOST = 'smtp.gmail.com' # или smtp.gmail.com, smtp.mail.ru
|
||||||
|
EMAIL_PORT = 587
|
||||||
|
EMAIL_USE_TLS = True
|
||||||
|
EMAIL_HOST_USER = os.getenv('EMAIL_HOST_USER', 'nikdizell@gmail.com')
|
||||||
|
EMAIL_HOST_PASSWORD = os.getenv('EMAIL_HOST_PASSWORD', 'qvmw yccb msqv mmpj')
|
||||||
|
DEFAULT_FROM_EMAIL = EMAIL_HOST_USER
|
||||||
|
SERVER_EMAIL = EMAIL_HOST_USER
|
||||||
|
|
||||||
|
# Email для уведомлений (можно указать несколько через запятую)
|
||||||
|
# ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'nikdizell@gmail.com').split(',')
|
||||||
|
ADMIN_EMAILS = os.getenv('ADMIN_EMAILS', 'it@nserdyuk.ru').split(',')
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -1,275 +0,0 @@
|
|||||||
select.admin-autocomplete {
|
|
||||||
width: 20em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete.select2-container {
|
|
||||||
min-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--single,
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--multiple {
|
|
||||||
min-height: 30px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete.select2-container--focus .select2-selection,
|
|
||||||
.select2-container--admin-autocomplete.select2-container--open .select2-selection {
|
|
||||||
border-color: var(--body-quiet-color);
|
|
||||||
min-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--single,
|
|
||||||
.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--single {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete.select2-container--focus .select2-selection.select2-selection--multiple,
|
|
||||||
.select2-container--admin-autocomplete.select2-container--open .select2-selection.select2-selection--multiple {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--single {
|
|
||||||
background-color: var(--body-bg);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__rendered {
|
|
||||||
color: var(--body-fg);
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__clear {
|
|
||||||
cursor: pointer;
|
|
||||||
float: right;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__placeholder {
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow {
|
|
||||||
height: 26px;
|
|
||||||
position: absolute;
|
|
||||||
top: 1px;
|
|
||||||
right: 1px;
|
|
||||||
width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--single .select2-selection__arrow b {
|
|
||||||
border-color: #888 transparent transparent transparent;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 5px 4px 0 4px;
|
|
||||||
height: 0;
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -4px;
|
|
||||||
margin-top: -2px;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__clear {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--single .select2-selection__arrow {
|
|
||||||
left: 1px;
|
|
||||||
right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single {
|
|
||||||
background-color: var(--darkened-bg);
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--single .select2-selection__clear {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete.select2-container--open .select2-selection--single .select2-selection__arrow b {
|
|
||||||
border-color: transparent transparent #888 transparent;
|
|
||||||
border-width: 0 4px 5px 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--multiple {
|
|
||||||
background-color: var(--body-bg);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: text;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered {
|
|
||||||
box-sizing: border-box;
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 10px 5px 5px;
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__rendered li {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__placeholder {
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
margin-top: 5px;
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__clear {
|
|
||||||
cursor: pointer;
|
|
||||||
float: right;
|
|
||||||
font-weight: bold;
|
|
||||||
margin: 5px;
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice {
|
|
||||||
background-color: var(--darkened-bg);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: default;
|
|
||||||
float: left;
|
|
||||||
margin-right: 5px;
|
|
||||||
margin-top: 5px;
|
|
||||||
padding: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove {
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-selection--multiple .select2-selection__choice__remove:hover {
|
|
||||||
color: var(--body-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__placeholder, .select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-search--inline {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
|
|
||||||
margin-left: 2px;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete.select2-container--focus .select2-selection--multiple {
|
|
||||||
border: solid var(--body-quiet-color) 1px;
|
|
||||||
outline: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection--multiple {
|
|
||||||
background-color: var(--darkened-bg);
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete.select2-container--disabled .select2-selection__choice__remove {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--above .select2-selection--multiple {
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--single, .select2-container--admin-autocomplete.select2-container--open.select2-container--below .select2-selection--multiple {
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-search--dropdown {
|
|
||||||
background: var(--darkened-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-search--dropdown .select2-search__field {
|
|
||||||
background: var(--body-bg);
|
|
||||||
color: var(--body-fg);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-search--inline .select2-search__field {
|
|
||||||
background: transparent;
|
|
||||||
color: var(--body-fg);
|
|
||||||
border: none;
|
|
||||||
outline: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
-webkit-appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results > .select2-results__options {
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto;
|
|
||||||
color: var(--body-fg);
|
|
||||||
background: var(--body-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results__option[role=group] {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results__option[aria-disabled=true] {
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results__option[aria-selected=true] {
|
|
||||||
background-color: var(--selected-bg);
|
|
||||||
color: var(--body-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results__option .select2-results__option {
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__group {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option {
|
|
||||||
margin-left: -1em;
|
|
||||||
padding-left: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
|
||||||
margin-left: -2em;
|
|
||||||
padding-left: 3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
|
||||||
margin-left: -3em;
|
|
||||||
padding-left: 4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
|
||||||
margin-left: -4em;
|
|
||||||
padding-left: 5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
|
||||||
margin-left: -5em;
|
|
||||||
padding-left: 6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results__option--highlighted[aria-selected] {
|
|
||||||
background-color: var(--primary);
|
|
||||||
color: var(--primary-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.select2-container--admin-autocomplete .select2-results__group {
|
|
||||||
cursor: default;
|
|
||||||
display: block;
|
|
||||||
padding: 6px;
|
|
||||||
}
|
|
||||||
@ -1,328 +0,0 @@
|
|||||||
/* CHANGELISTS */
|
|
||||||
|
|
||||||
#changelist {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
justify-content: space-between;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .changelist-form-container {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change-list .hiddenfields { display:none; }
|
|
||||||
|
|
||||||
.change-list .filtered table {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change-list .filtered {
|
|
||||||
min-height: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change-list .filtered .results, .change-list .filtered .paginator,
|
|
||||||
.filtered #toolbar, .filtered div.xfull {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change-list .filtered table tbody th {
|
|
||||||
padding-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-form .results {
|
|
||||||
overflow-x: auto;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .toplinks {
|
|
||||||
border-bottom: 1px solid var(--hairline-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .paginator {
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
border-bottom: 1px solid var(--hairline-color);
|
|
||||||
background: var(--body-bg);
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CHANGELIST TABLES */
|
|
||||||
|
|
||||||
#changelist table thead th {
|
|
||||||
padding: 0;
|
|
||||||
white-space: nowrap;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist table thead th.action-checkbox-column {
|
|
||||||
width: 1.5em;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist table tbody td.action-checkbox {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist table tfoot {
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TOOLBAR */
|
|
||||||
|
|
||||||
#toolbar {
|
|
||||||
padding: 8px 10px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
border-top: 1px solid var(--hairline-color);
|
|
||||||
border-bottom: 1px solid var(--hairline-color);
|
|
||||||
background: var(--darkened-bg);
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#toolbar form input {
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
padding: 5px;
|
|
||||||
color: var(--body-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#toolbar #searchbar {
|
|
||||||
height: 1.1875rem;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
padding: 2px 5px;
|
|
||||||
margin: 0;
|
|
||||||
vertical-align: top;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toolbar #searchbar:focus {
|
|
||||||
border-color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#toolbar form input[type="submit"] {
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
padding: 4px 8px;
|
|
||||||
margin: 0;
|
|
||||||
vertical-align: middle;
|
|
||||||
background: var(--body-bg);
|
|
||||||
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
|
|
||||||
cursor: pointer;
|
|
||||||
color: var(--body-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#toolbar form input[type="submit"]:focus,
|
|
||||||
#toolbar form input[type="submit"]:hover {
|
|
||||||
border-color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-search img {
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-search .help {
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FILTER COLUMN */
|
|
||||||
|
|
||||||
#changelist-filter {
|
|
||||||
flex: 0 0 240px;
|
|
||||||
order: 1;
|
|
||||||
background: var(--darkened-bg);
|
|
||||||
border-left: none;
|
|
||||||
margin: 0 0 0 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter h2 {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 0.5px;
|
|
||||||
padding: 5px 15px;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter h3,
|
|
||||||
#changelist-filter details summary {
|
|
||||||
font-weight: 400;
|
|
||||||
padding: 0 15px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter details summary > * {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter details > summary {
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter details > summary::-webkit-details-marker {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter details > summary::before {
|
|
||||||
content: '→';
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--link-hover-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter details[open] > summary::before {
|
|
||||||
content: '↓';
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter ul {
|
|
||||||
margin: 5px 0;
|
|
||||||
padding: 0 15px 15px;
|
|
||||||
border-bottom: 1px solid var(--hairline-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter ul:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter li {
|
|
||||||
list-style-type: none;
|
|
||||||
margin-left: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter a {
|
|
||||||
display: block;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter li.selected {
|
|
||||||
border-left: 5px solid var(--hairline-color);
|
|
||||||
padding-left: 10px;
|
|
||||||
margin-left: -15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter li.selected a {
|
|
||||||
color: var(--link-selected-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter a:focus, #changelist-filter a:hover,
|
|
||||||
#changelist-filter li.selected a:focus,
|
|
||||||
#changelist-filter li.selected a:hover {
|
|
||||||
color: var(--link-hover-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter #changelist-filter-clear a {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
padding-bottom: 10px;
|
|
||||||
border-bottom: 1px solid var(--hairline-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* DATE DRILLDOWN */
|
|
||||||
|
|
||||||
.change-list .toplinks {
|
|
||||||
display: flex;
|
|
||||||
padding-bottom: 5px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 3px 17px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change-list .toplinks a {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change-list .toplinks .date-back {
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.change-list .toplinks .date-back:focus,
|
|
||||||
.change-list .toplinks .date-back:hover {
|
|
||||||
color: var(--link-hover-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ACTIONS */
|
|
||||||
|
|
||||||
.filtered .actions {
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist table input {
|
|
||||||
margin: 0;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Once the :has() pseudo-class is supported by all browsers, the tr.selected
|
|
||||||
selector and the JS adding the class can be removed. */
|
|
||||||
#changelist tbody tr.selected {
|
|
||||||
background-color: var(--selected-row);
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist tbody tr:has(.action-select:checked) {
|
|
||||||
background-color: var(--selected-row);
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions {
|
|
||||||
padding: 10px;
|
|
||||||
background: var(--body-bg);
|
|
||||||
border-top: none;
|
|
||||||
border-bottom: none;
|
|
||||||
line-height: 1.5rem;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions span.all,
|
|
||||||
#changelist .actions span.action-counter,
|
|
||||||
#changelist .actions span.clear,
|
|
||||||
#changelist .actions span.question {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
margin: 0 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions select {
|
|
||||||
vertical-align: top;
|
|
||||||
height: 1.5rem;
|
|
||||||
color: var(--body-fg);
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
padding: 0 0 0 4px;
|
|
||||||
margin: 0;
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions select:focus {
|
|
||||||
border-color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions label {
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions .button {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
background: var(--body-bg);
|
|
||||||
box-shadow: 0 -15px 20px -10px rgba(0, 0, 0, 0.15) inset;
|
|
||||||
cursor: pointer;
|
|
||||||
height: 1.5rem;
|
|
||||||
line-height: 1;
|
|
||||||
padding: 4px 8px;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--body-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions .button:focus, #changelist .actions .button:hover {
|
|
||||||
border-color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
@ -1,137 +0,0 @@
|
|||||||
@media (prefers-color-scheme: dark) {
|
|
||||||
:root {
|
|
||||||
--primary: #264b5d;
|
|
||||||
--primary-fg: #f7f7f7;
|
|
||||||
|
|
||||||
--body-fg: #eeeeee;
|
|
||||||
--body-bg: #121212;
|
|
||||||
--body-quiet-color: #e0e0e0;
|
|
||||||
--body-loud-color: #ffffff;
|
|
||||||
|
|
||||||
--breadcrumbs-link-fg: #e0e0e0;
|
|
||||||
--breadcrumbs-bg: var(--primary);
|
|
||||||
|
|
||||||
--link-fg: #81d4fa;
|
|
||||||
--link-hover-color: #4ac1f7;
|
|
||||||
--link-selected-fg: #6f94c6;
|
|
||||||
|
|
||||||
--hairline-color: #272727;
|
|
||||||
--border-color: #353535;
|
|
||||||
|
|
||||||
--error-fg: #e35f5f;
|
|
||||||
--message-success-bg: #006b1b;
|
|
||||||
--message-warning-bg: #583305;
|
|
||||||
--message-error-bg: #570808;
|
|
||||||
|
|
||||||
--darkened-bg: #212121;
|
|
||||||
--selected-bg: #1b1b1b;
|
|
||||||
--selected-row: #00363a;
|
|
||||||
|
|
||||||
--close-button-bg: #333333;
|
|
||||||
--close-button-hover-bg: #666666;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
html[data-theme="dark"] {
|
|
||||||
--primary: #264b5d;
|
|
||||||
--primary-fg: #f7f7f7;
|
|
||||||
|
|
||||||
--body-fg: #eeeeee;
|
|
||||||
--body-bg: #121212;
|
|
||||||
--body-quiet-color: #e0e0e0;
|
|
||||||
--body-loud-color: #ffffff;
|
|
||||||
|
|
||||||
--breadcrumbs-link-fg: #e0e0e0;
|
|
||||||
--breadcrumbs-bg: var(--primary);
|
|
||||||
|
|
||||||
--link-fg: #81d4fa;
|
|
||||||
--link-hover-color: #4ac1f7;
|
|
||||||
--link-selected-fg: #6f94c6;
|
|
||||||
|
|
||||||
--hairline-color: #272727;
|
|
||||||
--border-color: #353535;
|
|
||||||
|
|
||||||
--error-fg: #e35f5f;
|
|
||||||
--message-success-bg: #006b1b;
|
|
||||||
--message-warning-bg: #583305;
|
|
||||||
--message-error-bg: #570808;
|
|
||||||
|
|
||||||
--darkened-bg: #212121;
|
|
||||||
--selected-bg: #1b1b1b;
|
|
||||||
--selected-row: #00363a;
|
|
||||||
|
|
||||||
--close-button-bg: #333333;
|
|
||||||
--close-button-hover-bg: #666666;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* THEME SWITCH */
|
|
||||||
.theme-toggle {
|
|
||||||
cursor: pointer;
|
|
||||||
border: none;
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin-inline-start: 5px;
|
|
||||||
margin-top: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.theme-toggle svg {
|
|
||||||
vertical-align: middle;
|
|
||||||
height: 1rem;
|
|
||||||
width: 1rem;
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Fully hide screen reader text so we only show the one matching the current
|
|
||||||
theme.
|
|
||||||
*/
|
|
||||||
.theme-toggle .visually-hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="auto"] .theme-toggle .theme-label-when-auto {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"] .theme-toggle .theme-label-when-dark {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"] .theme-toggle .theme-label-when-light {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ICONS */
|
|
||||||
.theme-toggle svg.theme-icon-when-auto,
|
|
||||||
.theme-toggle svg.theme-icon-when-dark,
|
|
||||||
.theme-toggle svg.theme-icon-when-light {
|
|
||||||
fill: var(--header-link-color);
|
|
||||||
color: var(--header-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="auto"] .theme-toggle svg.theme-icon-when-auto {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="dark"] .theme-toggle svg.theme-icon-when-dark {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
html[data-theme="light"] .theme-toggle svg.theme-icon-when-light {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.visually-hidden {
|
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
|
||||||
height: 1px;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
clip: rect(0,0,0,0);
|
|
||||||
white-space: nowrap;
|
|
||||||
border: 0;
|
|
||||||
color: var(--body-fg);
|
|
||||||
background-color: var(--body-bg);
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
/* DASHBOARD */
|
|
||||||
.dashboard td, .dashboard th {
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard .module table th {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard .module table td {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard .module table td a {
|
|
||||||
display: block;
|
|
||||||
padding-right: .6em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* RECENT ACTIONS MODULE */
|
|
||||||
|
|
||||||
.module ul.actionlist {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.actionlist li {
|
|
||||||
list-style-type: none;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
@ -1,530 +0,0 @@
|
|||||||
@import url('widgets.css');
|
|
||||||
|
|
||||||
/* FORM ROWS */
|
|
||||||
|
|
||||||
.form-row {
|
|
||||||
overflow: hidden;
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
border-bottom: 1px solid var(--hairline-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row img, .form-row input {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row label input[type="checkbox"] {
|
|
||||||
margin-top: 0;
|
|
||||||
vertical-align: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .form-row p {
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-container {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-multiline > div {
|
|
||||||
padding-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FORM LABELS */
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-weight: normal;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.required label, label.required {
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--body-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* RADIO BUTTONS */
|
|
||||||
|
|
||||||
form div.radiolist div {
|
|
||||||
padding-right: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form div.radiolist.inline div {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
form div.radiolist label {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
form div.radiolist input[type="radio"] {
|
|
||||||
margin: -2px 4px 0 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form ul.inline {
|
|
||||||
margin-left: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form ul.inline li {
|
|
||||||
float: left;
|
|
||||||
padding-right: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ALIGNED FIELDSETS */
|
|
||||||
|
|
||||||
.aligned label {
|
|
||||||
display: block;
|
|
||||||
padding: 4px 10px 0 0;
|
|
||||||
width: 160px;
|
|
||||||
word-wrap: break-word;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned label:not(.vCheckboxLabel):after {
|
|
||||||
content: '';
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
height: 1.625rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned label + p, .aligned .checkbox-row + div.help, .aligned label + div.readonly {
|
|
||||||
padding: 6px 0;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
overflow-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned ul label {
|
|
||||||
display: inline;
|
|
||||||
float: none;
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned .form-row input {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.colMS .aligned .vLargeTextField, .colMS .aligned .vXMLLargeTextField {
|
|
||||||
width: 350px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned ul {
|
|
||||||
margin-left: 160px;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned div.radiolist {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned p.help,
|
|
||||||
form .aligned div.help {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-left: 160px;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned p.date div.help.timezonewarning,
|
|
||||||
form .aligned p.datetime div.help.timezonewarning,
|
|
||||||
form .aligned p.time div.help.timezonewarning {
|
|
||||||
margin-left: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned p.help:last-child,
|
|
||||||
form .aligned div.help:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned input + p.help,
|
|
||||||
form .aligned textarea + p.help,
|
|
||||||
form .aligned select + p.help,
|
|
||||||
form .aligned input + div.help,
|
|
||||||
form .aligned textarea + div.help,
|
|
||||||
form .aligned select + div.help {
|
|
||||||
margin-left: 160px;
|
|
||||||
padding-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned ul li {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned table p {
|
|
||||||
margin-left: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned .vCheckboxLabel {
|
|
||||||
float: none;
|
|
||||||
width: auto;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: -3px;
|
|
||||||
padding: 0 0 5px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned .vCheckboxLabel + p.help,
|
|
||||||
.aligned .vCheckboxLabel + div.help {
|
|
||||||
margin-top: -4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.colM .aligned .vLargeTextField, .colM .aligned .vXMLLargeTextField {
|
|
||||||
width: 610px;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset .fieldBox {
|
|
||||||
margin-right: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* WIDE FIELDSETS */
|
|
||||||
|
|
||||||
.wide label {
|
|
||||||
width: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .wide p,
|
|
||||||
form .wide ul.errorlist,
|
|
||||||
form .wide input + p.help,
|
|
||||||
form .wide input + div.help {
|
|
||||||
margin-left: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .wide p.help,
|
|
||||||
form .wide div.help {
|
|
||||||
padding-left: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form div.help ul {
|
|
||||||
padding-left: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.colM fieldset.wide .vLargeTextField, .colM fieldset.wide .vXMLLargeTextField {
|
|
||||||
width: 450px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* COLLAPSED FIELDSETS */
|
|
||||||
|
|
||||||
fieldset.collapsed * {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset.collapsed h2, fieldset.collapsed {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset.collapsed {
|
|
||||||
border: 1px solid var(--hairline-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset.collapsed h2 {
|
|
||||||
background: var(--darkened-bg);
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset .collapse-toggle {
|
|
||||||
color: var(--header-link-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset.collapsed .collapse-toggle {
|
|
||||||
background: transparent;
|
|
||||||
display: inline;
|
|
||||||
color: var(--link-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* MONOSPACE TEXTAREAS */
|
|
||||||
|
|
||||||
fieldset.monospace textarea {
|
|
||||||
font-family: var(--font-family-monospace);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SUBMIT ROW */
|
|
||||||
|
|
||||||
.submit-row {
|
|
||||||
padding: 12px 14px 12px;
|
|
||||||
margin: 0 0 20px;
|
|
||||||
background: var(--darkened-bg);
|
|
||||||
border: 1px solid var(--hairline-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
display: flex;
|
|
||||||
gap: 10px;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
body.popup .submit-row {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row input {
|
|
||||||
height: 2.1875rem;
|
|
||||||
line-height: 0.9375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row input, .submit-row a {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row input.default {
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row a.deletelink {
|
|
||||||
margin-left: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row a.deletelink {
|
|
||||||
display: block;
|
|
||||||
background: var(--delete-button-bg);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 0.625rem 0.9375rem;
|
|
||||||
height: 0.9375rem;
|
|
||||||
line-height: 0.9375rem;
|
|
||||||
color: var(--button-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row a.closelink {
|
|
||||||
display: inline-block;
|
|
||||||
background: var(--close-button-bg);
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 10px 15px;
|
|
||||||
height: 0.9375rem;
|
|
||||||
line-height: 0.9375rem;
|
|
||||||
color: var(--button-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row a.deletelink:focus,
|
|
||||||
.submit-row a.deletelink:hover,
|
|
||||||
.submit-row a.deletelink:active {
|
|
||||||
background: var(--delete-button-hover-bg);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row a.closelink:focus,
|
|
||||||
.submit-row a.closelink:hover,
|
|
||||||
.submit-row a.closelink:active {
|
|
||||||
background: var(--close-button-hover-bg);
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CUSTOM FORM FIELDS */
|
|
||||||
|
|
||||||
.vSelectMultipleField {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vCheckboxField {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vDateField, .vTimeField {
|
|
||||||
margin-right: 2px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vDateField {
|
|
||||||
min-width: 6.85em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vTimeField {
|
|
||||||
min-width: 4.7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vURLField {
|
|
||||||
width: 30em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vLargeTextField, .vXMLLargeTextField {
|
|
||||||
width: 48em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flatpages-flatpage #id_content {
|
|
||||||
height: 40.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module table .vPositiveSmallIntegerField {
|
|
||||||
width: 2.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vIntegerField {
|
|
||||||
width: 5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vBigIntegerField {
|
|
||||||
width: 10em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vForeignKeyRawIdAdminField {
|
|
||||||
width: 5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vTextField, .vUUIDField {
|
|
||||||
width: 20em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* INLINES */
|
|
||||||
|
|
||||||
.inline-group {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0 0 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group thead th {
|
|
||||||
padding: 8px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group .aligned label {
|
|
||||||
width: 160px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-related {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-related h3 {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
padding: 5px;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
background: var(--darkened-bg);
|
|
||||||
border-top: 1px solid var(--hairline-color);
|
|
||||||
border-bottom: 1px solid var(--hairline-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-related h3 span.delete {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-related h3 span.delete label {
|
|
||||||
margin-left: 2px;
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-related fieldset {
|
|
||||||
margin: 0;
|
|
||||||
background: var(--body-bg);
|
|
||||||
border: none;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-related fieldset.module h3 {
|
|
||||||
margin: 0;
|
|
||||||
padding: 2px 5px 3px 5px;
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
text-align: left;
|
|
||||||
font-weight: bold;
|
|
||||||
background: #bcd;
|
|
||||||
color: var(--body-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group .tabular fieldset.module {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-related.tabular fieldset.module table {
|
|
||||||
width: 100%;
|
|
||||||
overflow-x: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
.last-related fieldset {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group .tabular tr.has_original td {
|
|
||||||
padding-top: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group .tabular tr td.original {
|
|
||||||
padding: 2px 0 0 0;
|
|
||||||
width: 0;
|
|
||||||
_position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group .tabular th.original {
|
|
||||||
width: 0px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group .tabular td.original p {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
height: 1.1em;
|
|
||||||
padding: 2px 9px;
|
|
||||||
overflow: hidden;
|
|
||||||
font-size: 0.5625rem;
|
|
||||||
font-weight: bold;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
_width: 700px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group ul.tools {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group ul.tools li {
|
|
||||||
display: inline;
|
|
||||||
padding: 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group div.add-row,
|
|
||||||
.inline-group .tabular tr.add-row td {
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
background: var(--darkened-bg);
|
|
||||||
padding: 8px 10px;
|
|
||||||
border-bottom: 1px solid var(--hairline-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group .tabular tr.add-row td {
|
|
||||||
padding: 8px 10px;
|
|
||||||
border-bottom: 1px solid var(--hairline-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group ul.tools a.add,
|
|
||||||
.inline-group div.add-row a,
|
|
||||||
.inline-group .tabular tr.add-row td a {
|
|
||||||
background: url(../img/icon-addlink.svg) 0 1px no-repeat;
|
|
||||||
padding-left: 16px;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.empty-form {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* RELATED FIELD ADD ONE / LOOKUP */
|
|
||||||
|
|
||||||
.related-lookup {
|
|
||||||
margin-left: 5px;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-lookup {
|
|
||||||
width: 1rem;
|
|
||||||
height: 1rem;
|
|
||||||
background-image: url(../img/search.svg);
|
|
||||||
}
|
|
||||||
|
|
||||||
form .related-widget-wrapper ul {
|
|
||||||
display: inline-block;
|
|
||||||
margin-left: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clearable-file-input input {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
@ -1,61 +0,0 @@
|
|||||||
/* LOGIN FORM */
|
|
||||||
|
|
||||||
.login {
|
|
||||||
background: var(--darkened-bg);
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login #header {
|
|
||||||
height: auto;
|
|
||||||
padding: 15px 16px;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login #header h1 {
|
|
||||||
font-size: 1.125rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login #header h1 a {
|
|
||||||
color: var(--header-link-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.login #content {
|
|
||||||
padding: 20px 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login #container {
|
|
||||||
background: var(--body-bg);
|
|
||||||
border: 1px solid var(--hairline-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
width: 28em;
|
|
||||||
min-width: 300px;
|
|
||||||
margin: 100px auto;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login .form-row {
|
|
||||||
padding: 4px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login .form-row label {
|
|
||||||
display: block;
|
|
||||||
line-height: 2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login .form-row #id_username, .login .form-row #id_password {
|
|
||||||
padding: 8px;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login .submit-row {
|
|
||||||
padding: 1em 0 0 0;
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login .password-reset-link {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
@ -1,144 +0,0 @@
|
|||||||
.sticky {
|
|
||||||
position: sticky;
|
|
||||||
top: 0;
|
|
||||||
max-height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-nav-sidebar {
|
|
||||||
z-index: 20;
|
|
||||||
left: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
flex: 0 0 23px;
|
|
||||||
width: 23px;
|
|
||||||
border: 0;
|
|
||||||
border-right: 1px solid var(--hairline-color);
|
|
||||||
background-color: var(--body-bg);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1.25rem;
|
|
||||||
color: var(--link-fg);
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] .toggle-nav-sidebar {
|
|
||||||
border-left: 1px solid var(--hairline-color);
|
|
||||||
border-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-nav-sidebar:hover,
|
|
||||||
.toggle-nav-sidebar:focus {
|
|
||||||
background-color: var(--darkened-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav-sidebar {
|
|
||||||
z-index: 15;
|
|
||||||
flex: 0 0 275px;
|
|
||||||
left: -276px;
|
|
||||||
margin-left: -276px;
|
|
||||||
border-top: 1px solid transparent;
|
|
||||||
border-right: 1px solid var(--hairline-color);
|
|
||||||
background-color: var(--body-bg);
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] #nav-sidebar {
|
|
||||||
border-left: 1px solid var(--hairline-color);
|
|
||||||
border-right: 0;
|
|
||||||
left: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
right: -276px;
|
|
||||||
margin-right: -276px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.toggle-nav-sidebar::before {
|
|
||||||
content: '\00BB';
|
|
||||||
}
|
|
||||||
|
|
||||||
.main.shifted .toggle-nav-sidebar::before {
|
|
||||||
content: '\00AB';
|
|
||||||
}
|
|
||||||
|
|
||||||
.main > #nav-sidebar {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main.shifted > #nav-sidebar {
|
|
||||||
margin-left: 0;
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] .main.shifted > #nav-sidebar {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav-sidebar .module th {
|
|
||||||
width: 100%;
|
|
||||||
overflow-wrap: anywhere;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav-sidebar .module th,
|
|
||||||
#nav-sidebar .module caption {
|
|
||||||
padding-left: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav-sidebar .module td {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] #nav-sidebar .module th,
|
|
||||||
[dir="rtl"] #nav-sidebar .module caption {
|
|
||||||
padding-left: 8px;
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav-sidebar .current-app .section:link,
|
|
||||||
#nav-sidebar .current-app .section:visited {
|
|
||||||
color: var(--header-color);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav-sidebar .current-model {
|
|
||||||
background: var(--selected-row);
|
|
||||||
}
|
|
||||||
|
|
||||||
.main > #nav-sidebar + .content {
|
|
||||||
max-width: calc(100% - 23px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.main.shifted > #nav-sidebar + .content {
|
|
||||||
max-width: calc(100% - 299px);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
#nav-sidebar, #toggle-nav-sidebar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.main > #nav-sidebar + .content,
|
|
||||||
.main.shifted > #nav-sidebar + .content {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav-filter {
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: 2px 5px;
|
|
||||||
margin: 5px 0;
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
background-color: var(--darkened-bg);
|
|
||||||
color: var(--body-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav-filter:focus {
|
|
||||||
border-color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav-filter.no-results {
|
|
||||||
background: var(--message-error-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#nav-sidebar table {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
@ -1,998 +0,0 @@
|
|||||||
/* Tablets */
|
|
||||||
|
|
||||||
input[type="submit"], button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
/* Basic */
|
|
||||||
|
|
||||||
html {
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
td, th {
|
|
||||||
padding: 10px;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small {
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Layout */
|
|
||||||
|
|
||||||
#container {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content {
|
|
||||||
padding: 15px 20px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.breadcrumbs {
|
|
||||||
padding: 10px 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Header */
|
|
||||||
|
|
||||||
#header {
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 15px 30px;
|
|
||||||
justify-content: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
#branding h1 {
|
|
||||||
margin: 0 0 8px;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
#user-tools {
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1.85;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
#user-tools a {
|
|
||||||
display: inline-block;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dashboard */
|
|
||||||
|
|
||||||
.dashboard #content {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content-related {
|
|
||||||
margin-right: -290px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.colSM #content-related {
|
|
||||||
margin-left: -290px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.colMS {
|
|
||||||
margin-right: 290px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.colSM {
|
|
||||||
margin-left: 290px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dashboard .module table td a {
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
td .changelink, td .addlink {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Changelist */
|
|
||||||
|
|
||||||
#toolbar {
|
|
||||||
border: none;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-search > div {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
max-width: 480px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-search label {
|
|
||||||
line-height: 1.375rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toolbar form #searchbar {
|
|
||||||
flex: 1 0 auto;
|
|
||||||
width: 0;
|
|
||||||
height: 1.375rem;
|
|
||||||
margin: 0 10px 0 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toolbar form input[type=submit] {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-search .quiet {
|
|
||||||
width: 0;
|
|
||||||
flex: 1 0 auto;
|
|
||||||
margin: 5px 0 0 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
padding: 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions label {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions select {
|
|
||||||
background: var(--body-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions .button {
|
|
||||||
min-width: 48px;
|
|
||||||
margin: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions span.all,
|
|
||||||
#changelist .actions span.clear,
|
|
||||||
#changelist .actions span.question,
|
|
||||||
#changelist .actions span.action-counter {
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
margin: 0 10px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter {
|
|
||||||
flex-basis: 200px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.change-list .filtered .results,
|
|
||||||
.change-list .filtered .paginator,
|
|
||||||
.filtered #toolbar,
|
|
||||||
.filtered .actions,
|
|
||||||
|
|
||||||
#changelist .paginator {
|
|
||||||
border-top-color: var(--hairline-color); /* XXX Is this used at all? */
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .results + .paginator {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Forms */
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row input[type=text],
|
|
||||||
.form-row input[type=password],
|
|
||||||
.form-row input[type=email],
|
|
||||||
.form-row input[type=url],
|
|
||||||
.form-row input[type=tel],
|
|
||||||
.form-row input[type=number],
|
|
||||||
.form-row textarea,
|
|
||||||
.form-row select,
|
|
||||||
.form-row .vTextField {
|
|
||||||
box-sizing: border-box;
|
|
||||||
margin: 0;
|
|
||||||
padding: 6px 8px;
|
|
||||||
min-height: 2.25rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row select {
|
|
||||||
height: 2.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row select[multiple] {
|
|
||||||
height: auto;
|
|
||||||
min-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset .fieldBox + .fieldBox {
|
|
||||||
margin-top: 10px;
|
|
||||||
padding-top: 10px;
|
|
||||||
border-top: 1px solid var(--hairline-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned label {
|
|
||||||
padding-top: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned .related-lookup,
|
|
||||||
.aligned .datetimeshortcuts,
|
|
||||||
.aligned .related-lookup + strong {
|
|
||||||
align-self: center;
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned div.radiolist {
|
|
||||||
margin-left: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row {
|
|
||||||
padding: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row a.deletelink {
|
|
||||||
padding: 10px 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button, input[type=submit], input[type=button], .submit-row input, a.button {
|
|
||||||
padding: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Related widget */
|
|
||||||
|
|
||||||
.related-widget-wrapper {
|
|
||||||
float: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-widget-wrapper-link + .selector {
|
|
||||||
max-width: calc(100% - 30px);
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
select + .related-widget-wrapper-link,
|
|
||||||
.related-widget-wrapper-link + .related-widget-wrapper-link {
|
|
||||||
margin-left: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Selector */
|
|
||||||
|
|
||||||
.selector {
|
|
||||||
display: flex;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector .selector-filter {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector .selector-filter label {
|
|
||||||
margin: 0 8px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector .selector-filter input {
|
|
||||||
width: auto;
|
|
||||||
min-height: 0;
|
|
||||||
flex: 1 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-available, .selector-chosen {
|
|
||||||
width: auto;
|
|
||||||
flex: 1 1;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector select {
|
|
||||||
width: 100%;
|
|
||||||
flex: 1 0 auto;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector ul.selector-chooser {
|
|
||||||
width: 26px;
|
|
||||||
height: 52px;
|
|
||||||
padding: 2px 0;
|
|
||||||
margin: auto 15px;
|
|
||||||
border-radius: 20px;
|
|
||||||
transform: translateY(-10px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-add, .selector-remove {
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
background-size: 20px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-add {
|
|
||||||
background-position: 0 -120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-remove {
|
|
||||||
background-position: 0 -80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.selector-chooseall, a.selector-clearall {
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked {
|
|
||||||
flex-direction: column;
|
|
||||||
max-width: 480px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked > * {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked select {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .selector-available, .stacked .selector-chosen {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked ul.selector-chooser {
|
|
||||||
width: 52px;
|
|
||||||
height: 26px;
|
|
||||||
padding: 0 2px;
|
|
||||||
margin: 15px auto;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .selector-chooser li {
|
|
||||||
padding: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .selector-add, .stacked .selector-remove {
|
|
||||||
background-size: 20px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .selector-add {
|
|
||||||
background-position: 0 -40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .active.selector-add {
|
|
||||||
background-position: 0 -40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active.selector-add:focus, .active.selector-add:hover {
|
|
||||||
background-position: 0 -140px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .active.selector-add:focus, .stacked .active.selector-add:hover {
|
|
||||||
background-position: 0 -60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .selector-remove {
|
|
||||||
background-position: 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .active.selector-remove {
|
|
||||||
background-position: 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active.selector-remove:focus, .active.selector-remove:hover {
|
|
||||||
background-position: 0 -100px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .active.selector-remove:focus, .stacked .active.selector-remove:hover {
|
|
||||||
background-position: 0 -20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.help-tooltip, .selector .help-icon {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetime input {
|
|
||||||
width: 50%;
|
|
||||||
max-width: 120px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetime span {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetime .timezonewarning {
|
|
||||||
display: block;
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetimeshortcuts {
|
|
||||||
color: var(--border-color); /* XXX Redundant, .datetime span also sets #ccc */
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
|
|
||||||
width: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Messages */
|
|
||||||
|
|
||||||
ul.messagelist li {
|
|
||||||
padding-left: 55px;
|
|
||||||
background-position: 30px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.messagelist li.error {
|
|
||||||
background-position: 30px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.messagelist li.warning {
|
|
||||||
background-position: 30px 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Login */
|
|
||||||
|
|
||||||
.login #header {
|
|
||||||
padding: 15px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login #branding h1 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* GIS */
|
|
||||||
|
|
||||||
div.olMap {
|
|
||||||
max-width: calc(100vw - 30px);
|
|
||||||
max-height: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.olMap + .clear_features {
|
|
||||||
display: block;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Docs */
|
|
||||||
|
|
||||||
.module table.xfull {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre.literal-block {
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mobile */
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
/* Layout */
|
|
||||||
|
|
||||||
#header, #content, #footer {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#footer:empty {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.breadcrumbs {
|
|
||||||
padding: 10px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Dashboard */
|
|
||||||
|
|
||||||
.colMS, .colSM {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content-related, .colSM #content-related {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content-related .module {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content-related .module h2 {
|
|
||||||
padding: 10px 15px;
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Changelist */
|
|
||||||
|
|
||||||
#changelist {
|
|
||||||
align-items: stretch;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
#toolbar {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions label {
|
|
||||||
flex: 1 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions select {
|
|
||||||
flex: 1 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist .actions span {
|
|
||||||
flex: 1 0 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter {
|
|
||||||
position: static;
|
|
||||||
width: auto;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.object-tools {
|
|
||||||
float: none;
|
|
||||||
margin: 0 0 15px;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.object-tools li {
|
|
||||||
height: auto;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.object-tools li + li {
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Forms */
|
|
||||||
|
|
||||||
.form-row {
|
|
||||||
padding: 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned .form-row,
|
|
||||||
.aligned .form-row > div {
|
|
||||||
max-width: 100vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned .form-row > div {
|
|
||||||
width: calc(100vw - 30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex-container {
|
|
||||||
flex-flow: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
max-width: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vURLField {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset .fieldBox + .fieldBox {
|
|
||||||
margin-top: 15px;
|
|
||||||
padding-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset.collapsed .form-row {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned label {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned label:after {
|
|
||||||
max-height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned .form-row input,
|
|
||||||
.aligned .form-row select,
|
|
||||||
.aligned .form-row textarea {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned .checkbox-row {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned .checkbox-row input {
|
|
||||||
flex: 0 1 auto;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned .vCheckboxLabel {
|
|
||||||
flex: 1 0;
|
|
||||||
padding: 1px 0 0 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned label + p,
|
|
||||||
.aligned label + div.help,
|
|
||||||
.aligned label + div.readonly {
|
|
||||||
padding: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned p.file-upload {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.clearable-file-input {
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.clearable-file-input label {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned .timezonewarning {
|
|
||||||
flex: 1 0 100%;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned .form-row div.help {
|
|
||||||
width: 100%;
|
|
||||||
margin: 5px 0 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned ul,
|
|
||||||
form .aligned ul.errorlist {
|
|
||||||
margin-left: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned div.radiolist {
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-right: 15px;
|
|
||||||
margin-bottom: -3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned div.radiolist:not(.inline) div + div {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Related widget */
|
|
||||||
|
|
||||||
.related-widget-wrapper {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-widget-wrapper .selector {
|
|
||||||
order: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-widget-wrapper > a {
|
|
||||||
order: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-widget-wrapper .radiolist ~ a {
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-widget-wrapper > select ~ a {
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
select + .related-widget-wrapper-link,
|
|
||||||
.related-widget-wrapper-link + .related-widget-wrapper-link {
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Selector */
|
|
||||||
|
|
||||||
.selector {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector > * {
|
|
||||||
float: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-available, .selector-chosen {
|
|
||||||
margin-bottom: 0;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector select {
|
|
||||||
max-height: 96px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector ul.selector-chooser {
|
|
||||||
display: block;
|
|
||||||
float: none;
|
|
||||||
width: 52px;
|
|
||||||
height: 26px;
|
|
||||||
padding: 0 2px;
|
|
||||||
margin: 15px auto 20px;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector ul.selector-chooser li {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-remove {
|
|
||||||
background-position: 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active.selector-remove:focus, .active.selector-remove:hover {
|
|
||||||
background-position: 0 -20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-add {
|
|
||||||
background-position: 0 -40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active.selector-add:focus, .active.selector-add:hover {
|
|
||||||
background-position: 0 -60px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Inlines */
|
|
||||||
|
|
||||||
.inline-group[data-inline-type="stacked"] .inline-related {
|
|
||||||
border: 1px solid var(--hairline-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
margin-top: 15px;
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group[data-inline-type="stacked"] .inline-related > * {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group[data-inline-type="stacked"] .inline-related .module {
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group[data-inline-type="stacked"] .inline-related .module .form-row {
|
|
||||||
border-top: 1px solid var(--hairline-color);
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group[data-inline-type="stacked"] .inline-related .module .form-row:first-child {
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group[data-inline-type="stacked"] .inline-related h3 {
|
|
||||||
padding: 10px;
|
|
||||||
border-top-width: 0;
|
|
||||||
border-bottom-width: 2px;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group[data-inline-type="stacked"] .inline-related h3 .inline_label {
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group[data-inline-type="stacked"] .inline-related h3 span.delete {
|
|
||||||
float: none;
|
|
||||||
flex: 1 1 100%;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group[data-inline-type="stacked"] .aligned .form-row > div:not([class]) {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group[data-inline-type="stacked"] .aligned label {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group[data-inline-type="stacked"] div.add-row {
|
|
||||||
margin-top: 15px;
|
|
||||||
border: 1px solid var(--hairline-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group div.add-row,
|
|
||||||
.inline-group .tabular tr.add-row td {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-group div.add-row a,
|
|
||||||
.inline-group .tabular tr.add-row td a {
|
|
||||||
display: block;
|
|
||||||
padding: 8px 10px 8px 26px;
|
|
||||||
background-position: 8px 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Submit row */
|
|
||||||
|
|
||||||
.submit-row {
|
|
||||||
padding: 10px;
|
|
||||||
margin: 0 0 15px;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row input, .submit-row input.default, .submit-row a {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row a.closelink {
|
|
||||||
padding: 10px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row a.deletelink {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Messages */
|
|
||||||
|
|
||||||
ul.messagelist li {
|
|
||||||
padding-left: 40px;
|
|
||||||
background-position: 15px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.messagelist li.error {
|
|
||||||
background-position: 15px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.messagelist li.warning {
|
|
||||||
background-position: 15px 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Paginator */
|
|
||||||
|
|
||||||
.paginator .this-page, .paginator a:link, .paginator a:visited {
|
|
||||||
padding: 4px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Login */
|
|
||||||
|
|
||||||
body.login {
|
|
||||||
padding: 0 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login #container {
|
|
||||||
width: auto;
|
|
||||||
max-width: 480px;
|
|
||||||
margin: 50px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login #header,
|
|
||||||
.login #content {
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login #content-main {
|
|
||||||
float: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login .form-row {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login .form-row + .form-row {
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login .form-row label {
|
|
||||||
margin: 0 0 5px;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login .submit-row {
|
|
||||||
padding: 15px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login br {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login .submit-row input {
|
|
||||||
margin: 0;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.errornote {
|
|
||||||
margin: 0 0 20px;
|
|
||||||
padding: 8px 12px;
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Calendar and clock */
|
|
||||||
|
|
||||||
.calendarbox, .clockbox {
|
|
||||||
position: fixed !important;
|
|
||||||
top: 50% !important;
|
|
||||||
left: 50% !important;
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
margin: 0;
|
|
||||||
border: none;
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarbox:before, .clockbox:before {
|
|
||||||
content: '';
|
|
||||||
position: fixed;
|
|
||||||
top: 50%;
|
|
||||||
left: 50%;
|
|
||||||
width: 100vw;
|
|
||||||
height: 100vh;
|
|
||||||
background: rgba(0, 0, 0, 0.75);
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarbox > *, .clockbox > * {
|
|
||||||
position: relative;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarbox > div:first-child {
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarbox .calendar, .clockbox h2 {
|
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarbox .calendar-cancel, .clockbox .calendar-cancel {
|
|
||||||
border-radius: 0 0 4px 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-shortcuts {
|
|
||||||
padding: 10px 0;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
line-height: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-shortcuts a {
|
|
||||||
margin: 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timelist a {
|
|
||||||
background: var(--body-bg);
|
|
||||||
padding: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-cancel {
|
|
||||||
padding: 8px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clockbox h2 {
|
|
||||||
padding: 8px 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar caption {
|
|
||||||
padding: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
|
|
||||||
z-index: 1;
|
|
||||||
top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* History */
|
|
||||||
|
|
||||||
table#change-history tbody th, table#change-history tbody td {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
table#change-history tbody th {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Docs */
|
|
||||||
|
|
||||||
table.model tbody th, table.model tbody td {
|
|
||||||
font-size: 0.8125rem;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,81 +0,0 @@
|
|||||||
/* TABLETS */
|
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
|
||||||
[dir="rtl"] .colMS {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] #user-tools {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] #changelist .actions label {
|
|
||||||
padding-left: 10px;
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] #changelist .actions select {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] .change-list .filtered .results,
|
|
||||||
[dir="rtl"] .change-list .filtered .paginator,
|
|
||||||
[dir="rtl"] .filtered #toolbar,
|
|
||||||
[dir="rtl"] .filtered div.xfull,
|
|
||||||
[dir="rtl"] .filtered .actions,
|
|
||||||
[dir="rtl"] #changelist-filter {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] .inline-group ul.tools a.add,
|
|
||||||
[dir="rtl"] .inline-group div.add-row a,
|
|
||||||
[dir="rtl"] .inline-group .tabular tr.add-row td a {
|
|
||||||
padding: 8px 26px 8px 10px;
|
|
||||||
background-position: calc(100% - 8px) 9px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] .related-widget-wrapper-link + .selector {
|
|
||||||
margin-right: 0;
|
|
||||||
margin-left: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] .selector .selector-filter label {
|
|
||||||
margin-right: 0;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] .object-tools li {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] .object-tools li + li {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] .dashboard .module table td a {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* MOBILE */
|
|
||||||
|
|
||||||
@media (max-width: 767px) {
|
|
||||||
[dir="rtl"] .aligned .related-lookup,
|
|
||||||
[dir="rtl"] .aligned .datetimeshortcuts {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] .aligned ul,
|
|
||||||
[dir="rtl"] form .aligned ul.errorlist {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
[dir="rtl"] #changelist-filter {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,288 +0,0 @@
|
|||||||
/* GLOBAL */
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module h2, .module caption {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.module ul, .module ol {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewlink, .addlink, .changelink {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: 16px;
|
|
||||||
background-position: 100% 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deletelink {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: 16px;
|
|
||||||
background-position: 100% 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.object-tools {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead th:first-child,
|
|
||||||
tfoot td:first-child {
|
|
||||||
border-left: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* LAYOUT */
|
|
||||||
|
|
||||||
#user-tools {
|
|
||||||
right: auto;
|
|
||||||
left: 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.breadcrumbs {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content-main {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content-related {
|
|
||||||
float: left;
|
|
||||||
margin-left: -300px;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.colMS {
|
|
||||||
margin-left: 300px;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* SORTABLE TABLES */
|
|
||||||
|
|
||||||
table thead th.sorted .sortoptions {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
thead th.sorted .text {
|
|
||||||
padding-right: 0;
|
|
||||||
padding-left: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* dashboard styles */
|
|
||||||
|
|
||||||
.dashboard .module table td a {
|
|
||||||
padding-left: .6em;
|
|
||||||
padding-right: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* changelists styles */
|
|
||||||
|
|
||||||
.change-list .filtered table {
|
|
||||||
border-left: none;
|
|
||||||
border-right: 0px none;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter {
|
|
||||||
border-left: none;
|
|
||||||
border-right: none;
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist-filter li.selected {
|
|
||||||
border-left: none;
|
|
||||||
padding-left: 10px;
|
|
||||||
margin-left: 0;
|
|
||||||
border-right: 5px solid var(--hairline-color);
|
|
||||||
padding-right: 10px;
|
|
||||||
margin-right: -15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#changelist table tbody td:first-child, #changelist table tbody th:first-child {
|
|
||||||
border-right: none;
|
|
||||||
border-left: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FORMS */
|
|
||||||
|
|
||||||
.aligned label {
|
|
||||||
padding: 0 0 3px 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row a.deletelink {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.vDateField, .vTimeField {
|
|
||||||
margin-left: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.aligned .form-row input {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned ul {
|
|
||||||
margin-right: 163px;
|
|
||||||
padding-right: 10px;
|
|
||||||
margin-left: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form ul.inline li {
|
|
||||||
float: right;
|
|
||||||
padding-right: 0;
|
|
||||||
padding-left: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .aligned p.help,
|
|
||||||
form .aligned div.help {
|
|
||||||
margin-right: 160px;
|
|
||||||
padding-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form div.help ul,
|
|
||||||
form .aligned .checkbox-row + .help,
|
|
||||||
form .aligned p.date div.help.timezonewarning,
|
|
||||||
form .aligned p.datetime div.help.timezonewarning,
|
|
||||||
form .aligned p.time div.help.timezonewarning {
|
|
||||||
margin-right: 0;
|
|
||||||
padding-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .wide p.help, form .wide div.help {
|
|
||||||
padding-left: 0;
|
|
||||||
padding-right: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .wide p,
|
|
||||||
form .wide ul.errorlist,
|
|
||||||
form .wide input + p.help,
|
|
||||||
form .wide input + div.help {
|
|
||||||
margin-right: 200px;
|
|
||||||
margin-left: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.submit-row {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset .fieldBox {
|
|
||||||
margin-left: 20px;
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.errorlist li {
|
|
||||||
background-position: 100% 12px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.errornote {
|
|
||||||
background-position: 100% 12px;
|
|
||||||
padding: 10px 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* WIDGETS */
|
|
||||||
|
|
||||||
.calendarnav-previous {
|
|
||||||
top: 0;
|
|
||||||
left: auto;
|
|
||||||
right: 10px;
|
|
||||||
background: url(../img/calendar-icons.svg) 0 -30px no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarbox .calendarnav-previous:focus,
|
|
||||||
.calendarbox .calendarnav-previous:hover {
|
|
||||||
background-position: 0 -45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarnav-next {
|
|
||||||
top: 0;
|
|
||||||
right: auto;
|
|
||||||
left: 10px;
|
|
||||||
background: url(../img/calendar-icons.svg) 0 0 no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarbox .calendarnav-next:focus,
|
|
||||||
.calendarbox .calendarnav-next:hover {
|
|
||||||
background-position: 0 -15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar caption, .calendarbox h2 {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector .selector-filter {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-add {
|
|
||||||
background: url(../img/selector-icons.svg) 0 -64px no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active.selector-add:focus, .active.selector-add:hover {
|
|
||||||
background-position: 0 -80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-remove {
|
|
||||||
background: url(../img/selector-icons.svg) 0 -96px no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active.selector-remove:focus, .active.selector-remove:hover {
|
|
||||||
background-position: 0 -112px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.selector-chooseall {
|
|
||||||
background: url(../img/selector-icons.svg) right -128px no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.active.selector-chooseall:focus, a.active.selector-chooseall:hover {
|
|
||||||
background-position: 100% -144px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.selector-clearall {
|
|
||||||
background: url(../img/selector-icons.svg) 0 -160px no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.active.selector-clearall:focus, a.active.selector-clearall:hover {
|
|
||||||
background-position: 0 -176px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-deletelink {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
form .form-row p.datetime {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-widget-wrapper {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* MISC */
|
|
||||||
|
|
||||||
.inline-related h2, .inline-group h2 {
|
|
||||||
text-align: right
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-related h3 span.delete {
|
|
||||||
padding-right: 20px;
|
|
||||||
padding-left: inherit;
|
|
||||||
left: 10px;
|
|
||||||
right: inherit;
|
|
||||||
float:left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-related h3 span.delete label {
|
|
||||||
margin-left: inherit;
|
|
||||||
margin-right: 2px;
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2012-2017 Kevin Brown, Igor Vaynberg, and Select2 contributors
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
@ -1,481 +0,0 @@
|
|||||||
.select2-container {
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0;
|
|
||||||
position: relative;
|
|
||||||
vertical-align: middle; }
|
|
||||||
.select2-container .select2-selection--single {
|
|
||||||
box-sizing: border-box;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
height: 28px;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none; }
|
|
||||||
.select2-container .select2-selection--single .select2-selection__rendered {
|
|
||||||
display: block;
|
|
||||||
padding-left: 8px;
|
|
||||||
padding-right: 20px;
|
|
||||||
overflow: hidden;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap; }
|
|
||||||
.select2-container .select2-selection--single .select2-selection__clear {
|
|
||||||
position: relative; }
|
|
||||||
.select2-container[dir="rtl"] .select2-selection--single .select2-selection__rendered {
|
|
||||||
padding-right: 8px;
|
|
||||||
padding-left: 20px; }
|
|
||||||
.select2-container .select2-selection--multiple {
|
|
||||||
box-sizing: border-box;
|
|
||||||
cursor: pointer;
|
|
||||||
display: block;
|
|
||||||
min-height: 32px;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none; }
|
|
||||||
.select2-container .select2-selection--multiple .select2-selection__rendered {
|
|
||||||
display: inline-block;
|
|
||||||
overflow: hidden;
|
|
||||||
padding-left: 8px;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
white-space: nowrap; }
|
|
||||||
.select2-container .select2-search--inline {
|
|
||||||
float: left; }
|
|
||||||
.select2-container .select2-search--inline .select2-search__field {
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: none;
|
|
||||||
font-size: 100%;
|
|
||||||
margin-top: 5px;
|
|
||||||
padding: 0; }
|
|
||||||
.select2-container .select2-search--inline .select2-search__field::-webkit-search-cancel-button {
|
|
||||||
-webkit-appearance: none; }
|
|
||||||
|
|
||||||
.select2-dropdown {
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
border-radius: 4px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
left: -100000px;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1051; }
|
|
||||||
|
|
||||||
.select2-results {
|
|
||||||
display: block; }
|
|
||||||
|
|
||||||
.select2-results__options {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0; }
|
|
||||||
|
|
||||||
.select2-results__option {
|
|
||||||
padding: 6px;
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: none; }
|
|
||||||
.select2-results__option[aria-selected] {
|
|
||||||
cursor: pointer; }
|
|
||||||
|
|
||||||
.select2-container--open .select2-dropdown {
|
|
||||||
left: 0; }
|
|
||||||
|
|
||||||
.select2-container--open .select2-dropdown--above {
|
|
||||||
border-bottom: none;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-bottom-right-radius: 0; }
|
|
||||||
|
|
||||||
.select2-container--open .select2-dropdown--below {
|
|
||||||
border-top: none;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-top-right-radius: 0; }
|
|
||||||
|
|
||||||
.select2-search--dropdown {
|
|
||||||
display: block;
|
|
||||||
padding: 4px; }
|
|
||||||
.select2-search--dropdown .select2-search__field {
|
|
||||||
padding: 4px;
|
|
||||||
width: 100%;
|
|
||||||
box-sizing: border-box; }
|
|
||||||
.select2-search--dropdown .select2-search__field::-webkit-search-cancel-button {
|
|
||||||
-webkit-appearance: none; }
|
|
||||||
.select2-search--dropdown.select2-search--hide {
|
|
||||||
display: none; }
|
|
||||||
|
|
||||||
.select2-close-mask {
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
display: block;
|
|
||||||
position: fixed;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
min-height: 100%;
|
|
||||||
min-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
width: auto;
|
|
||||||
opacity: 0;
|
|
||||||
z-index: 99;
|
|
||||||
background-color: #fff;
|
|
||||||
filter: alpha(opacity=0); }
|
|
||||||
|
|
||||||
.select2-hidden-accessible {
|
|
||||||
border: 0 !important;
|
|
||||||
clip: rect(0 0 0 0) !important;
|
|
||||||
-webkit-clip-path: inset(50%) !important;
|
|
||||||
clip-path: inset(50%) !important;
|
|
||||||
height: 1px !important;
|
|
||||||
overflow: hidden !important;
|
|
||||||
padding: 0 !important;
|
|
||||||
position: absolute !important;
|
|
||||||
width: 1px !important;
|
|
||||||
white-space: nowrap !important; }
|
|
||||||
|
|
||||||
.select2-container--default .select2-selection--single {
|
|
||||||
background-color: #fff;
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
border-radius: 4px; }
|
|
||||||
.select2-container--default .select2-selection--single .select2-selection__rendered {
|
|
||||||
color: #444;
|
|
||||||
line-height: 28px; }
|
|
||||||
.select2-container--default .select2-selection--single .select2-selection__clear {
|
|
||||||
cursor: pointer;
|
|
||||||
float: right;
|
|
||||||
font-weight: bold; }
|
|
||||||
.select2-container--default .select2-selection--single .select2-selection__placeholder {
|
|
||||||
color: #999; }
|
|
||||||
.select2-container--default .select2-selection--single .select2-selection__arrow {
|
|
||||||
height: 26px;
|
|
||||||
position: absolute;
|
|
||||||
top: 1px;
|
|
||||||
right: 1px;
|
|
||||||
width: 20px; }
|
|
||||||
.select2-container--default .select2-selection--single .select2-selection__arrow b {
|
|
||||||
border-color: #888 transparent transparent transparent;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 5px 4px 0 4px;
|
|
||||||
height: 0;
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -4px;
|
|
||||||
margin-top: -2px;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
width: 0; }
|
|
||||||
|
|
||||||
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__clear {
|
|
||||||
float: left; }
|
|
||||||
|
|
||||||
.select2-container--default[dir="rtl"] .select2-selection--single .select2-selection__arrow {
|
|
||||||
left: 1px;
|
|
||||||
right: auto; }
|
|
||||||
|
|
||||||
.select2-container--default.select2-container--disabled .select2-selection--single {
|
|
||||||
background-color: #eee;
|
|
||||||
cursor: default; }
|
|
||||||
.select2-container--default.select2-container--disabled .select2-selection--single .select2-selection__clear {
|
|
||||||
display: none; }
|
|
||||||
|
|
||||||
.select2-container--default.select2-container--open .select2-selection--single .select2-selection__arrow b {
|
|
||||||
border-color: transparent transparent #888 transparent;
|
|
||||||
border-width: 0 4px 5px 4px; }
|
|
||||||
|
|
||||||
.select2-container--default .select2-selection--multiple {
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: text; }
|
|
||||||
.select2-container--default .select2-selection--multiple .select2-selection__rendered {
|
|
||||||
box-sizing: border-box;
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 5px;
|
|
||||||
width: 100%; }
|
|
||||||
.select2-container--default .select2-selection--multiple .select2-selection__rendered li {
|
|
||||||
list-style: none; }
|
|
||||||
.select2-container--default .select2-selection--multiple .select2-selection__clear {
|
|
||||||
cursor: pointer;
|
|
||||||
float: right;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-top: 5px;
|
|
||||||
margin-right: 10px;
|
|
||||||
padding: 1px; }
|
|
||||||
.select2-container--default .select2-selection--multiple .select2-selection__choice {
|
|
||||||
background-color: #e4e4e4;
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: default;
|
|
||||||
float: left;
|
|
||||||
margin-right: 5px;
|
|
||||||
margin-top: 5px;
|
|
||||||
padding: 0 5px; }
|
|
||||||
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove {
|
|
||||||
color: #999;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-right: 2px; }
|
|
||||||
.select2-container--default .select2-selection--multiple .select2-selection__choice__remove:hover {
|
|
||||||
color: #333; }
|
|
||||||
|
|
||||||
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice, .select2-container--default[dir="rtl"] .select2-selection--multiple .select2-search--inline {
|
|
||||||
float: right; }
|
|
||||||
|
|
||||||
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-right: auto; }
|
|
||||||
|
|
||||||
.select2-container--default[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
|
|
||||||
margin-left: 2px;
|
|
||||||
margin-right: auto; }
|
|
||||||
|
|
||||||
.select2-container--default.select2-container--focus .select2-selection--multiple {
|
|
||||||
border: solid black 1px;
|
|
||||||
outline: 0; }
|
|
||||||
|
|
||||||
.select2-container--default.select2-container--disabled .select2-selection--multiple {
|
|
||||||
background-color: #eee;
|
|
||||||
cursor: default; }
|
|
||||||
|
|
||||||
.select2-container--default.select2-container--disabled .select2-selection__choice__remove {
|
|
||||||
display: none; }
|
|
||||||
|
|
||||||
.select2-container--default.select2-container--open.select2-container--above .select2-selection--single, .select2-container--default.select2-container--open.select2-container--above .select2-selection--multiple {
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-top-right-radius: 0; }
|
|
||||||
|
|
||||||
.select2-container--default.select2-container--open.select2-container--below .select2-selection--single, .select2-container--default.select2-container--open.select2-container--below .select2-selection--multiple {
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-bottom-right-radius: 0; }
|
|
||||||
|
|
||||||
.select2-container--default .select2-search--dropdown .select2-search__field {
|
|
||||||
border: 1px solid #aaa; }
|
|
||||||
|
|
||||||
.select2-container--default .select2-search--inline .select2-search__field {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
outline: 0;
|
|
||||||
box-shadow: none;
|
|
||||||
-webkit-appearance: textfield; }
|
|
||||||
|
|
||||||
.select2-container--default .select2-results > .select2-results__options {
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto; }
|
|
||||||
|
|
||||||
.select2-container--default .select2-results__option[role=group] {
|
|
||||||
padding: 0; }
|
|
||||||
|
|
||||||
.select2-container--default .select2-results__option[aria-disabled=true] {
|
|
||||||
color: #999; }
|
|
||||||
|
|
||||||
.select2-container--default .select2-results__option[aria-selected=true] {
|
|
||||||
background-color: #ddd; }
|
|
||||||
|
|
||||||
.select2-container--default .select2-results__option .select2-results__option {
|
|
||||||
padding-left: 1em; }
|
|
||||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__group {
|
|
||||||
padding-left: 0; }
|
|
||||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option {
|
|
||||||
margin-left: -1em;
|
|
||||||
padding-left: 2em; }
|
|
||||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
|
||||||
margin-left: -2em;
|
|
||||||
padding-left: 3em; }
|
|
||||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
|
||||||
margin-left: -3em;
|
|
||||||
padding-left: 4em; }
|
|
||||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
|
||||||
margin-left: -4em;
|
|
||||||
padding-left: 5em; }
|
|
||||||
.select2-container--default .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option .select2-results__option {
|
|
||||||
margin-left: -5em;
|
|
||||||
padding-left: 6em; }
|
|
||||||
|
|
||||||
.select2-container--default .select2-results__option--highlighted[aria-selected] {
|
|
||||||
background-color: #5897fb;
|
|
||||||
color: white; }
|
|
||||||
|
|
||||||
.select2-container--default .select2-results__group {
|
|
||||||
cursor: default;
|
|
||||||
display: block;
|
|
||||||
padding: 6px; }
|
|
||||||
|
|
||||||
.select2-container--classic .select2-selection--single {
|
|
||||||
background-color: #f7f7f7;
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
border-radius: 4px;
|
|
||||||
outline: 0;
|
|
||||||
background-image: -webkit-linear-gradient(top, white 50%, #eeeeee 100%);
|
|
||||||
background-image: -o-linear-gradient(top, white 50%, #eeeeee 100%);
|
|
||||||
background-image: linear-gradient(to bottom, white 50%, #eeeeee 100%);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
|
|
||||||
.select2-container--classic .select2-selection--single:focus {
|
|
||||||
border: 1px solid #5897fb; }
|
|
||||||
.select2-container--classic .select2-selection--single .select2-selection__rendered {
|
|
||||||
color: #444;
|
|
||||||
line-height: 28px; }
|
|
||||||
.select2-container--classic .select2-selection--single .select2-selection__clear {
|
|
||||||
cursor: pointer;
|
|
||||||
float: right;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-right: 10px; }
|
|
||||||
.select2-container--classic .select2-selection--single .select2-selection__placeholder {
|
|
||||||
color: #999; }
|
|
||||||
.select2-container--classic .select2-selection--single .select2-selection__arrow {
|
|
||||||
background-color: #ddd;
|
|
||||||
border: none;
|
|
||||||
border-left: 1px solid #aaa;
|
|
||||||
border-top-right-radius: 4px;
|
|
||||||
border-bottom-right-radius: 4px;
|
|
||||||
height: 26px;
|
|
||||||
position: absolute;
|
|
||||||
top: 1px;
|
|
||||||
right: 1px;
|
|
||||||
width: 20px;
|
|
||||||
background-image: -webkit-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
|
|
||||||
background-image: -o-linear-gradient(top, #eeeeee 50%, #cccccc 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #eeeeee 50%, #cccccc 100%);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFCCCCCC', GradientType=0); }
|
|
||||||
.select2-container--classic .select2-selection--single .select2-selection__arrow b {
|
|
||||||
border-color: #888 transparent transparent transparent;
|
|
||||||
border-style: solid;
|
|
||||||
border-width: 5px 4px 0 4px;
|
|
||||||
height: 0;
|
|
||||||
left: 50%;
|
|
||||||
margin-left: -4px;
|
|
||||||
margin-top: -2px;
|
|
||||||
position: absolute;
|
|
||||||
top: 50%;
|
|
||||||
width: 0; }
|
|
||||||
|
|
||||||
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__clear {
|
|
||||||
float: left; }
|
|
||||||
|
|
||||||
.select2-container--classic[dir="rtl"] .select2-selection--single .select2-selection__arrow {
|
|
||||||
border: none;
|
|
||||||
border-right: 1px solid #aaa;
|
|
||||||
border-radius: 0;
|
|
||||||
border-top-left-radius: 4px;
|
|
||||||
border-bottom-left-radius: 4px;
|
|
||||||
left: 1px;
|
|
||||||
right: auto; }
|
|
||||||
|
|
||||||
.select2-container--classic.select2-container--open .select2-selection--single {
|
|
||||||
border: 1px solid #5897fb; }
|
|
||||||
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow {
|
|
||||||
background: transparent;
|
|
||||||
border: none; }
|
|
||||||
.select2-container--classic.select2-container--open .select2-selection--single .select2-selection__arrow b {
|
|
||||||
border-color: transparent transparent #888 transparent;
|
|
||||||
border-width: 0 4px 5px 4px; }
|
|
||||||
|
|
||||||
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--single {
|
|
||||||
border-top: none;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-top-right-radius: 0;
|
|
||||||
background-image: -webkit-linear-gradient(top, white 0%, #eeeeee 50%);
|
|
||||||
background-image: -o-linear-gradient(top, white 0%, #eeeeee 50%);
|
|
||||||
background-image: linear-gradient(to bottom, white 0%, #eeeeee 50%);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFFFFFFF', endColorstr='#FFEEEEEE', GradientType=0); }
|
|
||||||
|
|
||||||
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--single {
|
|
||||||
border-bottom: none;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-bottom-right-radius: 0;
|
|
||||||
background-image: -webkit-linear-gradient(top, #eeeeee 50%, white 100%);
|
|
||||||
background-image: -o-linear-gradient(top, #eeeeee 50%, white 100%);
|
|
||||||
background-image: linear-gradient(to bottom, #eeeeee 50%, white 100%);
|
|
||||||
background-repeat: repeat-x;
|
|
||||||
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#FFEEEEEE', endColorstr='#FFFFFFFF', GradientType=0); }
|
|
||||||
|
|
||||||
.select2-container--classic .select2-selection--multiple {
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: text;
|
|
||||||
outline: 0; }
|
|
||||||
.select2-container--classic .select2-selection--multiple:focus {
|
|
||||||
border: 1px solid #5897fb; }
|
|
||||||
.select2-container--classic .select2-selection--multiple .select2-selection__rendered {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 5px; }
|
|
||||||
.select2-container--classic .select2-selection--multiple .select2-selection__clear {
|
|
||||||
display: none; }
|
|
||||||
.select2-container--classic .select2-selection--multiple .select2-selection__choice {
|
|
||||||
background-color: #e4e4e4;
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
border-radius: 4px;
|
|
||||||
cursor: default;
|
|
||||||
float: left;
|
|
||||||
margin-right: 5px;
|
|
||||||
margin-top: 5px;
|
|
||||||
padding: 0 5px; }
|
|
||||||
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove {
|
|
||||||
color: #888;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-right: 2px; }
|
|
||||||
.select2-container--classic .select2-selection--multiple .select2-selection__choice__remove:hover {
|
|
||||||
color: #555; }
|
|
||||||
|
|
||||||
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice {
|
|
||||||
float: right;
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-right: auto; }
|
|
||||||
|
|
||||||
.select2-container--classic[dir="rtl"] .select2-selection--multiple .select2-selection__choice__remove {
|
|
||||||
margin-left: 2px;
|
|
||||||
margin-right: auto; }
|
|
||||||
|
|
||||||
.select2-container--classic.select2-container--open .select2-selection--multiple {
|
|
||||||
border: 1px solid #5897fb; }
|
|
||||||
|
|
||||||
.select2-container--classic.select2-container--open.select2-container--above .select2-selection--multiple {
|
|
||||||
border-top: none;
|
|
||||||
border-top-left-radius: 0;
|
|
||||||
border-top-right-radius: 0; }
|
|
||||||
|
|
||||||
.select2-container--classic.select2-container--open.select2-container--below .select2-selection--multiple {
|
|
||||||
border-bottom: none;
|
|
||||||
border-bottom-left-radius: 0;
|
|
||||||
border-bottom-right-radius: 0; }
|
|
||||||
|
|
||||||
.select2-container--classic .select2-search--dropdown .select2-search__field {
|
|
||||||
border: 1px solid #aaa;
|
|
||||||
outline: 0; }
|
|
||||||
|
|
||||||
.select2-container--classic .select2-search--inline .select2-search__field {
|
|
||||||
outline: 0;
|
|
||||||
box-shadow: none; }
|
|
||||||
|
|
||||||
.select2-container--classic .select2-dropdown {
|
|
||||||
background-color: white;
|
|
||||||
border: 1px solid transparent; }
|
|
||||||
|
|
||||||
.select2-container--classic .select2-dropdown--above {
|
|
||||||
border-bottom: none; }
|
|
||||||
|
|
||||||
.select2-container--classic .select2-dropdown--below {
|
|
||||||
border-top: none; }
|
|
||||||
|
|
||||||
.select2-container--classic .select2-results > .select2-results__options {
|
|
||||||
max-height: 200px;
|
|
||||||
overflow-y: auto; }
|
|
||||||
|
|
||||||
.select2-container--classic .select2-results__option[role=group] {
|
|
||||||
padding: 0; }
|
|
||||||
|
|
||||||
.select2-container--classic .select2-results__option[aria-disabled=true] {
|
|
||||||
color: grey; }
|
|
||||||
|
|
||||||
.select2-container--classic .select2-results__option--highlighted[aria-selected] {
|
|
||||||
background-color: #3875d7;
|
|
||||||
color: white; }
|
|
||||||
|
|
||||||
.select2-container--classic .select2-results__group {
|
|
||||||
cursor: default;
|
|
||||||
display: block;
|
|
||||||
padding: 6px; }
|
|
||||||
|
|
||||||
.select2-container--classic.select2-container--open .select2-dropdown {
|
|
||||||
border-color: #5897fb; }
|
|
||||||
@ -1,603 +0,0 @@
|
|||||||
/* SELECTOR (FILTER INTERFACE) */
|
|
||||||
|
|
||||||
.selector {
|
|
||||||
width: 800px;
|
|
||||||
float: left;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector select {
|
|
||||||
width: 380px;
|
|
||||||
height: 17.2em;
|
|
||||||
flex: 1 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-available, .selector-chosen {
|
|
||||||
width: 380px;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-available h2, .selector-chosen h2 {
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-chosen .list-footer-display {
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-top: none;
|
|
||||||
border-radius: 0 0 4px 4px;
|
|
||||||
margin: 0 0 10px;
|
|
||||||
padding: 8px;
|
|
||||||
text-align: center;
|
|
||||||
background: var(--primary);
|
|
||||||
color: var(--header-link-color);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.selector-chosen .list-footer-display__clear {
|
|
||||||
color: var(--breadcrumbs-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-chosen h2 {
|
|
||||||
background: var(--primary);
|
|
||||||
color: var(--header-link-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector .selector-available h2 {
|
|
||||||
background: var(--darkened-bg);
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector .selector-filter {
|
|
||||||
border: 1px solid var(--border-color);
|
|
||||||
border-width: 0 1px;
|
|
||||||
padding: 8px;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
font-size: 0.625rem;
|
|
||||||
margin: 0;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector .selector-filter label,
|
|
||||||
.inline-group .aligned .selector .selector-filter label {
|
|
||||||
float: left;
|
|
||||||
margin: 7px 0 0;
|
|
||||||
width: 18px;
|
|
||||||
height: 18px;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector .selector-available input,
|
|
||||||
.selector .selector-chosen input {
|
|
||||||
width: 320px;
|
|
||||||
margin-left: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector ul.selector-chooser {
|
|
||||||
align-self: center;
|
|
||||||
width: 22px;
|
|
||||||
background-color: var(--selected-bg);
|
|
||||||
border-radius: 10px;
|
|
||||||
margin: 0 5px;
|
|
||||||
padding: 0;
|
|
||||||
transform: translateY(-17px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-chooser li {
|
|
||||||
margin: 0;
|
|
||||||
padding: 3px;
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector select {
|
|
||||||
padding: 0 10px;
|
|
||||||
margin: 0 0 10px;
|
|
||||||
border-radius: 0 0 4px 4px;
|
|
||||||
}
|
|
||||||
.selector .selector-chosen--with-filtered select {
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
height: 14em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector .selector-chosen:not(.selector-chosen--with-filtered) .list-footer-display {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-add, .selector-remove {
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
display: block;
|
|
||||||
text-indent: -3000px;
|
|
||||||
overflow: hidden;
|
|
||||||
cursor: default;
|
|
||||||
opacity: 0.55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active.selector-add, .active.selector-remove {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active.selector-add:hover, .active.selector-remove:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-add {
|
|
||||||
background: url(../img/selector-icons.svg) 0 -96px no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active.selector-add:focus, .active.selector-add:hover {
|
|
||||||
background-position: 0 -112px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector-remove {
|
|
||||||
background: url(../img/selector-icons.svg) 0 -64px no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active.selector-remove:focus, .active.selector-remove:hover {
|
|
||||||
background-position: 0 -80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.selector-chooseall, a.selector-clearall {
|
|
||||||
display: inline-block;
|
|
||||||
height: 16px;
|
|
||||||
text-align: left;
|
|
||||||
margin: 1px auto 3px;
|
|
||||||
overflow: hidden;
|
|
||||||
font-weight: bold;
|
|
||||||
line-height: 16px;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
text-decoration: none;
|
|
||||||
opacity: 0.55;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.active.selector-chooseall:focus, a.active.selector-clearall:focus,
|
|
||||||
a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
|
|
||||||
color: var(--link-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
a.active.selector-chooseall, a.active.selector-clearall {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.active.selector-chooseall:hover, a.active.selector-clearall:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.selector-chooseall {
|
|
||||||
padding: 0 18px 0 0;
|
|
||||||
background: url(../img/selector-icons.svg) right -160px no-repeat;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.active.selector-chooseall:focus, a.active.selector-chooseall:hover {
|
|
||||||
background-position: 100% -176px;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.selector-clearall {
|
|
||||||
padding: 0 0 0 18px;
|
|
||||||
background: url(../img/selector-icons.svg) 0 -128px no-repeat;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.active.selector-clearall:focus, a.active.selector-clearall:hover {
|
|
||||||
background-position: 0 -144px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* STACKED SELECTORS */
|
|
||||||
|
|
||||||
.stacked {
|
|
||||||
float: left;
|
|
||||||
width: 490px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked select {
|
|
||||||
width: 480px;
|
|
||||||
height: 10.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .selector-available, .stacked .selector-chosen {
|
|
||||||
width: 480px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .selector-available {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .selector-available input {
|
|
||||||
width: 422px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked ul.selector-chooser {
|
|
||||||
height: 22px;
|
|
||||||
width: 50px;
|
|
||||||
margin: 0 0 10px 40%;
|
|
||||||
background-color: #eee;
|
|
||||||
border-radius: 10px;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .selector-chooser li {
|
|
||||||
float: left;
|
|
||||||
padding: 3px 3px 3px 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .selector-chooseall, .stacked .selector-clearall {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .selector-add {
|
|
||||||
background: url(../img/selector-icons.svg) 0 -32px no-repeat;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .active.selector-add {
|
|
||||||
background-position: 0 -32px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .active.selector-add:focus, .stacked .active.selector-add:hover {
|
|
||||||
background-position: 0 -48px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .selector-remove {
|
|
||||||
background: url(../img/selector-icons.svg) 0 0 no-repeat;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .active.selector-remove {
|
|
||||||
background-position: 0 0px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stacked .active.selector-remove:focus, .stacked .active.selector-remove:hover {
|
|
||||||
background-position: 0 -16px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector .help-icon {
|
|
||||||
background: url(../img/icon-unknown.svg) 0 0 no-repeat;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
margin: -2px 0 0 2px;
|
|
||||||
width: 13px;
|
|
||||||
height: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector .selector-chosen .help-icon {
|
|
||||||
background: url(../img/icon-unknown-alt.svg) 0 0 no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.selector .search-label-icon {
|
|
||||||
background: url(../img/search.svg) 0 0 no-repeat;
|
|
||||||
display: inline-block;
|
|
||||||
height: 1.125rem;
|
|
||||||
width: 1.125rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* DATE AND TIME */
|
|
||||||
|
|
||||||
p.datetime {
|
|
||||||
line-height: 20px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetime span {
|
|
||||||
white-space: nowrap;
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetime input, .form-row .datetime input.vDateField, .form-row .datetime input.vTimeField {
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table p.datetime {
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
margin-left: 0;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetimeshortcuts .clock-icon, .datetimeshortcuts .date-icon {
|
|
||||||
position: relative;
|
|
||||||
display: inline-block;
|
|
||||||
vertical-align: middle;
|
|
||||||
height: 16px;
|
|
||||||
width: 16px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetimeshortcuts .clock-icon {
|
|
||||||
background: url(../img/icon-clock.svg) 0 0 no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetimeshortcuts a:focus .clock-icon,
|
|
||||||
.datetimeshortcuts a:hover .clock-icon {
|
|
||||||
background-position: 0 -16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetimeshortcuts .date-icon {
|
|
||||||
background: url(../img/icon-calendar.svg) 0 0 no-repeat;
|
|
||||||
top: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.datetimeshortcuts a:focus .date-icon,
|
|
||||||
.datetimeshortcuts a:hover .date-icon {
|
|
||||||
background-position: 0 -16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timezonewarning {
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* URL */
|
|
||||||
|
|
||||||
p.url {
|
|
||||||
line-height: 20px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.url a {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* FILE UPLOADS */
|
|
||||||
|
|
||||||
p.file-upload {
|
|
||||||
line-height: 20px;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-upload a {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
.file-upload .deletelink {
|
|
||||||
margin-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.clearable-file-input label {
|
|
||||||
color: var(--body-fg);
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
display: inline;
|
|
||||||
float: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* CALENDARS & CLOCKS */
|
|
||||||
|
|
||||||
.calendarbox, .clockbox {
|
|
||||||
margin: 5px auto;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
width: 19em;
|
|
||||||
text-align: center;
|
|
||||||
background: var(--body-bg);
|
|
||||||
color: var(--body-fg);
|
|
||||||
border: 1px solid var(--hairline-color);
|
|
||||||
border-radius: 4px;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
|
|
||||||
overflow: hidden;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clockbox {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar table {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
border-collapse: collapse;
|
|
||||||
background: white;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar caption, .calendarbox h2 {
|
|
||||||
margin: 0;
|
|
||||||
text-align: center;
|
|
||||||
border-top: none;
|
|
||||||
font-weight: 700;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: #333;
|
|
||||||
background: var(--accent);
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar th {
|
|
||||||
padding: 8px 5px;
|
|
||||||
background: var(--darkened-bg);
|
|
||||||
border-bottom: 1px solid var(--border-color);
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
text-align: center;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar td {
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
text-align: center;
|
|
||||||
padding: 0;
|
|
||||||
border-top: 1px solid var(--hairline-color);
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar td.selected a {
|
|
||||||
background: var(--primary);
|
|
||||||
color: var(--button-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar td.nonday {
|
|
||||||
background: var(--darkened-bg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar td.today a {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar td a, .timelist a {
|
|
||||||
display: block;
|
|
||||||
font-weight: 400;
|
|
||||||
padding: 6px;
|
|
||||||
text-decoration: none;
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar td a:focus, .timelist a:focus,
|
|
||||||
.calendar td a:hover, .timelist a:hover {
|
|
||||||
background: var(--primary);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar td a:active, .timelist a:active {
|
|
||||||
background: var(--header-bg);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarnav {
|
|
||||||
font-size: 0.625rem;
|
|
||||||
text-align: center;
|
|
||||||
color: #ccc;
|
|
||||||
margin: 0;
|
|
||||||
padding: 1px 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarnav a:link, #calendarnav a:visited,
|
|
||||||
#calendarnav a:focus, #calendarnav a:hover {
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-shortcuts {
|
|
||||||
background: var(--body-bg);
|
|
||||||
color: var(--body-quiet-color);
|
|
||||||
font-size: 0.6875rem;
|
|
||||||
line-height: 0.6875rem;
|
|
||||||
border-top: 1px solid var(--hairline-color);
|
|
||||||
padding: 8px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarbox .calendarnav-previous, .calendarbox .calendarnav-next {
|
|
||||||
display: block;
|
|
||||||
position: absolute;
|
|
||||||
top: 8px;
|
|
||||||
width: 15px;
|
|
||||||
height: 15px;
|
|
||||||
text-indent: -9999px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarnav-previous {
|
|
||||||
left: 10px;
|
|
||||||
background: url(../img/calendar-icons.svg) 0 0 no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarbox .calendarnav-previous:focus,
|
|
||||||
.calendarbox .calendarnav-previous:hover {
|
|
||||||
background-position: 0 -15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarnav-next {
|
|
||||||
right: 10px;
|
|
||||||
background: url(../img/calendar-icons.svg) 0 -30px no-repeat;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendarbox .calendarnav-next:focus,
|
|
||||||
.calendarbox .calendarnav-next:hover {
|
|
||||||
background-position: 0 -45px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-cancel {
|
|
||||||
margin: 0;
|
|
||||||
padding: 4px 0;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
background: #eee;
|
|
||||||
border-top: 1px solid var(--border-color);
|
|
||||||
color: var(--body-fg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-cancel:focus, .calendar-cancel:hover {
|
|
||||||
background: #ddd;
|
|
||||||
}
|
|
||||||
|
|
||||||
.calendar-cancel a {
|
|
||||||
color: black;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.timelist, .timelist li {
|
|
||||||
list-style-type: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.timelist a {
|
|
||||||
padding: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* EDIT INLINE */
|
|
||||||
|
|
||||||
.inline-deletelink {
|
|
||||||
float: right;
|
|
||||||
text-indent: -9999px;
|
|
||||||
background: url(../img/inline-delete.svg) 0 0 no-repeat;
|
|
||||||
width: 16px;
|
|
||||||
height: 16px;
|
|
||||||
border: 0px none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-deletelink:focus, .inline-deletelink:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* RELATED WIDGET WRAPPER */
|
|
||||||
.related-widget-wrapper {
|
|
||||||
float: left; /* display properly in form rows with multiple fields */
|
|
||||||
overflow: hidden; /* clear floated contents */
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-widget-wrapper-link {
|
|
||||||
opacity: 0.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-widget-wrapper-link:link {
|
|
||||||
opacity: .8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related-widget-wrapper-link:link:focus,
|
|
||||||
.related-widget-wrapper-link:link:hover {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
select + .related-widget-wrapper-link,
|
|
||||||
.related-widget-wrapper-link + .related-widget-wrapper-link {
|
|
||||||
margin-left: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* GIS MAPS */
|
|
||||||
.dj_map {
|
|
||||||
width: 600px;
|
|
||||||
height: 400px;
|
|
||||||
}
|
|
||||||
@ -1,20 +0,0 @@
|
|||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Code Charm Ltd
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
|
||||||
the Software without restriction, including without limitation the rights to
|
|
||||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
||||||
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
||||||
subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
||||||
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
||||||
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
||||||
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
||||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
All icons are taken from Font Awesome (http://fontawesome.io/) project.
|
|
||||||
The Font Awesome font is licensed under the SIL OFL 1.1:
|
|
||||||
- https://scripts.sil.org/OFL
|
|
||||||
|
|
||||||
SVG icons source: https://github.com/encharm/Font-Awesome-SVG-PNG
|
|
||||||
Font-Awesome-SVG-PNG is licensed under the MIT license (see file license
|
|
||||||
in current folder).
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
<svg width="15" height="60" viewBox="0 0 1792 7168" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<defs>
|
|
||||||
<g id="previous">
|
|
||||||
<path d="M1037 1395l102-102q19-19 19-45t-19-45l-307-307 307-307q19-19 19-45t-19-45l-102-102q-19-19-45-19t-45 19l-454 454q-19 19-19 45t19 45l454 454q19 19 45 19t45-19zm627-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
|
||||||
</g>
|
|
||||||
<g id="next">
|
|
||||||
<path d="M845 1395l454-454q19-19 19-45t-19-45l-454-454q-19-19-45-19t-45 19l-102 102q-19 19-19 45t19 45l307 307-307 307q-19 19-19 45t19 45l102 102q19 19 45 19t45-19zm819-499q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
|
||||||
</g>
|
|
||||||
</defs>
|
|
||||||
<use xlink:href="#previous" x="0" y="0" fill="#333333" />
|
|
||||||
<use xlink:href="#previous" x="0" y="1792" fill="#000000" />
|
|
||||||
<use xlink:href="#next" x="0" y="3584" fill="#333333" />
|
|
||||||
<use xlink:href="#next" x="0" y="5376" fill="#000000" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1 +0,0 @@
|
|||||||
<svg width="24" height="22" viewBox="0 0 847 779" xmlns="http://www.w3.org/2000/svg"><g><path fill="#EBECE6" d="M120 1h607c66 0 120 54 120 120v536c0 66-54 120-120 120h-607c-66 0-120-54-120-120v-536c0-66 54-120 120-120z"/><path fill="#9E9E93" d="M120 1h607c66 0 120 54 120 120v536c0 66-54 120-120 120h-607c-66 0-120-54-120-120v-536c0-66 54-120 120-120zm607 25h-607c-26 0-50 11-67 28-17 18-28 41-28 67v536c0 27 11 50 28 68 17 17 41 27 67 27h607c26 0 49-10 67-27 17-18 28-41 28-68v-536c0-26-11-49-28-67-18-17-41-28-67-28z"/><path stroke="#A9A8A4" stroke-width="20" d="M706 295l-68 281"/><path stroke="#E47474" stroke-width="20" d="M316 648l390-353M141 435l175 213"/><path stroke="#C9C9C9" stroke-width="20" d="M319 151l-178 284M706 295l-387-144"/><g fill="#040405"><path d="M319 111c22 0 40 18 40 40s-18 40-40 40-40-18-40-40 18-40 40-40zM141 395c22 0 40 18 40 40s-18 40-40 40c-23 0-41-18-41-40s18-40 41-40zM316 608c22 0 40 18 40 40 0 23-18 41-40 41s-40-18-40-41c0-22 18-40 40-40zM706 254c22 0 40 18 40 41 0 22-18 40-40 40s-40-18-40-40c0-23 18-41 40-41zM638 536c22 0 40 18 40 40s-18 40-40 40-40-18-40-40 18-40 40-40z"/></g></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1 +0,0 @@
|
|||||||
<svg width="24" height="22" viewBox="0 0 847 779" xmlns="http://www.w3.org/2000/svg"><g><path fill="#F1C02A" d="M120 1h607c66 0 120 54 120 120v536c0 66-54 120-120 120h-607c-66 0-120-54-120-120v-536c0-66 54-120 120-120z"/><path fill="#9E9E93" d="M120 1h607c66 0 120 54 120 120v536c0 66-54 120-120 120h-607c-66 0-120-54-120-120v-536c0-66 54-120 120-120zm607 25h-607c-26 0-50 11-67 28-17 18-28 41-28 67v536c0 27 11 50 28 68 17 17 41 27 67 27h607c26 0 49-10 67-27 17-18 28-41 28-68v-536c0-26-11-49-28-67-18-17-41-28-67-28z"/><path stroke="#A9A8A4" stroke-width="20" d="M706 295l-68 281"/><path stroke="#E47474" stroke-width="20" d="M316 648l390-353M141 435l175 213"/><path stroke="#C9A741" stroke-width="20" d="M319 151l-178 284M706 295l-387-144"/><g fill="#040405"><path d="M319 111c22 0 40 18 40 40s-18 40-40 40-40-18-40-40 18-40 40-40zM141 395c22 0 40 18 40 40s-18 40-40 40c-23 0-41-18-41-40s18-40 41-40zM316 608c22 0 40 18 40 40 0 23-18 41-40 41s-40-18-40-41c0-22 18-40 40-40zM706 254c22 0 40 18 40 41 0 22-18 40-40 40s-40-18-40-40c0-23 18-41 40-41zM638 536c22 0 40 18 40 40s-18 40-40 40-40-18-40-40 18-40 40-40z"/></g></g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,3 +0,0 @@
|
|||||||
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="#70bf2b" d="M1600 796v192q0 40-28 68t-68 28h-416v416q0 40-28 68t-68 28h-192q-40 0-68-28t-28-68v-416h-416q-40 0-68-28t-28-68v-192q0-40 28-68t68-28h416v-416q0-40 28-68t68-28h192q40 0 68 28t28 68v416h416q40 0 68 28t28 68z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 331 B |
@ -1,3 +0,0 @@
|
|||||||
<svg width="14" height="14" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="#efb80b" d="M1024 1375v-190q0-14-9.5-23.5t-22.5-9.5h-192q-13 0-22.5 9.5t-9.5 23.5v190q0 14 9.5 23.5t22.5 9.5h192q13 0 22.5-9.5t9.5-23.5zm-2-374l18-459q0-12-10-19-13-11-24-11h-220q-11 0-24 11-10 7-10 21l17 457q0 10 10 16.5t24 6.5h185q14 0 23.5-6.5t10.5-16.5zm-14-934l768 1408q35 63-2 126-17 29-46.5 46t-63.5 17h-1536q-34 0-63.5-17t-46.5-46q-37-63-2-126l768-1408q17-31 47-49t65-18 65 18 47 49z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 504 B |
@ -1,9 +0,0 @@
|
|||||||
<svg width="16" height="32" viewBox="0 0 1792 3584" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<defs>
|
|
||||||
<g id="icon">
|
|
||||||
<path d="M192 1664h288v-288h-288v288zm352 0h320v-288h-320v288zm-352-352h288v-320h-288v320zm352 0h320v-320h-320v320zm-352-384h288v-288h-288v288zm736 736h320v-288h-320v288zm-384-736h320v-288h-320v288zm768 736h288v-288h-288v288zm-384-352h320v-320h-320v320zm-352-864v-288q0-13-9.5-22.5t-22.5-9.5h-64q-13 0-22.5 9.5t-9.5 22.5v288q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5-9.5t9.5-22.5zm736 864h288v-320h-288v320zm-384-384h320v-288h-320v288zm384 0h288v-288h-288v288zm32-480v-288q0-13-9.5-22.5t-22.5-9.5h-64q-13 0-22.5 9.5t-9.5 22.5v288q0 13 9.5 22.5t22.5 9.5h64q13 0 22.5-9.5t9.5-22.5zm384-64v1280q0 52-38 90t-90 38h-1408q-52 0-90-38t-38-90v-1280q0-52 38-90t90-38h128v-96q0-66 47-113t113-47h64q66 0 113 47t47 113v96h384v-96q0-66 47-113t113-47h64q66 0 113 47t47 113v96h128q52 0 90 38t38 90z"/>
|
|
||||||
</g>
|
|
||||||
</defs>
|
|
||||||
<use xlink:href="#icon" x="0" y="0" fill="#447e9b" />
|
|
||||||
<use xlink:href="#icon" x="0" y="1792" fill="#003366" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
@ -1,3 +0,0 @@
|
|||||||
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="#efb80b" d="M491 1536l91-91-235-235-91 91v107h128v128h107zm523-928q0-22-22-22-10 0-17 7l-542 542q-7 7-7 17 0 22 22 22 10 0 17-7l542-542q7-7 7-17zm-54-192l416 416-832 832h-416v-416zm683 96q0 53-37 90l-166 166-416-416 166-165q36-38 90-38 53 0 91 38l235 234q37 39 37 91z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 380 B |
@ -1,9 +0,0 @@
|
|||||||
<svg width="16" height="32" viewBox="0 0 1792 3584" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
||||||
<defs>
|
|
||||||
<g id="icon">
|
|
||||||
<path d="M1024 544v448q0 14-9 23t-23 9h-320q-14 0-23-9t-9-23v-64q0-14 9-23t23-9h224v-352q0-14 9-23t23-9h64q14 0 23 9t9 23zm416 352q0-148-73-273t-198-198-273-73-273 73-198 198-73 273 73 273 198 198 273 73 273-73 198-198 73-273zm224 0q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
|
||||||
</g>
|
|
||||||
</defs>
|
|
||||||
<use xlink:href="#icon" x="0" y="0" fill="#447e9b" />
|
|
||||||
<use xlink:href="#icon" x="0" y="1792" fill="#003366" />
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 677 B |
@ -1,3 +0,0 @@
|
|||||||
<svg width="14" height="14" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="#dd4646" d="M1490 1322q0 40-28 68l-136 136q-28 28-68 28t-68-28l-294-294-294 294q-28 28-68 28t-68-28l-136-136q-28-28-28-68t28-68l294-294-294-294q-28-28-28-68t28-68l136-136q28-28 68-28t68 28l294 294 294-294q28-28 68-28t68 28l136 136q28 28 28 68t-28 68l-294 294 294 294q28 28 28 68z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 392 B |
@ -1,3 +0,0 @@
|
|||||||
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="#dd4646" d="M1277 1122q0-26-19-45l-181-181 181-181q19-19 19-45 0-27-19-46l-90-90q-19-19-46-19-26 0-45 19l-181 181-181-181q-19-19-45-19-27 0-46 19l-90 90q-19 19-19 46 0 26 19 45l181 181-181 181q-19 19-19 45 0 27 19 46l90 90q19 19 46 19 26 0 45-19l181-181 181 181q19 19 45 19 27 0 46-19l90-90q19-19 19-46zm387-226q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 560 B |
@ -1,3 +0,0 @@
|
|||||||
<svg width="13" height="13" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="#ffffff" d="M1024 1376v-192q0-14-9-23t-23-9h-192q-14 0-23 9t-9 23v192q0 14 9 23t23 9h192q14 0 23-9t9-23zm256-672q0-88-55.5-163t-138.5-116-170-41q-243 0-371 213-15 24 8 42l132 100q7 6 19 6 16 0 25-12 53-68 86-92 34-24 86-24 48 0 85.5 26t37.5 59q0 38-20 61t-68 45q-63 28-115.5 86.5t-52.5 125.5v36q0 14 9 23t23 9h192q14 0 23-9t9-23q0-19 21.5-49.5t54.5-49.5q32-18 49-28.5t46-35 44.5-48 28-60.5 12.5-81zm384 192q0 209-103 385.5t-279.5 279.5-385.5 103-385.5-103-279.5-279.5-103-385.5 103-385.5 279.5-279.5 385.5-103 385.5 103 279.5 279.5 103 385.5z"/>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 655 B |