diff --git a/web/static/style.css b/web/static/style.css index 0b6d845..7f7be2a 100644 --- a/web/static/style.css +++ b/web/static/style.css @@ -1,444 +1,415 @@ -/* Brand colors */ -:root { - --pico-primary: #F05023; - --pico-primary-hover: #d44420; - --pico-primary-focus: rgba(240, 80, 35, 0.25); - --pico-primary-inverse: #fff; - --brand-primary: #F05023; - --brand-secondary: #0986E2; - --brand-secondary-hover: #0770c0; -} +/* ─── Reset ──────────────────────────────────────────────────────────── */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } +html, body { height: 100%; } +body { font-family: 'Golos Text', sans-serif; background: #F4F5F7; color: #1C1F2E; font-size: 14px; } +a { text-decoration: none; color: inherit; } +::-webkit-scrollbar { width: 5px; height: 5px; } +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb { background: #E4E6EE; border-radius: 3px; } -/* Header / nav */ -.site-header { - background: #fff; - border-bottom: 2px solid var(--brand-primary); - padding: 0; - margin-bottom: 0; -} - -.site-header nav { - display: flex; - align-items: center; - justify-content: space-between; - padding-top: 0.75rem; - padding-bottom: 0.75rem; - gap: 1rem; -} - -.site-header nav > ul { - margin: 0; - padding: 0; - list-style: none; - display: flex; - align-items: center; - gap: 0.25rem; -} - -.brand-logo { - font-size: 1.3rem; - font-weight: 700; - color: var(--brand-primary) !important; - text-decoration: none; -} - -.nav-links { - flex: 1; - justify-content: flex-end; -} - -.nav-links a { - color: var(--pico-color); - text-decoration: none; - padding: 0.25rem 0.5rem; - border-radius: 4px; -} - -.nav-links a:hover { - color: var(--brand-primary); -} - -.nav-links a.secondary { - color: var(--pico-muted-color); -} - -.mobile-menu { - display: none; -} - -.mobile-menu summary { - padding: 0.25rem 0.5rem; - font-size: 1.25rem; -} - -.mobile-menu > ul { - position: absolute; - right: 1rem; - background: var(--pico-background-color); - border: 1px solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - padding: 0.5rem 0; - list-style: none; - margin: 0; - z-index: 100; - min-width: 180px; - box-shadow: 0 4px 12px rgba(0,0,0,0.1); -} - -.mobile-menu > ul li a { - display: block; - padding: 0.5rem 1rem; - text-decoration: none; - color: var(--pico-color); -} - -.mobile-menu > ul li a:hover { - background: var(--pico-muted-background-color); -} - -@media (max-width: 768px) { - .nav-links { display: none; } - .mobile-menu { display: block; } -} - -/* Page spacing */ -.py-4 { - padding-top: 1.5rem; - padding-bottom: 1.5rem; -} - -/* Alerts */ -.alert { - border-radius: var(--pico-border-radius); - padding: 0.75rem 1rem; - margin-bottom: 1rem; -} - -.alert p { margin: 0; } -.alert p + p { margin-top: 0.25rem; } - -.alert-danger { - background: #fef2f2; - border: 1px solid #fecaca; - color: #b91c1c; -} - -.alert-success { - background: #f0fdf4; - border: 1px solid #bbf7d0; - color: #15803d; -} - -.alert-warning { - background: #fffbeb; - border: 1px solid #fde68a; - color: #b45309; -} - -/* Cards (using
) */ -article.card { - margin: 0; - padding: 0; - overflow: hidden; -} - -article.card > header { - padding: 0.75rem 1rem; - background: var(--pico-muted-background-color); - border-bottom: 1px solid var(--pico-border-color); - margin: 0; -} - -article.card > header h1, -article.card > header h2, -article.card > header h5 { - margin: 0; - font-size: 1rem; - font-weight: 600; -} - -article.card > .card-body { - padding: 1.25rem; -} - -article.card > footer { - padding: 0.75rem 1rem; - background: var(--pico-muted-background-color); - border-top: 1px solid var(--pico-border-color); - margin: 0; -} - -/* List groups */ -.list-group { - list-style: none; - padding: 0; - margin: 0; -} - -.list-group-item { - padding: 0.6rem 1rem; - border-bottom: 1px solid var(--pico-border-color); - display: flex; - justify-content: space-between; - align-items: center; -} - -.list-group-item:last-child { border-bottom: none; } - -/* Badges */ -.badge { - display: inline-flex; - align-items: center; - gap: 0.25rem; - padding: 0.2rem 0.5rem; - border-radius: 999px; - font-size: 0.75rem; - font-weight: 600; - line-height: 1; -} - -.badge-success { background: #dcfce7; color: #15803d; } -.badge-danger { background: #fee2e2; color: #b91c1c; } -.badge-warning { background: #fef3c7; color: #b45309; } -.badge-secondary { background: var(--pico-muted-background-color); color: var(--pico-muted-color); } -.badge-light { background: var(--pico-muted-background-color); color: var(--pico-muted-color); border: 1px solid var(--pico-border-color); } - -/* Buttons */ -button.secondary, a[role="button"].secondary { - --pico-background-color: var(--brand-secondary); - --pico-border-color: var(--brand-secondary); - --pico-color: #fff; -} - -button.outline.danger, a[role="button"].outline.danger { - --pico-color: #dc2626; - --pico-border-color: #dc2626; -} - -button.danger, a[role="button"].danger { - --pico-background-color: #dc2626; - --pico-border-color: #dc2626; - --pico-color: #fff; -} - -button.sm, a[role="button"].sm { - padding: 0.25rem 0.6rem; - font-size: 0.875rem; -} - -/* Layout helpers */ -.row { - display: flex; - flex-wrap: wrap; - gap: 1rem; -} - -.col { flex: 1 1 0; } -.col-auto { flex: 0 0 auto; } - -.col-sm-6 { flex: 0 0 calc(50% - 0.5rem); min-width: 200px; } -.col-sm-10 { flex: 0 0 calc(83.33% - 0.5rem); } -.col-md-6 { flex: 0 0 calc(50% - 0.5rem); } -.col-md-7 { flex: 0 0 calc(58.33% - 0.5rem); } -.col-lg-4 { flex: 0 0 calc(33.33% - 0.67rem); } -.col-lg-5 { flex: 0 0 calc(41.67% - 0.5rem); } -.col-lg-6 { flex: 0 0 calc(50% - 0.5rem); } -.col-12 { flex: 0 0 100%; } -.col-md-6-auto { flex: 0 0 calc(50% - 0.5rem); } - -@media (max-width: 768px) { - .col-sm-6, .col-md-6, .col-md-7, .col-lg-4, .col-lg-5, .col-lg-6, .col-md-6-auto { flex: 0 0 100%; } -} - -.justify-center { justify-content: center; } -.justify-between { justify-content: space-between; } -.justify-end { justify-content: flex-end; } -.align-center { align-items: center; } -.flex-wrap { flex-wrap: wrap; } -.flex-col { flex-direction: column; } -.flex-1 { flex: 1; } -.flex-fill { flex: 1 1 0; } - -.gap-1 { gap: 0.25rem; } -.gap-2 { gap: 0.5rem; } -.gap-3 { gap: 0.75rem; } - -.mt-2 { margin-top: 0.5rem; } -.mt-3 { margin-top: 0.75rem; } -.mt-4 { margin-top: 1.5rem; } -.mt-5 { margin-top: 3rem; } -.mb-0 { margin-bottom: 0; } -.mb-1 { margin-bottom: 0.25rem; } -.mb-2 { margin-bottom: 0.5rem; } -.mb-3 { margin-bottom: 0.75rem; } -.mb-4 { margin-bottom: 1.5rem; } -.ms-auto { margin-left: auto; } -.me-1 { margin-right: 0.25rem; } -.me-2 { margin-right: 0.5rem; } -.me-3 { margin-right: 0.75rem; } - -.d-flex { display: flex; } -.d-grid { display: grid; } -.d-none { display: none; } -.d-block { display: block; } - -.text-center { text-align: center; } -.text-end { text-align: right; } -.text-muted { color: var(--pico-muted-color); } -.small { font-size: 0.875rem; } -.fs-1 { font-size: 2rem; } -.fs-2 { font-size: 1.5rem; } -.fs-5 { font-size: 1.15rem; } -.fs-6 { font-size: 0.875rem; } - -.text-success { color: #15803d; } -.text-danger { color: #dc2626; } -.text-warning { color: #b45309; } -.text-primary { color: var(--brand-primary); } -.text-secondary { color: var(--brand-secondary); } -.text-white { color: #fff; } - -.bg-danger-header { - background: #dc2626; - color: #fff; -} - -.font-monospace { font-family: monospace; } +/* ─── Utility ─────────────────────────────────────────────────────────── */ +.mono { font-family: 'JetBrains Mono', monospace; } +.hr { height: 1px; background: #E4E6EE; margin: 18px 0; } .w-100 { width: 100%; } -.h-100 { height: 100%; } +.text-center { text-align: center; } +.d-none { display: none; } -/* Table */ -.table-scroll { - overflow-x: auto; - max-width: 100%; +/* ─── Layout ──────────────────────────────────────────────────────────── */ +.shell { display: flex; min-height: 100vh; } +.sidebar { + width: 228px; min-height: 100vh; flex-shrink: 0; + background: #1C1F2E; + display: flex; flex-direction: column; + position: sticky; top: 0; height: 100vh; overflow-y: auto; } +.main { flex: 1; display: flex; flex-direction: column; min-width: 0; } -article.card { - overflow: hidden; - width: 100%; - max-width: 100%; - box-sizing: border-box; +/* ─── Sidebar ─────────────────────────────────────────────────────────── */ +.sb-logo { + padding: 22px 20px 18px; + border-bottom: 1px solid rgba(255,255,255,0.07); + display: flex; align-items: center; gap: 11px; } - -table.align-middle { - width: 100%; +.sb-logo-icon { + width: 36px; height: 36px; border-radius: 9px; + background: #FF5500; + display: flex; align-items: center; justify-content: center; + flex-shrink: 0; } - -table.align-middle td, -table.align-middle th { - vertical-align: middle; - white-space: nowrap; +.sb-logo-name { font-size: 15px; font-weight: 800; color: #fff; line-height: 1.1; letter-spacing: -0.02em; } +.sb-logo-sub { font-size: 10px; color: #5C6278; font-weight: 500; margin-top: 1px; letter-spacing: 0.03em; } +.sb-nav { flex: 1; padding: 10px 12px; display: flex; flex-direction: column; gap: 1px; } +.sb-section { + font-size: 9.5px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.1em; + color: #5C6278; padding: 14px 8px 5px; } - -/* Breadcrumb */ -.breadcrumb { - display: flex; - align-items: center; - gap: 0.25rem; - list-style: none; - padding: 0; - margin: 0 0 1rem; - font-size: 0.9rem; - color: var(--pico-muted-color); +.sb-item { + display: flex; align-items: center; gap: 9px; + padding: 9px 10px; border-radius: 8px; cursor: pointer; + font-size: 13.5px; font-weight: 500; color: #A8ADC3; + transition: all 0.13s; text-decoration: none; position: relative; } - -.breadcrumb-item + .breadcrumb-item::before { - content: "/"; - margin-right: 0.25rem; - color: var(--pico-muted-color); +.sb-item:hover { background: #252A3D; color: #fff; } +.sb-item.active { background: rgba(255,85,0,0.18); color: #fff; } +.sb-item.active .sb-icon { color: #FF5500; } +.sb-icon { font-size: 16px; width: 20px; text-align: center; flex-shrink: 0; } +.sb-badge { + margin-left: auto; font-size: 10px; font-weight: 700; + padding: 1px 6px; border-radius: 9px; + background: rgba(255,85,0,0.25); color: #FF5500; + font-family: 'JetBrains Mono', monospace; } - -.breadcrumb-item.active { color: var(--pico-color); } - -/* Dropdown */ -.dropdown { - position: relative; - display: inline-block; +.sb-badge.err { background: rgba(229,57,53,0.2); color: #E53935; } +.sb-user { + padding: 14px 16px; border-top: 1px solid rgba(255,255,255,0.07); + display: flex; align-items: center; gap: 10px; cursor: pointer; + text-decoration: none; } - -.dropdown-menu { - display: none; - position: absolute; - right: 0; - top: calc(100% + 4px); - background: var(--pico-background-color); - border: 1px solid var(--pico-border-color); - border-radius: var(--pico-border-radius); - box-shadow: 0 4px 12px rgba(0,0,0,0.12); - z-index: 200; - min-width: 220px; - padding: 0.25rem 0; - list-style: none; - margin: 0; +.sb-user:hover { background: #252A3D; } +.avatar { + width: 34px; height: 34px; border-radius: 50%; flex-shrink: 0; + display: flex; align-items: center; justify-content: center; + font-size: 12px; font-weight: 700; color: #fff; + background: linear-gradient(135deg, #FF5500 0%, #FF8C42 100%); + text-transform: uppercase; } - -.dropdown.open .dropdown-menu { display: block; } - -.dropdown-item { - display: block; - width: 100%; - padding: 0.45rem 1rem; - background: none; - border: none; - text-align: left; - cursor: pointer; - color: var(--pico-color); - font-size: 0.9rem; - text-decoration: none; +.avatar.admin { background: linear-gradient(135deg, #6B48FF 0%, #A78BFA 100%); } +.sb-user-name { font-size: 12.5px; font-weight: 600; color: #fff; } +.sb-user-role { font-size: 10.5px; color: #5C6278; margin-top: 1px; } +.role-chip { + display: inline-block; font-size: 9px; font-weight: 700; + text-transform: uppercase; letter-spacing: 0.05em; + padding: 1px 5px; border-radius: 4px; } +.role-chip.user { background: rgba(255,85,0,0.2); color: #FF5500; } +.role-chip.admin { background: rgba(107,72,255,0.2); color: #A78BFA; } -.dropdown-item:hover { - background: var(--pico-muted-background-color); +/* ─── Topbar ──────────────────────────────────────────────────────────── */ +.topbar { + height: 56px; border-bottom: 1px solid #E4E6EE; + display: flex; align-items: center; padding: 0 28px; gap: 14px; + background: #FFFFFF; position: sticky; top: 0; z-index: 10; + flex-shrink: 0; } - -.dropdown-item.muted { color: var(--pico-muted-color); } - -.dropdown-divider { - border: none; - border-top: 1px solid var(--pico-border-color); - margin: 0.25rem 0; +.topbar-title { font-size: 15px; font-weight: 700; flex: 1; color: #1C1F2E; } +.conn-pill { + display: flex; align-items: center; gap: 6px; padding: 5px 12px; + border-radius: 20px; border: 1px solid #E4E6EE; + font-size: 12px; font-weight: 500; color: #5C6278; background: #F4F5F7; } +.dot { width: 7px; height: 7px; border-radius: 50%; flex-shrink: 0; } +.dot.g { background: #17A865; box-shadow: 0 0 5px #17A865; } +.dot.r { background: #E53935; box-shadow: 0 0 5px #E53935; } +.dot.y { background: #F59E0B; box-shadow: 0 0 5px #F59E0B; } +.dot.d { background: #CDD0DC; } +.dot.pulse { animation: blink 2s ease-in-out infinite; } +@keyframes blink { 0%,100%{opacity:1} 50%{opacity:0.4} } -/* Spinner */ +/* ─── Content ─────────────────────────────────────────────────────────── */ +.content { flex: 1; padding: 28px 32px; overflow-y: auto; } +.pg-title { font-size: 22px; font-weight: 800; color: #1C1F2E; letter-spacing: -0.02em; } +.pg-sub { font-size: 13px; color: #5C6278; margin-top: 3px; margin-bottom: 22px; } + +/* ─── Cards ───────────────────────────────────────────────────────────── */ +.card { + background: #FFFFFF; border: 1px solid #E4E6EE; + border-radius: 12px; padding: 22px 24px; +} +.card-title { font-size: 14px; font-weight: 700; color: #1C1F2E; } +.card-sub { font-size: 12px; color: #5C6278; margin-top: 2px; } +.card-hd { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 16px; } + +/* ─── Stat cards ──────────────────────────────────────────────────────── */ +.stat-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 14px; margin-bottom: 20px; } +.stat-card { + background: #FFFFFF; border: 1px solid #E4E6EE; border-radius: 12px; + padding: 18px 20px; position: relative; overflow: hidden; +} +.stat-card::before { + content:''; position: absolute; top: 0; left: 0; right: 0; height: 3px; border-radius: 12px 12px 0 0; +} +.stat-card.or::before { background: #FF5500; } +.stat-card.gr::before { background: #17A865; } +.stat-card.yl::before { background: #F59E0B; } +.stat-card.rd::before { background: #E53935; } +.stat-card.bl::before { background: #3B6FFF; } +.stat-lbl { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.07em; color: #9EA8BE; } +.stat-val { font-size: 30px; font-weight: 800; color: #1C1F2E; margin: 5px 0 2px; font-family: 'JetBrains Mono', monospace; letter-spacing: -0.03em; } +.stat-delta { font-size: 12px; color: #5C6278; } +.stat-icon { position: absolute; right: 16px; top: 50%; transform: translateY(-50%); font-size: 28px; opacity: 0.1; } + +/* ─── Grids ───────────────────────────────────────────────────────────── */ +.g2 { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } +.g3 { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 14px; } +.g4 { display: grid; grid-template-columns: repeat(4,1fr); gap: 14px; } + +/* ─── Buttons ─────────────────────────────────────────────────────────── */ +.btn { + display: inline-flex; align-items: center; gap: 7px; + padding: 8px 18px; border-radius: 8px; font-size: 13px; font-weight: 600; + cursor: pointer; border: none; font-family: 'Golos Text', sans-serif; + transition: all 0.14s; white-space: nowrap; text-decoration: none; +} +.btn-primary { background: #FF5500; color: #fff; } +.btn-primary:hover { background: #E64D00; box-shadow: 0 3px 12px rgba(255,85,0,0.3); } +.btn-outline { background: transparent; border: 1.5px solid #E4E6EE; color: #5C6278; } +.btn-outline:hover { border-color: #CDD0DC; color: #1C1F2E; background: #F9FAFB; } +.btn-ghost { background: transparent; color: #5C6278; padding: 6px 10px; border: none; } +.btn-ghost:hover { color: #1C1F2E; background: #F4F5F7; } +.btn-danger { background: #FEF1F1; color: #E53935; border: 1.5px solid #F4AEAE; } +.btn-danger:hover { background: #fce4e4; } +.btn-sm { padding: 6px 13px; font-size: 12px; border-radius: 7px; } +.btn-xs { padding: 4px 9px; font-size: 11.5px; border-radius: 6px; } + +/* ─── Tags ────────────────────────────────────────────────────────────── */ +.tag { + display: inline-flex; align-items: center; gap: 5px; + padding: 3px 8px; border-radius: 5px; font-size: 11.5px; font-weight: 600; +} +.tag-gr { background: #EDFAF4; color: #17A865; border: 1px solid #A7E8CC; } +.tag-rd { background: #FEF1F1; color: #E53935; border: 1px solid #F4AEAE; } +.tag-yl { background: #FFFBEB; color: #F59E0B; border: 1px solid #FCD678; } +.tag-or { background: #FFF2EC; color: #FF5500; border: 1px solid #FFD9C7; } +.tag-dim { background: #F4F5F7; color: #5C6278; border: 1px solid #E4E6EE; } +.tag-bl { background: #EEF3FF; color: #3B6FFF; border: 1px solid #C7D7FF; } + +/* ─── Table ───────────────────────────────────────────────────────────── */ +.tbl { width: 100%; border-collapse: collapse; } +.tbl th { + text-align: left; font-size: 10.5px; font-weight: 700; + text-transform: uppercase; letter-spacing: 0.08em; color: #9EA8BE; + padding: 10px 16px; border-bottom: 1.5px solid #E4E6EE; + white-space: nowrap; +} +.tbl td { padding: 12px 16px; font-size: 13px; border-bottom: 1px solid #E4E6EE; vertical-align: middle; } +.tbl tr:last-child td { border-bottom: none; } +.tbl tbody tr:hover td { background: #F9FAFB; } +.tbl-name { font-weight: 600; color: #1C1F2E; } +.tbl-sub { font-size: 11px; color: #5C6278; margin-top: 1px; } + +/* ─── Toggle ──────────────────────────────────────────────────────────── */ +.tog { + width: 38px; height: 22px; border-radius: 11px; + background: #E4E6EE; position: relative; cursor: pointer; transition: background 0.18s; flex-shrink: 0; + display: inline-block; +} +.tog.on { background: #FF5500; } +.tog::after { + content:''; position: absolute; top: 3px; left: 3px; + width: 16px; height: 16px; background: #fff; border-radius: 50%; + transition: transform 0.18s; box-shadow: 0 1px 4px rgba(0,0,0,0.15); +} +.tog.on::after { transform: translateX(16px); } + +/* ─── Progress ────────────────────────────────────────────────────────── */ +.prog { height: 5px; background: #E4E6EE; border-radius: 3px; overflow: hidden; } +.prog-fill { height: 100%; border-radius: 3px; transition: width 0.4s; } +.prog-fill.or { background: #FF5500; } +.prog-fill.gr { background: #17A865; } + +/* ─── Inputs ──────────────────────────────────────────────────────────── */ +.inp { + background: #FFFFFF; border: 1.5px solid #E4E6EE; border-radius: 8px; + padding: 8px 12px; font-size: 13px; color: #1C1F2E; + font-family: 'Golos Text', sans-serif; outline: none; + transition: border-color 0.14s; width: 100%; + display: block; +} +.inp:focus { border-color: #FF5500; box-shadow: 0 0 0 3px #FFF2EC; } +.inp::placeholder { color: #9EA8BE; } +select.inp { cursor: pointer; } +.form-row { margin-bottom: 14px; } +.form-lbl { font-size: 11.5px; font-weight: 600; color: #5C6278; margin-bottom: 5px; text-transform: uppercase; letter-spacing: 0.04em; display: block; } + +/* ─── Alerts ──────────────────────────────────────────────────────────── */ +.alert { border-radius: 9px; padding: 11px 14px; font-size: 12.5px; display: flex; gap: 9px; margin-bottom: 16px; } +.alert-gr { background: #EDFAF4; border: 1px solid #A7E8CC; color: #136B41; } +.alert-yl { background: #FFFBEB; border: 1px solid #FCD678; color: #92680A; } +.alert-bl { background: #EEF3FF; border: 1px solid #C7D7FF; color: #3B6FFF; } +.alert-rd { background: #FEF1F1; border: 1px solid #F4AEAE; color: #E53935; } + +/* ─── Tabs ────────────────────────────────────────────────────────────── */ +.tabs { display: flex; gap: 0; border-bottom: 1.5px solid #E4E6EE; margin-bottom: 20px; } +.tab { + padding: 9px 18px; font-size: 13px; font-weight: 600; color: #5C6278; + cursor: pointer; border-bottom: 2.5px solid transparent; margin-bottom: -1.5px; transition: all 0.13s; + text-decoration: none; display: inline-block; +} +.tab:hover { color: #1C1F2E; } +.tab.active { color: #FF5500; border-bottom-color: #FF5500; } + +/* ─── Conn detail ─────────────────────────────────────────────────────── */ +.conn-detail { + background: #F4F5F7; border: 1px solid #E4E6EE; border-radius: 9px; + padding: 12px 16px; display: flex; flex-direction: column; gap: 9px; +} +.conn-row { display: flex; align-items: center; justify-content: space-between; } +.conn-k { font-size: 12px; color: #9EA8BE; } +.conn-v { font-size: 12px; font-weight: 600; color: #1C1F2E; font-family: 'JetBrains Mono', monospace; } + +/* ─── Activity ────────────────────────────────────────────────────────── */ +.act-item { display: flex; gap: 10px; padding: 10px 0; border-bottom: 1px solid #E4E6EE; } +.act-item:last-child { border-bottom: none; } +.act-icon { width: 32px; height: 32px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 14px; flex-shrink: 0; } +.act-text { font-size: 12.5px; color: #1C1F2E; } +.act-time { font-size: 11px; color: #9EA8BE; margin-top: 2px; font-family: 'JetBrains Mono', monospace; } + +/* ─── Section sep ─────────────────────────────────────────────────────── */ +.section-sep { display: flex; align-items: center; gap: 10px; margin: 20px 0; } +.section-sep-line { flex: 1; height: 1px; background: #E4E6EE; } +.section-sep-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: #9EA8BE; } + +/* ─── Pipeline ────────────────────────────────────────────────────────── */ +.pipeline { display: flex; align-items: center; padding: 18px 0; } +.pipe-step { display: flex; flex-direction: column; align-items: center; gap: 7px; flex: 1; } +.pipe-node { width: 46px; height: 46px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 18px; border: 2px solid; } +.pipe-node.done { border-color: #17A865; background: #EDFAF4; } +.pipe-node.active { border-color: #FF5500; background: #FFF2EC; } +.pipe-node.idle { border-color: #E4E6EE; background: #F4F5F7; } +.pipe-lbl { font-size: 11px; font-weight: 600; color: #5C6278; text-align: center; } +.pipe-line { flex: 1; height: 2px; max-width: 52px; align-self: flex-start; margin-top: 22px; } +.pipe-line.done { background: #17A865; } +.pipe-line.idle { background: #E4E6EE; } + +/* ─── Sync log ────────────────────────────────────────────────────────── */ +.log-row { display: flex; align-items: flex-start; gap: 10px; padding: 10px 0; border-bottom: 1px solid #E4E6EE; } +.log-row:last-child { border-bottom: none; } +.log-bullet { width: 8px; height: 8px; border-radius: 50%; margin-top: 4px; flex-shrink: 0; } +.log-msg { font-size: 12.5px; color: #1C1F2E; } +.log-meta { font-size: 11px; color: #9EA8BE; font-family: 'JetBrains Mono', monospace; margin-top: 2px; } + +/* ─── User row actions ────────────────────────────────────────────────── */ +.action-row { display: flex; gap: 6px; } + +/* ─── Inline status ───────────────────────────────────────────────────── */ +.inline-st { display: flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 500; } + +/* ─── List group (legacy compat) ──────────────────────────────────────── */ +.list-group { list-style: none; padding: 0; margin: 0; } +.list-group-item { + padding: 10px 0; border-bottom: 1px solid #E4E6EE; + display: flex; justify-content: space-between; align-items: center; + font-size: 13px; +} +.list-group-item:last-child { border-bottom: none; } +.list-group-item .lbl { font-size: 12px; color: #9EA8BE; } +.list-group-item .val { font-size: 12px; font-weight: 600; color: #1C1F2E; font-family: 'JetBrains Mono', monospace; } + +/* ─── Breadcrumb ──────────────────────────────────────────────────────── */ +.breadcrumb { display: flex; align-items: center; gap: 6px; margin-bottom: 16px; font-size: 12px; color: #9EA8BE; list-style: none; padding: 0; } +.breadcrumb a { color: #5C6278; text-decoration: none; } +.breadcrumb a:hover { color: #FF5500; } +.breadcrumb li + li::before { content: "/"; margin-right: 6px; color: #CDD0DC; } + +/* ─── Empty state ─────────────────────────────────────────────────────── */ +.empty-state { + text-align: center; padding: 48px 24px; color: #9EA8BE; +} +.empty-state i { font-size: 2.5rem; display: block; margin-bottom: 12px; } +.empty-state p { font-size: 13px; } + +/* ─── Table scroll wrapper ────────────────────────────────────────────── */ +.table-wrap { overflow-x: auto; width: 100%; } + +/* ─── Pagination ──────────────────────────────────────────────────────── */ +.pagination { display: flex; align-items: center; justify-content: center; gap: 8px; padding: 16px; } + +/* ─── Spinner ─────────────────────────────────────────────────────────── */ .spinner { - display: inline-block; - width: 2rem; - height: 2rem; - border: 3px solid var(--pico-muted-background-color); - border-top-color: var(--brand-primary); - border-radius: 50%; - animation: spin 0.75s linear infinite; + display: inline-block; width: 20px; height: 20px; + border: 2px solid #E4E6EE; border-top-color: #FF5500; + border-radius: 50%; animation: spin 0.75s linear infinite; } - @keyframes spin { to { transform: rotate(360deg); } } -/* Input group */ -.input-group { - display: flex; - gap: 0; +/* ─── Code ────────────────────────────────────────────────────────────── */ +pre, code { font-family: 'JetBrains Mono', monospace; font-size: 12px; } +pre { + background: #F4F5F7; border: 1px solid #E4E6EE; border-radius: 8px; + padding: 12px 14px; overflow-x: auto; white-space: pre-wrap; } -.input-group input { - border-radius: var(--pico-border-radius) 0 0 var(--pico-border-radius); - margin: 0; - flex: 1; +/* ─── Dialog ──────────────────────────────────────────────────────────── */ +dialog { + border: none; border-radius: 14px; padding: 0; + box-shadow: 0 8px 40px rgba(0,0,0,0.16); + max-width: 520px; width: 100%; +} +dialog::backdrop { background: rgba(0,0,0,0.45); } +.dialog-hd { + padding: 20px 24px 0; display: flex; align-items: center; + justify-content: space-between; margin-bottom: 16px; +} +.dialog-title { font-size: 16px; font-weight: 800; color: #1C1F2E; } +.dialog-body { padding: 0 24px 24px; } +.dialog-close { + background: none; border: none; cursor: pointer; font-size: 18px; + color: #9EA8BE; padding: 4px; border-radius: 6px; transition: color 0.13s; +} +.dialog-close:hover { color: #1C1F2E; } + +/* ─── Filter bar ──────────────────────────────────────────────────────── */ +.filter-bar { + display: flex; gap: 8px; flex-wrap: wrap; align-items: center; + padding: 14px 20px; border-bottom: 1px solid #E4E6EE; } -.input-group button { - border-radius: 0 var(--pico-border-radius) var(--pico-border-radius) 0; - margin: 0; - white-space: nowrap; +/* ─── Viewed-as banner ────────────────────────────────────────────────── */ +.view-as-bar { + background: #E64D00; color: #fff; text-align: center; + padding: 6px 16px; font-size: 13px; } +.view-as-bar strong { font-weight: 700; } -/* Empty state */ -.empty-state { - text-align: center; - padding: 3rem 1rem; - color: var(--pico-muted-color); +/* ──────────────────────────────────────────────────────────────────────── */ +/* LOGIN PAGE */ +/* ──────────────────────────────────────────────────────────────────────── */ +.login-wrap { min-height: 100vh; display: flex; background: #F4F5F7; } +.login-left { + flex: 1; background: #1C1F2E; display: flex; flex-direction: column; + justify-content: space-between; padding: 48px; + position: relative; overflow: hidden; } - -.empty-state .empty-icon { - font-size: 3rem; - display: block; - margin-bottom: 0.75rem; +.login-left-bg { + position: absolute; inset: 0; pointer-events: none; + background: + radial-gradient(ellipse 70% 55% at 0% 110%, rgba(255,85,0,0.18) 0%, transparent 65%), + radial-gradient(ellipse 50% 40% at 100% -10%, rgba(255,85,0,0.10) 0%, transparent 60%); } +.login-left-pattern { + position: absolute; inset: 0; pointer-events: none; + background-image: radial-gradient(circle, rgba(255,255,255,0.04) 1px, transparent 1px); + background-size: 28px 28px; +} +.login-brand { display: flex; align-items: center; gap: 12px; position: relative; z-index: 1; } +.login-brand-icon { width: 44px; height: 44px; border-radius: 11px; background: #FF5500; display: flex; align-items: center; justify-content: center; } +.login-brand-name { font-size: 20px; font-weight: 800; color: #fff; letter-spacing: -0.02em; } +.login-brand-sub { font-size: 11px; color: #5C6278; } +.login-hero { position: relative; z-index: 1; } +.login-hero-title { font-size: 32px; font-weight: 800; color: #fff; line-height: 1.25; letter-spacing: -0.03em; margin-bottom: 14px; } +.login-hero-title em { color: #FF5500; font-style: normal; } +.login-hero-body { font-size: 14px; color: #A8ADC3; line-height: 1.6; max-width: 340px; } +.login-chips { display: flex; gap: 10px; margin-top: 22px; flex-wrap: wrap; } +.login-chip { + display: flex; align-items: center; gap: 7px; padding: 7px 13px; + border-radius: 20px; background: rgba(255,255,255,0.07); border: 1px solid rgba(255,255,255,0.1); + font-size: 12px; color: rgba(255,255,255,0.65); font-weight: 500; +} +.login-footer { font-size: 11px; color: #5C6278; position: relative; z-index: 1; } +.login-right { width: 460px; flex-shrink: 0; display: flex; align-items: center; justify-content: center; padding: 40px; } +.login-box { width: 100%; max-width: 360px; } +.login-box-title { font-size: 24px; font-weight: 800; color: #1C1F2E; letter-spacing: -0.02em; margin-bottom: 4px; } +.login-box-sub { font-size: 13px; color: #5C6278; margin-bottom: 28px; } +.login-btn { + width: 100%; padding: 11px; border-radius: 9px; border: none; cursor: pointer; + background: #FF5500; color: #fff; font-size: 14px; font-weight: 700; + font-family: 'Golos Text', sans-serif; transition: all 0.15s; margin-top: 6px; display: block; +} +.login-btn:hover { background: #E64D00; box-shadow: 0 6px 20px rgba(255,85,0,0.3); } +.login-btn:disabled { opacity: 0.6; cursor: not-allowed; } +.login-divider { display: flex; align-items: center; gap: 10px; margin: 18px 0; } +.login-divider span { font-size: 11px; color: #9EA8BE; } +.login-divider::before, .login-divider::after { content:''; flex: 1; height: 1px; background: #E4E6EE; } +.login-hint { font-size: 11.5px; color: #9EA8BE; text-align: center; margin-top: 14px; line-height: 1.5; } +.login-hint a { color: #FF5500; text-decoration: none; } +.login-hint a:hover { text-decoration: underline; } diff --git a/web/templates/admin/logs.html b/web/templates/admin/logs.html index 5f86a7c..7f3509a 100644 --- a/web/templates/admin/logs.html +++ b/web/templates/admin/logs.html @@ -1,147 +1,147 @@ {% extends "base.html" %} -{% block title %}API Логи — ЭВОСИНК{% endblock %} +{% block title %}API Логи — Мои Товары{% endblock %} +{% block page_title %}API Логи{% endblock %} {% block content %} -
-

API Логи

- Найдено: {{ total }} +
API Логи
+
Журнал всех исходящих запросов · Найдено: {{ total }}
+ + +
+
+ + + + + + + {% if filter_service or filter_method or filter_status or filter_q or filter_hours != 24 %} + Сбросить + {% endif %} +
-{# ── filters ── #} -
- - - - - - - {% if filter_service or filter_method or filter_status or filter_q or filter_hours != 24 %} - Сбросить +
+ {% if logs %} +
+ + + + + + + + + + + + + + {% for log in logs %} + {% set is_error = log.response_status and log.response_status >= 400 %} + + + + + + + + + + + + + {% endfor %} + +
ВремяСервисМетодСтатусМсURL
{{ log.created_at | datefmt }} + {% if log.service == 'evotor' %} + {{ log.service }} + {% elif log.service == 'vk' %} + {{ log.service }} + {% else %} + {{ log.service }} + {% endif %} + {{ log.method }} + {% if log.response_status %} + {{ log.response_status }} + {% else %} + + {% endif %} + {{ log.duration_ms if log.duration_ms is not none else '—' }} + {{ log.url }} +
+
+ + {% if total_pages > 1 %} + + {% endif %} - {% else %} -
- -

Записей не найдено за выбранный период.

-
- {% endif %} -
- - + {% else %} +
+ +

Записей не найдено за выбранный период.

+
+ {% endif %} + +{% endblock %} +{% block scripts %} {% endblock %} diff --git a/web/templates/admin/roles.html b/web/templates/admin/roles.html index 13a87e5..3f10f3e 100644 --- a/web/templates/admin/roles.html +++ b/web/templates/admin/roles.html @@ -1,40 +1,44 @@ {% extends "base.html" %} -{% block title %}Роли и права — ЭВОСИНК{% endblock %} +{% block title %}Роли и права — Мои Товары{% endblock %} +{% block page_title %}Роли и права{% endblock %} {% block content %} - + -

Роли и права

+
Роли и права
+
Управление разрешениями для каждой роли
{% for role in roles %} -
-
-

{{ role.name }} - — {{ role.description or '' }} -

-
-
-
-
- {% for perm in permissions %} -
- -
- {% endfor %} -
- -
+
+
+
+
{{ role.name }}
+ {% if role.description %} +
{{ role.description }}
+ {% endif %}
-
+ +
+
+ {% for perm in permissions %} + + {% endfor %} +
+ +
+ {% endfor %} {% endblock %} diff --git a/web/templates/admin/user_detail.html b/web/templates/admin/user_detail.html index 0cfa501..aecc4a3 100644 --- a/web/templates/admin/user_detail.html +++ b/web/templates/admin/user_detail.html @@ -1,152 +1,176 @@ {% extends "base.html" %} -{% block title %}{{ target.first_name }} {{ target.last_name }} — Админ — ЭВОСИНК{% endblock %} +{% block title %}{{ target.first_name }} {{ target.last_name }} — Админ — Мои Товары{% endblock %} +{% block page_title %}Пользователь{% endblock %} {% block content %} - + {% if request.query_params.get('success') == 'reset_sent' %} -

Ссылка для сброса пароля отправлена.

+
Ссылка для сброса пароля отправлена.
{% elif request.query_params.get('success') == 'invite_sent' %} -

Приглашение отправлено.

+
Приглашение отправлено.
{% elif request.query_params.get('success') == 'saved' %} -

Данные сохранены.

+
Данные сохранены.
{% endif %} -
-
-
-

Профиль

-
    -
  • ID{{ target.id }}
  • -
  • Имя{{ target.first_name }} {{ target.last_name }}
  • -
  • Email - {{ target.email }} - {% if target.is_email_confirmed %} - подтверждён - {% else %} - не подтверждён - {% endif %} - -
  • -
  • Телефон{{ target.phone }}
  • -
  • Роль - - {% if target.role == 'system' %}Системный - {% elif target.role == 'admin' %}Администратор - {% else %}Пользователь - {% endif %} - -
  • -
  • Статус - - {% if target.status == 'active' %}Активен - {% elif target.status == 'pending' %}Ожидает - {% else %}Заблокирован - {% endif %} - -
  • -
  • Регистрация{{ target.created_at | datefmt }}
  • - {% if target.evotor_user_id %} -
  • Эвотор ID{{ target.evotor_user_id }}
  • - {% endif %} - {% if target.invite_token %} -
  • Приглашение до{{ target.invite_expires | datefmt }}
  • - {% endif %} -
-
+ +
+
+ {{ target.first_name[0] if target.first_name else '?' }}{{ target.last_name[0] if target.last_name else '' }} +
+
+
{{ target.first_name }} {{ target.last_name }}
+
{{ target.email }}
+
+
- {% if target.evotor_meta %} -
-

Данные Эвотор

-
-
{{ target.evotor_meta | tojson(indent=2) }}
-
-
+
+ + +
+ + +
+
Профиль
+
+
ID{{ target.id }}
+
+ Email + + {{ target.email }} + {% if target.is_email_confirmed %} + подтверждён + {% else %} + не подтверждён + {% endif %} + +
+
Телефон{{ target.phone or '—' }}
+
+ Роль + + {% if target.role == 'system' %}Системный + {% elif target.role == 'admin' %}Администратор + {% else %}Пользователь + {% endif %} + +
+
+ Статус + + {% if target.status == 'active' %}Активен + {% elif target.status == 'pending' %}Ожидает + {% else %}Заблокирован + {% endif %} + +
+
Регистрация{{ target.created_at | datefmt }}
+ {% if target.evotor_user_id %} +
Эвотор ID{{ target.evotor_user_id }}
{% endif %} + {% if target.invite_token %} +
Приглашение до{{ target.invite_expires | datefmt }}
+ {% endif %} +
-
-
-

Действия

-
- {% if target.status != 'active' %} -
- -
- {% endif %} - {% if target.status != 'suspended' %} -
- -
- {% endif %} -
- -
-
- -
-
- -
- {% if user.role == 'system' and target.id != user.id %} -
- -
- {% endif %} -
-
- -
-

Редактировать

-
-
-
-
- -
-
- -
-
- - - {% if user.role == 'system' %} - - {% endif %} - -
-
-
+ {% if target.evotor_meta %} +
+
Данные Эвотор
+
{{ target.evotor_meta | tojson(indent=2) }}
+ {% endif %} + +
+ + +
+ + +
+
Действия
+
+ {% if target.status != 'active' %} +
+ +
+ {% endif %} + {% if target.status != 'suspended' %} +
+ +
+ {% endif %} +
+ +
+
+ +
+
+ +
+ {% if user.role == 'system' and target.id != user.id %} +
+ +
+ {% endif %} +
+
+ + +
+
Редактировать
+
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+ {% if user.role == 'system' %} +
+ + +
+ {% endif %} + +
+
+ +
{% endblock %} diff --git a/web/templates/admin/users.html b/web/templates/admin/users.html index f86a195..9cd08c9 100644 --- a/web/templates/admin/users.html +++ b/web/templates/admin/users.html @@ -1,173 +1,201 @@ {% extends "base.html" %} -{% block title %}Пользователи — Администрирование — ЭВОСИНК{% endblock %} +{% block title %}Пользователи — Администрирование — Мои Товары{% endblock %} +{% block page_title %}Пользователи{% endblock %} {% block content %} -
-

Пользователи

- +
Пользователи
+
Управление аккаунтами и подключениями пользователей
+ + +
+
+ -
-
- -

Создать пользователя

-
- {% if create_errors %} -
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+ {% if user.role == 'system' %} +
+ + +
+ {% endif %} +
+ + +
+ +
{% if create_errors %} {% endif %} -
-
-
- - - - - {% if search or status_filter or role_filter %} - Сбросить - {% endif %} -
-
-
- -
-
- - - - - - - - - - - - - - - - {% for u in users %} - - - - - - - - - - - - {% else %} - - - - {% endfor %} - -
IDИмяEmailТелефонРольСтатусЭвоторДата
{{ u.id }}{{ u.first_name }} {{ u.last_name }} - {{ u.email }} - {% if not u.is_email_confirmed %} - - {% endif %} - {{ u.phone or '—' }} - {% if u.role == 'system' %}Системный - {% elif u.role == 'admin' %}Админ - {% else %}Польз. - {% endif %} - - {% if u.status == 'active' %}Активен - {% elif u.status == 'pending' %}Ожидает - {% else %}Заблок. - {% endif %} - - {% if u.evotor_user_id %} - - {% else %} - - {% endif %} - {{ u.created_at | datefmt }} - - - -
Пользователи не найдены
-
- {% if total_pages > 1 %} -
-
- {% if page > 1 %} - « - {% endif %} - Стр. {{ page }} из {{ total_pages }} - {% if page < total_pages %} - » - {% endif %} -
-
+ +
+
+ + + + + {% if search or status_filter or role_filter %} + Сбросить {% endif %} -
+ +
+ + +
+
+ + + + + + + + + + + + + + + {% for u in users %} + + + + + + + + + + + {% else %} + + + + {% endfor %} + +
IDПользовательТелефонРольСтатусЭвоторДата
{{ u.id }} +
+
+ {{ u.first_name[0] if u.first_name else '?' }}{{ u.last_name[0] if u.last_name else '' }} +
+
+
{{ u.first_name }} {{ u.last_name }}
+
+ {{ u.email }} + {% if not u.is_email_confirmed %} + + {% endif %} +
+
+
+
{{ u.phone or '—' }} + {% if u.role == 'system' %} + Системный + {% elif u.role == 'admin' %} + Админ + {% else %} + Польз. + {% endif %} + + {% if u.status == 'active' %} + Активен + {% elif u.status == 'pending' %} + Ожидает + {% else %} + Заблок. + {% endif %} + + {% if u.evotor_user_id %} + + {% else %} + + {% endif %} + {{ u.created_at | datefmt }} + + + +
Пользователи не найдены
+
+ + {% if total_pages > 1 %} + + {% endif %} +
{% if user.role == 'system' %} -
- - Управление ролями - +
+ + Управление ролями +
{% endif %} {% endblock %} diff --git a/web/templates/base.html b/web/templates/base.html index 73f82a8..69943ab 100644 --- a/web/templates/base.html +++ b/web/templates/base.html @@ -1,121 +1,155 @@ - + - - - {% block title %}ЭВОСИНК{% endblock %} - - - + + + {% block title %}Мои Товары{% endblock %} + + + + + - +{% block body %} +
+ + + + + +
+ + +
+
{% block page_title %}{% endblock %}
+ {% block topbar_extras %}{% endblock %} + {% if user %} + + + + {% endif %} +
{% if viewed_user %} -
- Просмотр от имени: {{ viewed_user.first_name }} {{ viewed_user.last_name }} ({{ viewed_user.email }}) -
- -
+
+ Просмотр от имени: {{ viewed_user.first_name }} {{ viewed_user.last_name }} ({{ viewed_user.email }}) +
+ +
{% endif %} -
- {% if errors %} - - {% endif %} +
+ {% if errors %} +
+ +
{% for e in errors %}
{{ e }}
{% endfor %}
+
+ {% endif %} + {% if success %} +
+ +
{{ success }}
+
+ {% endif %} + {% block content %}{% endblock %} +
- {% if success %} - - {% endif %} +
+
+{% endblock %} - {% block content %}{% endblock %} - - - {% if jivosite_widget_id %} - - {% endif %} - - - - {% block scripts %}{% endblock %} +{% if jivosite_widget_id %} + +{% endif %} + + +{% block scripts %}{% endblock %} diff --git a/web/templates/catalog/groups.html b/web/templates/catalog/groups.html index d7fbbb2..a5206ff 100644 --- a/web/templates/catalog/groups.html +++ b/web/templates/catalog/groups.html @@ -1,71 +1,63 @@ {% extends "base.html" %} -{% block title %}Группы — {{ store.name }} — ЭВОСИНК{% endblock %} +{% block title %}Группы — {{ store.name }} — Мои Товары{% endblock %} +{% block page_title %}Каталог Эвотор{% endblock %} {% block content %} - + -
-

Группы товаров — {{ store.name }}

- Всего: {{ groups | length }} +
Группы товаров — {{ store.name }}
+
Управление категориями · Всего: {{ groups | length }}
+ +
+ {% if groups %} +
+ + + + + + + + + + + + + {% for g in groups %} + {% set is_enabled = (enabled_ids is none) or (g.evotor_id in enabled_ids) %} + + + + + + + + + {% endfor %} + +
СинхронизацияГруппаТоваровIDОбновлено
+
+ +
+
+
{{ g.name }}
+
{{ product_counts.get(g.evotor_id, 0) }}{{ g.evotor_id }}{{ g.fetched_at | datefmt }} + + Товары + +
+
+ {% else %} +
+ +

Группы для этого магазина ещё не загружены.

+
+ {% endif %}
- -
- {% if groups %} -
- - - - - - - - - - - - - {% for g in groups %} - {% set is_enabled = (enabled_ids is none) or (g.evotor_id in enabled_ids) %} - - - - - - - - - {% endfor %} - -
СинхронизацияНазваниеКоличество товаровIDОбновлено
-
- -
-
{{ g.name }}{{ product_counts.get(g.evotor_id, 0) }}{{ g.evotor_id }}{{ g.fetched_at | datefmt }} - - Товары - -
-
- {% else %} -
- -

Группы для этого магазина ещё не загружены.

-
- {% endif %} -
{% endblock %} diff --git a/web/templates/catalog/products.html b/web/templates/catalog/products.html index 30925d2..f0dcad2 100644 --- a/web/templates/catalog/products.html +++ b/web/templates/catalog/products.html @@ -1,83 +1,78 @@ {% extends "base.html" %} -{% block title %}Товары — {{ store.name }} — ЭВОСИНК{% endblock %} +{% block title %}Товары — {{ store.name }} — Мои Товары{% endblock %} +{% block page_title %}Каталог Эвотор{% endblock %} {% block content %} - + -
-

Товары — {{ store.name }}

- Всего: {{ products | length }} -
+
Товары — {{ store.name }}
+
Всего: {{ products | length }}
{% if groups %} -
-
-
- - {% if group_id %} - Сбросить - {% endif %} -
-
-
+
+
+ + {% if group_id %} + Сбросить + {% endif %} +
+
{% endif %} -
- {% if products %} -
- - - - - - - - - - - - - - - {% for p in products %} - - - - - - - - - - - {% endfor %} - -
НазваниеГруппаАртикулЦенаОстатокЕд.ПродаётсяОбновлено
{{ p.name }}{{ group_map.get(p.group_evotor_id) or '—' }}{{ p.article_number or '—' }}{% if p.price is not none %}{{ p.price | price }}{% else %}—{% endif %}{% if p.quantity is not none %}{{ p.quantity }}{% else %}—{% endif %}{{ p.measure_name or '—' }} - {% if p.allow_to_sell %} - - {% elif p.allow_to_sell == false %} - - {% else %} - - {% endif %} - {{ p.fetched_at | datefmt }}
-
- {% else %} -
- -

Товары не найдены.

-
- {% endif %} -
+
+ {% if products %} +
+ + + + + + + + + + + + + + + {% for p in products %} + + + + + + + + + + + {% endfor %} + +
НазваниеГруппаАртикулЦенаОстатокЕд.ПродаётсяОбновлено
{{ p.name }}
{{ group_map.get(p.group_evotor_id) or '—' }}{{ p.article_number or '—' }}{% if p.price is not none %}{{ p.price | price }}{% else %}—{% endif %}{% if p.quantity is not none %}{{ p.quantity }}{% else %}—{% endif %}{{ p.measure_name or '—' }} + {% if p.allow_to_sell %} + + {% elif p.allow_to_sell == false %} + + {% else %} + + {% endif %} + {{ p.fetched_at | datefmt }}
+
+ {% else %} +
+ +

Товары не найдены.

+
+ {% endif %} +
{% endblock %} diff --git a/web/templates/catalog/stores.html b/web/templates/catalog/stores.html index 8a93aae..abd865c 100644 --- a/web/templates/catalog/stores.html +++ b/web/templates/catalog/stores.html @@ -1,66 +1,62 @@ {% extends "base.html" %} -{% block title %}Магазины — ЭВОСИНК{% endblock %} +{% block title %}Магазины — Мои Товары{% endblock %} +{% block page_title %}Каталог Эвотор{% endblock %} {% block content %} -
-

Магазины Эвотор

- Всего: {{ stores | length }} -
+
Магазины Эвотор
+
Выберите магазины для синхронизации · Всего: {{ stores | length }}
-
- {% if stores %} -
- - - - - - - - - - - - - {% for s in stores %} - {% set is_enabled = (enabled_ids is none) or (s.evotor_id in enabled_ids) %} - - - - - - - - - {% endfor %} - -
СинхронизацияНазваниеАдресIDОбновлено
-
- -
-
{{ s.name }}{{ s.address or '—' }}{{ s.evotor_id }}{{ s.fetched_at | datefmt }} - - Товары - - - Группы - -
-
- {% else %} -
- -

Магазины ещё не загружены.
Синхронизация выполняется каждые {{ refresh_interval }} сек. автоматически.

-
- {% endif %} -
+
+ {% if stores %} +
+ + + + + + + + + + + + + {% for s in stores %} + {% set is_enabled = (enabled_ids is none) or (s.evotor_id in enabled_ids) %} + + + + + + + + + {% endfor %} + +
СинхронизацияНазваниеАдресIDОбновлено
+
+ +
+
+
{{ s.name }}
+
{{ s.address or '—' }}{{ s.evotor_id }}{{ s.fetched_at | datefmt }} + +
+
+ {% else %} +
+ +

Магазины ещё не загружены.
Синхронизация выполняется каждые {{ refresh_interval }} сек. автоматически.

+
+ {% endif %} +
{% endblock %} diff --git a/web/templates/confirm_email.html b/web/templates/confirm_email.html index d912a54..7144e25 100644 --- a/web/templates/confirm_email.html +++ b/web/templates/confirm_email.html @@ -1,16 +1,22 @@ -{% extends "base.html" %} -{% block title %}Подтверждение email — ЭВОСИНК{% endblock %} + + + + + + Подтверждение email — Мои Товары + + + + + + + -{% block content %} -
-
-
-
- -

Подтвердите ваш email

-

Проверьте почту и нажмите на ссылку для подтверждения.

-
-
-
+
+ +
Подтвердите ваш email
+
Проверьте почту и нажмите на ссылку для подтверждения.
-{% endblock %} + + + diff --git a/web/templates/connections.html b/web/templates/connections.html index aed99b2..2281b7f 100644 --- a/web/templates/connections.html +++ b/web/templates/connections.html @@ -1,210 +1,212 @@ {% extends "base.html" %} -{% block title %}Подключения — ЭВОСИНК{% endblock %} +{% block title %}Подключения — Мои Товары{% endblock %} +{% block page_title %}Подключения{% endblock %} {% block content %} -
-
+
Подключения
+
Управление интеграциями с Эвотор и VK Market
-

- Подключения -

+{% if request.query_params.get('success') %} +
+ +
Подключение сохранено.
+
+{% endif %} - {% if request.query_params.get('success') %} - - {% endif %} - - {# ── Evotor ── #} -
-
- Эвотор - {% if evotor %} - Подключено - {% else %} - Не подключено - {% endif %} -
- - {% if evotor %} -
    -
  • - Токен - {{ evotor.access_token[:8] }}•••••••• -
  • - {% if evotor.evotor_user_id %} -
  • - Evotor User ID - {{ evotor.evotor_user_id }} -
  • - {% endif %} -
  • - Подключено - {{ evotor.connected_at | datefmt }} -
  • -
  • - Обновлено - {{ evotor.updated_at | datefmt }} -
  • -
- {% endif %} - -
-
- - {% if evotor %}Обновить токен{% else %}Ввести API-токен{% endif %} - -
- - - -
-
- - {% if evotor %} -
- - -
-
- -
- {% endif %} -
-
- - {# ── VK ── #} -
-
- ВКонтакте (Маркет) - {% if vk %} - Подключено - {% else %} - Не подключено - {% endif %} -
- - {% if vk %} -
    -
  • - Токен - {{ vk.access_token[:8] }}•••••••• -
  • - {% if vk.vk_user_id %} -
  • - ID сообщества - {{ vk.vk_user_id }} -
  • - {% endif %} - {% if vk.first_name or vk.last_name %} -
  • - Аккаунт - {{ vk.first_name }} {{ vk.last_name }} -
  • - {% endif %} -
  • - Подключено - {{ vk.connected_at | datefmt }} -
  • -
  • - Обновлено - {{ vk.updated_at | datefmt }} -
  • -
- {% endif %} - -
- - -
- Ввести токен вручную -
- - - -
-
- - {% if vk %} -
- - -
-
- -
- {% endif %} -
-
+
+ {# ── Evotor ── #} +
+
+
+
Эвотор
+
Платформа кассовых решений и товарного учёта
+
+ {% if evotor %} + Подключено + {% else %} + Не подключено + {% endif %}
+ + {% if evotor %} +
+
+ Токен + {{ evotor.access_token[:8] }}•••••••• +
+ {% if evotor.evotor_user_id %} +
+ Evotor User ID + {{ evotor.evotor_user_id }} +
+ {% endif %} +
+ Подключено + {{ evotor.connected_at | datefmt }} +
+
+ Обновлено + {{ evotor.updated_at | datefmt }} +
+
+ {% endif %} + +
+ + + {% if evotor %}Обновить токен{% else %}Ввести API-токен{% endif %} + +
+
+ + +
+
+ + +
+ +
+
+ + {% if evotor %} +
+ + +
+
+ +
+ {% endif %} +
+ + {# ── VK ── #} +
+
+
+
ВКонтакте (Маркет)
+
market.* API, версия 5.199
+
+ {% if vk %} + Подключено + {% else %} + Не подключено + {% endif %} +
+ + {% if vk %} +
+
+ Токен + {{ vk.access_token[:8] }}•••••••• +
+ {% if vk.vk_user_id %} +
+ ID сообщества + {{ vk.vk_user_id }} +
+ {% endif %} + {% if vk.first_name or vk.last_name %} +
+ Аккаунт + {{ vk.first_name }} {{ vk.last_name }} +
+ {% endif %} +
+ Подключено + {{ vk.connected_at | datefmt }} +
+
+ Обновлено + {{ vk.updated_at | datefmt }} +
+
+ {% endif %} + + + +
+ + Ввести токен вручную + +
+
+ + +
+
+ + +
Числовой ID группы/паблика с включённым Маркетом (без минуса)
+
+ +
+
+ + {% if vk %} +
+ + +
+
+ +
+ {% endif %} +
+
{% endblock %} {% block scripts %} {% endblock %} diff --git a/web/templates/email_confirmed.html b/web/templates/email_confirmed.html index f51283d..40be7c6 100644 --- a/web/templates/email_confirmed.html +++ b/web/templates/email_confirmed.html @@ -1,17 +1,23 @@ -{% extends "base.html" %} -{% block title %}Email подтвержден — ЭВОСИНК{% endblock %} + + + + + + Email подтверждён — Мои Товары + + + + + + + -{% block content %} -
-
-
-
- -

Email подтвержден!

-

Ваш email успешно подтвержден. Теперь вы можете войти в систему.

- Войти -
-
-
+
+ +
Email подтверждён!
+
Ваш email успешно подтверждён. Теперь вы можете войти в систему.
+ Войти
-{% endblock %} + + + diff --git a/web/templates/forgot_password.html b/web/templates/forgot_password.html index 0e115e6..62246cd 100644 --- a/web/templates/forgot_password.html +++ b/web/templates/forgot_password.html @@ -1,24 +1,48 @@ -{% extends "base.html" %} -{% block title %}Забыли пароль — ЭВОСИНК{% endblock %} + + + + + + Забыли пароль — Мои Товары + + + + + + + -{% block content %} -
-
-
-
-

Забыли пароль?

-

Введите email, указанный при регистрации.

-
- - -
- -
-
+
+
+
+ + + +
+ +
Введите email, указанный при регистрации
+
+ + {% if errors %} +
+ +
{% for e in errors %}
{{ e }}
{% endfor %}
+
+ {% endif %} + +
+
+ + +
+ +
+ +
-{% endblock %} + + + diff --git a/web/templates/invite.html b/web/templates/invite.html index ba05225..72abb28 100644 --- a/web/templates/invite.html +++ b/web/templates/invite.html @@ -1,44 +1,79 @@ -{% extends "base.html" %} -{% block title %}Завершение регистрации — ЭВОСИНК{% endblock %} + + + + + + Завершение регистрации — Мои Товары + + + + + + + -{% block content %} -
-
-
-
-

Добро пожаловать в ЭВОСИНК!

-
-
-

Ваш аккаунт был создан через Эвотор. Заполните данные профиля и задайте пароль для входа.

-
-
-
- -
-
- -
-
- - - - - -
-
-
+
+
+
+ + + +
+ +
Ваш аккаунт создан через Эвотор. Заполните профиль и задайте пароль для входа.
+
+ + {% if errors %} +
+ +
{% for e in errors %}
{{ e }}
{% endfor %}
+
+ {% endif %} + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
-{% endblock %} + + + + + diff --git a/web/templates/login.html b/web/templates/login.html index 39d9c09..d7293e0 100644 --- a/web/templates/login.html +++ b/web/templates/login.html @@ -1,26 +1,105 @@ -{% extends "base.html" %} -{% block title %}Вход — ЭВОСИНК{% endblock %} + + + + + + Вход — Мои Товары + + + + + + + -{% block content %} -
-
- + -{% endblock %} + + + diff --git a/web/templates/message.html b/web/templates/message.html index fdd483c..9267afd 100644 --- a/web/templates/message.html +++ b/web/templates/message.html @@ -1,18 +1,24 @@ {% extends "base.html" %} -{% block title %}{{ title }} — ЭВОСИНК{% endblock %} +{% block title %}{{ title }} — Мои Товары{% endblock %} +{% block page_title %}{{ title }}{% endblock %} {% block content %} -
-
-
-
-

{{ title }}

-

{{ message }}

- {% if link %} - {{ link_text }} - {% endif %} -
-
+
+
+
+ {% if 'ошибка' in title|lower or 'error' in title|lower %} + + {% elif 'успешно' in title|lower or 'готово' in title|lower or 'подтвержден' in title|lower %} + + {% else %} + + {% endif %}
+
{{ title }}
+
{{ message }}
+ {% if link %} + {{ link_text }} + {% endif %} +
{% endblock %} diff --git a/web/templates/profile_change_password.html b/web/templates/profile_change_password.html index 3dfd26b..b94bc7c 100644 --- a/web/templates/profile_change_password.html +++ b/web/templates/profile_change_password.html @@ -1,31 +1,31 @@ {% extends "base.html" %} -{% block title %}Изменить пароль — ЭВОСИНК{% endblock %} +{% block title %}Изменить пароль — Мои Товары{% endblock %} +{% block page_title %}Изменить пароль{% endblock %} {% block content %} -
-
-
-
-

Изменить пароль

-
-
-
- - - -
- - Отмена -
-
-
-
+
Изменить пароль
+
Обновите пароль для входа в систему
+ +
+
+
+ +
+
+ + +
+
+ + +
+
+ + Отмена +
+
{% endblock %} diff --git a/web/templates/profile_delete.html b/web/templates/profile_delete.html index 9fa1b58..225b33c 100644 --- a/web/templates/profile_delete.html +++ b/web/templates/profile_delete.html @@ -1,31 +1,27 @@ {% extends "base.html" %} -{% block title %}Удалить аккаунт — ЭВОСИНК{% endblock %} +{% block title %}Удалить аккаунт — Мои Товары{% endblock %} +{% block page_title %}Удалить аккаунт{% endblock %} {% block content %} -
-
-
-
-

Удалить аккаунт

-
-
- -
- -
- - Отмена -
-
-
-
+
Удалить аккаунт
+
Это действие необратимо
+ +
+
+ +
Внимание! Все ваши данные, подключения и история синхронизации будут удалены без возможности восстановления.
+
+
+
+ +
+
+ + Отмена +
+
{% endblock %} diff --git a/web/templates/profile_edit.html b/web/templates/profile_edit.html index f42506f..225db57 100644 --- a/web/templates/profile_edit.html +++ b/web/templates/profile_edit.html @@ -1,43 +1,40 @@ {% extends "base.html" %} -{% block title %}Редактировать профиль — ЭВОСИНК{% endblock %} +{% block title %}Редактировать профиль — Мои Товары{% endblock %} +{% block page_title %}Редактировать профиль{% endblock %} {% block content %} -
-
-
-
-

Редактировать профиль

-
-
-
-
-
- -
-
- -
-
- - -
- - Отмена -
-
-
-
+
Редактировать профиль
+
Обновите ваши личные данные
+ +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + Отмена +
+
{% endblock %} diff --git a/web/templates/profile_view.html b/web/templates/profile_view.html index 175ad4b..e78abf0 100644 --- a/web/templates/profile_view.html +++ b/web/templates/profile_view.html @@ -1,86 +1,113 @@ {% extends "base.html" %} -{% block title %}Личный кабинет — ЭВОСИНК{% endblock %} +{% block title %}Профиль — Мои Товары{% endblock %} +{% block page_title %}Профиль{% endblock %} {% block content %} -
-
-
-
-

Личный кабинет

-
-
    -
  • - Имя - {{ user.first_name }} -
  • -
  • - Фамилия - {{ user.last_name }} -
  • -
  • - Email - - {{ user.email }} - {% if user.is_email_confirmed %} - подтверждён - {% else %} - не подтверждён - {% endif %} - -
  • -
  • - Телефон - {{ user.phone }} -
  • -
  • - Роль - - {% if user.role == 'system' %}Системный - {% elif user.role == 'admin' %}Администратор - {% else %}Пользователь - {% endif %} - -
  • -
  • - Статус - - {% if user.status == 'active' %}Активен - {% elif user.status == 'pending' %}Ожидает подтверждения - {% else %}Заблокирован - {% endif %} - -
  • - {% if user.evotor_user_id %} -
  • - Эвотор ID - {{ user.evotor_user_id }} -
  • - {% endif %} -
  • - Регистрация - {{ user.created_at | datefmt }} -
  • -
- -
+
Личный кабинет
+
Ваши данные и настройки аккаунта
+ +
+ +
+
+
+
Данные профиля
+
Основная информация об аккаунте
+
+
+ {{ user.first_name[0] if user.first_name else '?' }}{{ user.last_name[0] if user.last_name else '' }} +
+ +
+
+ Имя + {{ user.first_name }} +
+
+ Фамилия + {{ user.last_name }} +
+
+ Email + + {{ user.email }} + {% if user.is_email_confirmed %} + подтверждён + {% else %} + не подтверждён + {% endif %} + +
+
+ Телефон + {{ user.phone or '—' }} +
+
+ Роль + + {% if user.role == 'system' %} + Системный + {% elif user.role == 'admin' %} + Администратор + {% else %} + Пользователь + {% endif %} + +
+
+ Статус + + {% if user.status == 'active' %} + Активен + {% elif user.status == 'pending' %} + Ожидает + {% else %} + Заблокирован + {% endif %} + +
+ {% if user.evotor_user_id %} +
+ Эвотор ID + {{ user.evotor_user_id }} +
+ {% endif %} +
+ Регистрация + {{ user.created_at | datefmt }} +
+
+
+ +
+ +
+
Опасная зона
+
Необратимые действия с аккаунтом
+ + Удалить аккаунт + +
+
+
{% endblock %} diff --git a/web/templates/register.html b/web/templates/register.html index 306a79c..27ef717 100644 --- a/web/templates/register.html +++ b/web/templates/register.html @@ -1,44 +1,83 @@ -{% extends "base.html" %} -{% block title %}Регистрация — ЭВОСИНК{% endblock %} + + + + + + Регистрация — Мои Товары + + + + + + + -{% block content %} -
-
-
-
-

Регистрация

-
-
-
- -
-
- -
-
- - - - - -
- -
-
+
+
+
+ + + +
+ +
Создайте аккаунт для работы с Мои Товары
+
+ + {% if errors %} +
+ +
{% for e in errors %}
{{ e }}
{% endfor %}
+
+ {% endif %} + +
+
+
+ + +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+ +
-{% endblock %} + + + + + diff --git a/web/templates/reset_password.html b/web/templates/reset_password.html index 845a9a8..2d84e94 100644 --- a/web/templates/reset_password.html +++ b/web/templates/reset_password.html @@ -1,23 +1,45 @@ -{% extends "base.html" %} -{% block title %}Новый пароль — ЭВОСИНК{% endblock %} + + + + + + Новый пароль — Мои Товары + + + + + + + -{% block content %} -
-
-
-
-

Новый пароль

-
- - - -
-
-
+
+
+
+
+ +
Введите и подтвердите новый пароль
+
+ + {% if errors %} +
+ +
{% for e in errors %}
{{ e }}
{% endfor %}
+
+ {% endif %} + +
+
+ + +
+
+ + +
+ +
-{% endblock %} + + + diff --git a/web/templates/sync.html b/web/templates/sync.html index 536347d..ecab9de 100644 --- a/web/templates/sync.html +++ b/web/templates/sync.html @@ -1,71 +1,102 @@ {% extends "base.html" %} -{% block title %}Синхронизация — ЭВОСИНК{% endblock %} +{% block title %}Синхронизация — Мои Товары{% endblock %} +{% block page_title %}Синхронизация{% endblock %} {% block content %} -
-

Синхронизация

-
+
Синхронизация
+
Настройка и управление синхронизацией товаров Эвотор → VK Market
{% if saved %} - +
+ +
Настройки сохранены.
+
{% endif %}
-
-

Фоновые задачи

-

Включайте поочерёдно: сначала проверьте зеркало Эвотор, затем ВК, затем синхронизацию.

+
-
+
+
+
+
Фоновые задачи
+
Включайте поочерёдно: сначала Эвотор, затем ВК, затем синхронизацию
+
+
-
+
-
-

Настройки цены

- -
+
+
+
+
Настройки цены
+
Трансформация цен при передаче в VK Market
+
+
+
+ + +
Цена из Эвотор умножается на это значение перед отправкой в ВК. По умолчанию: 1.
+
+
- +
+ + {% endblock %} diff --git a/web/templates/vk_catalog/albums.html b/web/templates/vk_catalog/albums.html index 3488c8d..19ec9c7 100644 --- a/web/templates/vk_catalog/albums.html +++ b/web/templates/vk_catalog/albums.html @@ -1,52 +1,53 @@ {% extends "base.html" %} -{% block title %}Альбомы ВК — ЭВОСИНК{% endblock %} +{% block title %}Альбомы ВК — Мои Товары{% endblock %} +{% block page_title %}Каталог ВКонтакте{% endblock %} {% block content %} -
-

Каталог ВКонтакте — Альбомы

- Всего: {{ albums | length }} -
+
Каталог ВКонтакте — Альбомы
+
Альбомы из VK Market · Всего: {{ albums | length }}
-
- {% if not vk_conn %} -
- -

ВКонтакте не подключён.
Перейти к подключениям

-
- {% elif albums %} -
- - - - - - - - - - - - {% for a in albums %} - - - - - - - - {% endfor %} - -
НазваниеТоваровIDОбновлено
{{ a.title }}{{ a.count if a.count is not none else '—' }}{{ a.album_id }}{{ a.fetched_at | datefmt }} - - Товары - -
-
- {% else %} -
- -

Альбомы ещё не загружены.
Синхронизация выполняется каждые {{ refresh_interval }} сек. автоматически.

-
- {% endif %} -
+
+ {% if not vk_conn %} +
+ +

ВКонтакте не подключён.
Перейти к подключениям

+
+ {% elif albums %} +
+ + + + + + + + + + + + {% for a in albums %} + + + + + + + + {% endfor %} + +
НазваниеТоваровIDОбновлено
+
{{ a.title }}
+
{{ a.count if a.count is not none else '—' }}{{ a.album_id }}{{ a.fetched_at | datefmt }} + + Товары + +
+
+ {% else %} +
+ +

Альбомы ещё не загружены.
Синхронизация выполняется каждые {{ refresh_interval }} сек. автоматически.

+
+ {% endif %} +
{% endblock %} diff --git a/web/templates/vk_catalog/products.html b/web/templates/vk_catalog/products.html index 32d83f8..986fc21 100644 --- a/web/templates/vk_catalog/products.html +++ b/web/templates/vk_catalog/products.html @@ -1,75 +1,72 @@ {% extends "base.html" %} -{% block title %}Товары ВК — {{ album.title }} — ЭВОСИНК{% endblock %} +{% block title %}Товары ВК — {{ album.title }} — Мои Товары{% endblock %} +{% block page_title %}Каталог ВКонтакте{% endblock %} {% block content %} - + -
-

{{ album.title }}

- Всего: {{ products | length }} +
{{ album.title }}
+
Товары из VK Market · Всего: {{ products | length }}
+ +
+ {% if products %} +
+ + + + + + + + + + + + + {% for p in products %} + + + + + + + + + {% endfor %} + +
НазваниеЦенаСтатусIDОбновлено
+ {% if p.thumb_url %} + + {% else %} +
+ +
+ {% endif %} +
+
{{ p.name }}
+ {% if p.description %} +
{{ p.description[:80] }}{% if p.description|length > 80 %}…{% endif %}
+ {% endif %} +
{% if p.price is not none %}{{ p.price | price }}{% else %}—{% endif %} + {% if p.availability == 0 %} + В наличии + {% elif p.availability == 1 %} + Удалён + {% elif p.availability == 2 %} + Недоступен + {% else %} + + {% endif %} + {{ p.vk_product_id }}{{ p.fetched_at | datefmt }}
+
+ {% else %} +
+ +

Товары в этом альбоме не найдены.

+
+ {% endif %}
- -
- {% if products %} -
- - - - - - - - - - - - - {% for p in products %} - - - - - - - - - {% endfor %} - -
НазваниеЦенаСтатусIDОбновлено
- {% if p.thumb_url %} - - {% else %} - - {% endif %} - - {{ p.name }} - {% if p.description %} -
{{ p.description[:80] }}{% if p.description|length > 80 %}…{% endif %} - {% endif %} -
- {% if p.price is not none %}{{ p.price | price }}{% else %}—{% endif %} - - {% if p.availability == 0 %} - В наличии - {% elif p.availability == 1 %} - Удалён - {% elif p.availability == 2 %} - Недоступен - {% else %} - - {% endif %} - {{ p.vk_product_id }}{{ p.fetched_at | datefmt }}
-
- {% else %} -
- -

Товары в этом альбоме не найдены.

-
- {% endif %} -
{% endblock %}