feat(admin): expand exchange admin and unify admin UX
Some checks failed
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m39s
CI/CD Pipeline / Run Tests (pull_request) Successful in 3m0s
CI/CD Pipeline / Run API Inventory E2E Tests (pull_request) Successful in 35s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped

This commit is contained in:
2026-03-24 13:58:24 +01:00
parent 559b9bc5ef
commit c98ba76081
33 changed files with 2915 additions and 209 deletions

View File

@@ -17,7 +17,14 @@
--mx-success: #2dd4bf;
--mx-warning: #ffc857;
--mx-danger: #ff6b9f;
--mx-table-head: rgba(10, 34, 40, 0.96);
--mx-table-head-active: rgba(13, 43, 50, 0.98);
--mx-table-row: rgba(6, 20, 25, 0.84);
--mx-table-row-alt: rgba(9, 28, 34, 0.94);
--mx-table-row-hover: rgba(15, 47, 56, 0.96);
--mx-table-row-selected: rgba(73, 208, 200, 0.16);
--mx-shadow: 0 24px 60px rgba(1, 8, 11, 0.44);
--mx-sidebar-width: clamp(23rem, 27vw, 26rem);
}
html,
@@ -83,11 +90,63 @@ body,
.main-sidebar {
background:
linear-gradient(180deg, rgba(3, 13, 17, 0.985) 0%, rgba(5, 19, 24, 0.985) 100%),
radial-gradient(circle at top, rgba(73, 208, 200, 0.1), transparent 34%);
linear-gradient(180deg, rgba(3, 13, 17, 0.985) 0%, rgba(5, 19, 24, 0.985) 100%);
border-right: 1px solid rgba(73, 208, 200, 0.1);
box-shadow: inset -1px 0 0 rgba(255, 255, 255, 0.03);
overflow-x: hidden;
position: relative;
}
.main-sidebar .brand-link,
.main-sidebar .sidebar {
width: 100%;
}
.main-sidebar .brand-link,
.main-sidebar .sidebar,
.main-sidebar .user-panel {
position: relative;
z-index: 2;
}
#mx-sidebar-particles {
position: absolute;
inset: 0;
z-index: 0;
pointer-events: none;
overflow: hidden;
opacity: 0.98;
background:
radial-gradient(circle at 14% 10%, rgba(73, 208, 200, 0.22), transparent 30%),
radial-gradient(circle at 84% 16%, rgba(136, 217, 255, 0.16), transparent 27%),
radial-gradient(circle at 46% 42%, rgba(90, 100, 201, 0.12), transparent 34%),
linear-gradient(180deg, rgba(4, 15, 20, 0.06), rgba(4, 15, 20, 0.2));
}
#mx-sidebar-particles::after {
content: "";
position: absolute;
inset: 0;
background:
linear-gradient(180deg, rgba(2, 10, 13, 0.02), rgba(2, 10, 13, 0.22) 42%, rgba(2, 10, 13, 0.34));
box-shadow:
inset 0 18px 38px rgba(120, 244, 233, 0.05),
inset 0 -24px 52px rgba(7, 17, 34, 0.22);
}
#mx-sidebar-particles .particles-js-canvas-el {
display: block;
width: 100% !important;
height: 100% !important;
opacity: 0.98;
filter: saturate(1.18) brightness(1.08);
}
.layout-fixed .brand-link,
.layout-fixed .main-sidebar .brand-link,
.layout-navbar-fixed.layout-fixed .wrapper .brand-link,
.layout-navbar-fixed.layout-fixed .wrapper .brand-link.text-sm {
width: var(--mx-sidebar-width) !important;
}
.brand-link {
@@ -97,13 +156,18 @@ body,
.brand-link .brand-text {
font-weight: 700;
letter-spacing: 0.04em;
text-transform: uppercase;
letter-spacing: 0.01em;
text-transform: none;
display: block;
white-space: normal;
line-height: 1.15;
font-size: 0.98rem;
}
.nav-sidebar > .nav-item > .nav-link {
margin: 0 0 0.4rem;
border-radius: 14px;
padding: 0.78rem 0.9rem;
border-radius: 10px;
color: var(--mx-text-muted);
transition: background-color 0.18s ease, color 0.18s ease, transform 0.18s ease;
}
@@ -118,18 +182,23 @@ body,
.nav-sidebar > .nav-item > .nav-link.active,
.nav-sidebar .nav-treeview > .nav-item > .nav-link.active {
background: linear-gradient(90deg, rgba(73, 208, 200, 0.18), rgba(86, 100, 201, 0.18));
border: 1px solid rgba(73, 208, 200, 0.16);
background: rgba(91, 127, 177, 0.2);
border: 1px solid rgba(119, 155, 204, 0.2);
color: #ffffff;
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.04),
0 8px 18px rgba(6, 18, 22, 0.28);
0 6px 16px rgba(6, 18, 22, 0.2);
}
#jazzy-sidebar .sidebar {
padding: 0.85rem 0.75rem 1rem;
}
#jazzy-sidebar .brand-link {
min-height: 4.1rem;
padding-right: 1rem;
}
#jazzy-sidebar .user-panel {
margin-left: 0;
margin-right: 0;
@@ -137,10 +206,51 @@ body,
padding-right: 0.25rem;
}
#jazzy-sidebar nav,
#jazzy-sidebar .nav-sidebar,
#jazzy-sidebar .nav-sidebar > .nav-item,
#jazzy-sidebar .nav-sidebar > .nav-item > .nav-link,
#jazzy-sidebar .nav-sidebar .nav-treeview > .nav-item,
#jazzy-sidebar .nav-sidebar .nav-treeview > .nav-item > .nav-link,
#jazzy-sidebar .user-panel {
width: 100%;
max-width: none;
}
#jazzy-sidebar .nav-sidebar {
padding-right: 0.1rem;
}
#jazzy-sidebar .nav-sidebar .nav-link {
display: flex;
align-items: flex-start;
gap: 0.35rem;
}
#jazzy-sidebar .nav-sidebar .nav-link p {
white-space: normal;
overflow: visible;
text-overflow: clip;
line-height: 1.24;
margin-right: 1.1rem;
overflow-wrap: anywhere;
word-break: normal;
font-size: 0.9rem;
font-weight: 600;
}
#jazzy-sidebar .nav-sidebar .nav-icon {
margin-top: 0.15rem;
}
#jazzy-sidebar .nav-sidebar .nav-treeview {
padding-left: 0.6rem;
}
#jazzy-sidebar .nav-sidebar .nav-link > .right {
top: 0.95rem;
}
.nav-sidebar .nav-header {
color: var(--mx-text-dim);
font-size: 0.7rem;
@@ -269,8 +379,9 @@ table,
}
.results thead th,
.table thead th {
background: rgba(10, 34, 40, 0.96);
.table thead th,
#result_list thead th {
background: var(--mx-table-head);
border-bottom: 1px solid rgba(73, 208, 200, 0.12);
color: #d5f6f2;
font-size: 0.76rem;
@@ -279,31 +390,102 @@ table,
text-transform: uppercase;
}
.results thead th.sorted,
.table thead th.sorted,
#result_list thead th.sorted {
background: var(--mx-table-head-active);
}
.results thead th a,
.results thead th .text a,
.results thead th .sortoptions a,
.table thead th a,
#result_list thead th a {
color: inherit;
}
.results thead th a:hover,
.results thead th .sortoptions a:hover,
.table thead th a:hover,
#result_list thead th a:hover {
color: #ffffff;
}
.results tbody tr,
.table tbody tr {
background: rgba(5, 18, 23, 0.68);
.results tbody tr.row1,
.results tbody tr.row2,
.table tbody tr,
.table-striped tbody tr,
#result_list tbody tr {
background: var(--mx-table-row);
}
.results tbody tr:nth-child(odd),
.results tbody tr.row1,
.table tbody tr:nth-child(odd),
.table-striped tbody tr:nth-of-type(odd),
#result_list tbody tr:nth-child(odd) {
background: var(--mx-table-row);
}
.results tbody tr:nth-child(even),
.table tbody tr:nth-child(even) {
background: rgba(8, 25, 31, 0.8);
.results tbody tr.row2,
.table tbody tr:nth-child(even),
.table-striped tbody tr:nth-of-type(even),
#result_list tbody tr:nth-child(even) {
background: var(--mx-table-row-alt);
}
.results tbody tr:hover,
.table tbody tr:hover {
background: rgba(13, 40, 48, 0.9);
.results tbody tr.row1:hover,
.results tbody tr.row2:hover,
.table tbody tr:hover,
.table-striped tbody tr:hover,
#result_list tbody tr:hover {
background: var(--mx-table-row-hover) !important;
}
.results tbody tr.selected,
.results tbody tr.selected:nth-child(odd),
.results tbody tr.selected:nth-child(even),
.table tbody tr.selected,
.table-striped tbody tr.selected,
#result_list tbody tr.selected {
background: var(--mx-table-row-selected) !important;
}
.results td,
.results th,
.table td,
.table th {
.table th,
#result_list td,
#result_list th {
border-color: rgba(73, 208, 200, 0.08);
border-top: 1px solid rgba(73, 208, 200, 0.08);
padding-top: 0.78rem;
padding-bottom: 0.78rem;
vertical-align: middle;
}
.results tbody a,
.table tbody a,
#result_list tbody a {
color: #86e2dc;
font-weight: 600;
}
.results tbody a:hover,
.table tbody a:hover,
#result_list tbody a:hover {
color: #c9f8ff;
}
.results .action-checkbox-column,
#result_list .action-checkbox-column {
background: transparent;
width: 2.8rem;
}
input[type="text"],
input[type="password"],
input[type="email"],
@@ -414,6 +596,288 @@ a.button:hover,
cursor: pointer;
}
.mx-admin-action-bar {
display: flex;
flex-wrap: wrap;
gap: 0.9rem;
margin: 0 0 1rem;
padding: 0;
background: transparent;
border: 0;
box-shadow: none;
backdrop-filter: none;
}
.mx-admin-action-bar__link {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 14rem;
min-height: 2.7rem;
padding: 0.72rem 1rem;
border: 1px solid rgba(73, 208, 200, 0.14);
border-radius: 10px;
color: #f5fbff;
font-weight: 600;
line-height: 1.2;
text-align: center;
text-decoration: none;
box-shadow: 0 8px 18px rgba(2, 10, 13, 0.16);
transition:
transform 0.16s ease,
filter 0.16s ease,
border-color 0.16s ease;
}
.mx-admin-action-bar__link:hover {
color: #ffffff;
filter: brightness(1.06);
transform: translateY(-1px);
}
.mx-admin-action-bar__link--primary {
background: rgba(73, 208, 200, 0.14);
border-color: rgba(73, 208, 200, 0.22);
}
.mx-admin-action-bar__link--secondary {
background: rgba(255, 255, 255, 0.03);
}
.mx-admin-action-bar__link--ghost {
background: rgba(91, 127, 177, 0.12);
border-color: rgba(255, 255, 255, 0.12);
}
.mx-admin-page {
display: grid;
gap: 1rem;
max-width: 1240px;
}
.mx-admin-page__intro {
margin: 0;
color: var(--mx-text-muted);
line-height: 1.55;
}
.mx-admin-page__section {
overflow: hidden;
}
.mx-admin-page__section-title {
padding: 0.9rem 1.1rem;
background: linear-gradient(90deg, rgba(10, 34, 40, 0.96), rgba(12, 40, 47, 0.94));
border-bottom: 1px solid rgba(73, 208, 200, 0.12);
color: #f6f9ff;
font-size: 0.98rem;
font-weight: 700;
letter-spacing: 0.02em;
line-height: 1.35;
text-transform: none;
}
.mx-admin-page__section-body {
padding: 1rem 1.1rem 1.1rem;
}
.mx-admin-page__section-body--table {
padding: 0;
}
.mx-admin-page__connection {
display: grid;
gap: 0.3rem;
}
.mx-admin-page__connection strong {
font-size: 1rem;
}
.mx-admin-page__meta {
color: var(--mx-text-muted);
font-size: 0.9rem;
line-height: 1.45;
}
.mx-admin-page__table-wrap {
overflow-x: auto;
}
.mx-admin-page__table {
width: 100%;
}
.mx-admin-page__table td,
.mx-admin-page__table th {
white-space: normal;
}
.mx-admin-page__table td:last-child {
width: 1%;
white-space: nowrap;
}
.mx-admin-page__empty-cell {
padding: 1rem 1.1rem !important;
color: var(--mx-text-muted);
}
.mx-admin-inline-actions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.mx-admin-inline-actions__link {
display: inline-flex;
align-items: center;
justify-content: center;
min-height: 2rem;
padding: 0.42rem 0.75rem;
background: rgba(73, 208, 200, 0.1);
border: 1px solid rgba(73, 208, 200, 0.16);
border-radius: 8px;
color: #dff9f6;
font-size: 0.84rem;
font-weight: 600;
line-height: 1.2;
text-decoration: none;
}
.mx-admin-inline-actions__link:hover {
color: #ffffff;
background: rgba(73, 208, 200, 0.16);
}
.mx-admin-inline-actions__link--ghost {
background: rgba(255, 255, 255, 0.04);
border-color: rgba(255, 255, 255, 0.12);
}
.mx-admin-status-badge {
display: inline-flex;
align-items: center;
min-height: 1.55rem;
padding: 0.18rem 0.55rem;
border-radius: 999px;
font-size: 0.78rem;
font-weight: 700;
letter-spacing: 0.01em;
}
.mx-admin-status-badge--success {
background: rgba(45, 212, 191, 0.16);
border: 1px solid rgba(45, 212, 191, 0.24);
color: #d5fffb;
}
.mx-admin-status-badge--muted {
background: rgba(255, 255, 255, 0.06);
border: 1px solid rgba(255, 255, 255, 0.11);
color: var(--mx-text-muted);
}
.mx-admin-page__form {
display: grid;
gap: 1rem;
}
.mx-admin-page__form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 1rem 1.1rem;
}
.mx-admin-page__field {
min-width: 0;
}
.mx-admin-page__field--full {
grid-column: 1 / -1;
}
.mx-admin-page__field label {
display: block;
margin: 0 0 0.45rem;
color: #d8e7ff;
font-size: 0.9rem;
font-weight: 600;
line-height: 1.35;
}
.mx-admin-page__field input[type="text"],
.mx-admin-page__field input[type="password"],
.mx-admin-page__field input[type="email"],
.mx-admin-page__field input[type="number"],
.mx-admin-page__field input[type="url"],
.mx-admin-page__field input[type="search"],
.mx-admin-page__field input[type="date"],
.mx-admin-page__field select,
.mx-admin-page__field textarea {
width: 100%;
}
.mx-admin-page__field select[multiple] {
min-height: 13rem;
padding-top: 0.4rem;
padding-bottom: 0.4rem;
}
.mx-admin-page__field .help {
margin-top: 0.45rem;
}
.mx-admin-page__field .errorlist {
margin: 0 0 0.5rem;
padding-left: 1rem;
}
.mx-admin-page__field.errors input,
.mx-admin-page__field.errors select,
.mx-admin-page__field.errors textarea {
border-color: rgba(255, 107, 159, 0.4);
}
.mx-admin-page__field--checkbox {
display: flex;
align-items: center;
min-height: 2.7rem;
}
.mx-admin-page__checkbox {
display: inline-flex !important;
align-items: center;
gap: 0.6rem;
margin: 0;
}
.mx-admin-page__checkbox input[type="checkbox"] {
width: 1rem;
height: 1rem;
margin: 0;
}
.mx-admin-page__checkbox span {
color: #d8e7ff;
font-size: 0.9rem;
font-weight: 600;
}
.submit-row.mx-admin-page__actions {
display: flex;
flex-wrap: wrap;
gap: 0.75rem;
align-items: center;
justify-content: flex-start;
padding: 1rem 1.1rem;
}
.submit-row.mx-admin-page__actions .button,
.submit-row.mx-admin-page__actions input[type="submit"] {
margin: 0;
}
.deletelink,
.btn-danger {
background: linear-gradient(135deg, rgba(255, 107, 159, 0.82), rgba(167, 53, 118, 0.88));
@@ -591,6 +1055,31 @@ code {
}
}
@media (min-width: 992px) {
body.sidebar-mini:not(.sidebar-collapse) .main-sidebar,
body.sidebar-mini:not(.sidebar-collapse) .main-sidebar::before,
body.sidebar-mini-md:not(.sidebar-collapse) .main-sidebar,
body.sidebar-mini-md:not(.sidebar-collapse) .main-sidebar::before {
width: var(--mx-sidebar-width) !important;
}
body.sidebar-mini:not(.sidebar-collapse) .content-wrapper,
body.sidebar-mini:not(.sidebar-collapse) .main-footer,
body.sidebar-mini:not(.sidebar-collapse) .main-header,
body.sidebar-mini-md:not(.sidebar-collapse) .content-wrapper,
body.sidebar-mini-md:not(.sidebar-collapse) .main-footer,
body.sidebar-mini-md:not(.sidebar-collapse) .main-header {
margin-left: var(--mx-sidebar-width) !important;
}
body.sidebar-mini.sidebar-collapse .main-sidebar,
body.sidebar-mini.sidebar-collapse .main-sidebar::before,
body.sidebar-mini-md.sidebar-collapse .main-sidebar,
body.sidebar-mini-md.sidebar-collapse .main-sidebar::before {
width: 4.6rem !important;
}
}
@media (max-width: 991.98px) {
.dashboard .admin-dashboard-grid {
padding-right: 0.15rem;
@@ -628,4 +1117,17 @@ code {
.main-header .form-control-navbar {
width: 100%;
}
.mx-admin-page__form-grid {
grid-template-columns: 1fr;
}
.mx-admin-page__field--full {
grid-column: auto;
}
.mx-admin-action-bar__link {
width: 100%;
min-width: 0;
}
}

View File

@@ -0,0 +1,153 @@
(function () {
const SIDEBAR_SELECTOR = ".main-sidebar";
const CONTAINER_ID = "mx-sidebar-particles";
const DESKTOP_MEDIA_QUERY =
"(min-width: 992px) and (prefers-reduced-motion: no-preference)";
let particlesLoaderPromise = null;
function getVendorUrl() {
const currentScript = document.currentScript;
if (currentScript && currentScript.src) {
return new URL("./vendor/particles.min.js", currentScript.src).href;
}
return "/static/admin/js/vendor/particles.min.js";
}
function loadParticlesLibrary() {
if (window.particlesJS) {
return Promise.resolve();
}
if (particlesLoaderPromise) {
return particlesLoaderPromise;
}
particlesLoaderPromise = new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = getVendorUrl();
script.async = true;
script.onload = resolve;
script.onerror = reject;
document.head.appendChild(script);
});
return particlesLoaderPromise;
}
function shouldEnableParticles() {
return window.matchMedia(DESKTOP_MEDIA_QUERY).matches;
}
function ensureContainer(sidebar) {
let container = document.getElementById(CONTAINER_ID);
if (container) {
return container;
}
container = document.createElement("div");
container.id = CONTAINER_ID;
container.setAttribute("aria-hidden", "true");
sidebar.prepend(container);
return container;
}
function initSidebarParticles() {
const sidebar = document.querySelector(SIDEBAR_SELECTOR);
if (!sidebar || !shouldEnableParticles() || document.getElementById(CONTAINER_ID)) {
return;
}
loadParticlesLibrary()
.then(() => {
if (!window.particlesJS || document.getElementById(CONTAINER_ID)) {
return;
}
ensureContainer(sidebar);
window.particlesJS(CONTAINER_ID, {
particles: {
number: {
value: 52,
density: {
enable: true,
value_area: 900,
},
},
color: {
value: ["#49d0c8", "#8cefeb", "#dffdfb", "#8ba8ff"],
},
shape: {
type: "circle",
},
opacity: {
value: 0.52,
random: true,
anim: {
enable: true,
speed: 0.7,
opacity_min: 0.18,
sync: false,
},
},
size: {
value: 3.8,
random: true,
anim: {
enable: true,
speed: 2.2,
size_min: 1,
sync: false,
},
},
line_linked: {
enable: true,
distance: 142,
color: "#7be8df",
opacity: 0.2,
width: 1.15,
},
move: {
enable: true,
speed: 1.28,
direction: "none",
random: true,
straight: false,
out_mode: "out",
bounce: false,
},
},
interactivity: {
detect_on: "canvas",
events: {
onhover: {
enable: false,
},
onclick: {
enable: false,
},
resize: true,
},
},
retina_detect: true,
});
})
.catch(() => {
// Silently skip the effect if the vendor script fails to load.
});
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initSidebarParticles, { once: true });
} else {
initSidebarParticles();
}
})();

File diff suppressed because one or more lines are too long