Большое обновление GUI интерфейс
Большое обновление GUI интерфейс - Добавлен фраемворr Walles - Удалена консольная версия - Проработан интерфейс и дизайн - Добавлено кеширование для быстрой реакции. - Сделан .ps1 сборщик для удобной сборки проекта. - Обновлён Readme
This commit is contained in:
103
Backend/admin/frontend/assets/css/base.css
Normal file
103
Backend/admin/frontend/assets/css/base.css
Normal file
@@ -0,0 +1,103 @@
|
||||
/* ============================================
|
||||
Base Styles & Reset
|
||||
Базовые стили приложения
|
||||
============================================ */
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
/* Градиентный фон с размытием */
|
||||
&::before {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: -50%;
|
||||
left: -50%;
|
||||
width: 200%;
|
||||
height: 200%;
|
||||
/* background: radial-gradient(circle at 80% 80%, rgba(138, 92, 246, 0.116) 0%, transparent 50%); */
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
code {
|
||||
background: rgba(139, 92, 246, 0.15);
|
||||
backdrop-filter: var(--backdrop-blur-light);
|
||||
color: var(--accent-purple-light);
|
||||
padding: 3px 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-family: var(--font-mono);
|
||||
font-size: var(--text-sm);
|
||||
border: 1px solid var(--glass-border);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
/* Scrollbar Styling */
|
||||
::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: rgba(139, 92, 246, 0.05);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: rgba(139, 92, 246, 0.3);
|
||||
border-radius: var(--radius-sm);
|
||||
|
||||
&:hover {
|
||||
background: rgba(139, 92, 246, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bounce {
|
||||
0%, 100% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
50% {
|
||||
transform: translateY(-20px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Utility Classes */
|
||||
.hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.fade-in {
|
||||
animation: fadeIn var(--transition-slow);
|
||||
}
|
||||
|
||||
87
Backend/admin/frontend/assets/css/components/badges.css
Normal file
87
Backend/admin/frontend/assets/css/components/badges.css
Normal file
@@ -0,0 +1,87 @@
|
||||
/* ============================================
|
||||
Badges Component
|
||||
Единая система бейджей
|
||||
============================================ */
|
||||
|
||||
/* Base Badge */
|
||||
.badge {
|
||||
padding: 4px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: var(--text-xs);
|
||||
font-weight: var(--font-bold);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
backdrop-filter: var(--backdrop-blur-light);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Status Badges */
|
||||
.badge-online {
|
||||
background: linear-gradient(135deg, rgba(16, 185, 129, 0.25), rgba(16, 185, 129, 0.15));
|
||||
color: var(--accent-green);
|
||||
border: 1px solid rgba(16, 185, 129, 0.4);
|
||||
box-shadow: 0 0 12px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.badge-offline {
|
||||
background: linear-gradient(135deg, rgba(239, 68, 68, 0.25), rgba(239, 68, 68, 0.15));
|
||||
color: var(--accent-red);
|
||||
border: 1px solid rgba(239, 68, 68, 0.4);
|
||||
box-shadow: 0 0 12px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.badge-pending {
|
||||
background: linear-gradient(135deg, rgba(245, 158, 11, 0.25), rgba(245, 158, 11, 0.15));
|
||||
color: var(--accent-yellow);
|
||||
border: 1px solid rgba(245, 158, 11, 0.4);
|
||||
box-shadow: 0 0 12px rgba(245, 158, 11, 0.3);
|
||||
}
|
||||
|
||||
/* Yes/No Badges */
|
||||
.badge-yes {
|
||||
background: rgba(34, 197, 94, 0.2);
|
||||
color: var(--accent-green);
|
||||
border: 1px solid var(--accent-green);
|
||||
}
|
||||
|
||||
.badge-no {
|
||||
background: rgba(100, 116, 139, 0.2);
|
||||
color: var(--text-muted);
|
||||
border: 1px solid var(--text-muted);
|
||||
}
|
||||
|
||||
/* Status Indicator (Dot) */
|
||||
.status-indicator {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: var(--radius-full);
|
||||
box-shadow: 0 0 8px currentColor;
|
||||
}
|
||||
|
||||
.status-online {
|
||||
background: var(--accent-green);
|
||||
color: var(--accent-green);
|
||||
}
|
||||
|
||||
.status-offline {
|
||||
background: var(--accent-red);
|
||||
color: var(--accent-red);
|
||||
}
|
||||
|
||||
/* Mini Tags (для таблиц) */
|
||||
.mini-tag {
|
||||
display: inline-block;
|
||||
padding: 4px 10px;
|
||||
background: rgba(139, 92, 246, 0.15);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: 12px;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--accent-purple-light);
|
||||
margin: 2px;
|
||||
transition: all var(--transition-base);
|
||||
|
||||
&:hover {
|
||||
background: rgba(139, 92, 246, 0.25);
|
||||
}
|
||||
}
|
||||
|
||||
308
Backend/admin/frontend/assets/css/components/buttons.css
Normal file
308
Backend/admin/frontend/assets/css/components/buttons.css
Normal file
@@ -0,0 +1,308 @@
|
||||
/* ============================================
|
||||
Buttons Component
|
||||
Единая система кнопок
|
||||
============================================ */
|
||||
|
||||
/* Base Button Styles */
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
white-space: nowrap;
|
||||
|
||||
&:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
&:not(:disabled):active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
/* Action Button - Основная кнопка действия */
|
||||
.action-btn {
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
background: rgba(139, 92, 246, 0.15);
|
||||
backdrop-filter: var(--backdrop-blur-light);
|
||||
border: 1px solid rgba(139, 92, 246, 0.3);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--accent-purple-light);
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: rgba(139, 92, 246, 0.25);
|
||||
border-color: rgba(139, 92, 246, 0.5);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
}
|
||||
|
||||
/* Save Button Variant */
|
||||
.save-btn {
|
||||
background: rgba(16, 185, 129, 0.15);
|
||||
border-color: rgba(16, 185, 129, 0.3);
|
||||
color: var(--accent-green);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: rgba(16, 185, 129, 0.25);
|
||||
border-color: rgba(16, 185, 129, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
/* Icon Button - Квадратная кнопка с иконкой */
|
||||
.icon-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 0;
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
border: 1px solid rgba(139, 92, 246, 0.3);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--accent-purple-light);
|
||||
font-size: var(--text-md);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: rgba(139, 92, 246, 0.25);
|
||||
border-color: rgba(139, 92, 246, 0.5);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
}
|
||||
|
||||
/* Small Icon Button */
|
||||
.icon-btn-small {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: rgba(239, 68, 68, 0.1);
|
||||
border: 1px solid rgba(239, 68, 68, 0.3);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--accent-red);
|
||||
font-size: 12px;
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* Window Control Buttons */
|
||||
.window-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: all var(--transition-base);
|
||||
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn:hover {
|
||||
background: var(--accent-red);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Server Control Button */
|
||||
.server-control-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-sm) 18px;
|
||||
background: linear-gradient(135deg, rgba(239, 68, 68, 0.2), rgba(239, 68, 68, 0.1));
|
||||
backdrop-filter: var(--backdrop-blur-light);
|
||||
border: 1px solid rgba(239, 68, 68, 0.4);
|
||||
border-radius: 20px;
|
||||
color: var(--accent-red);
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
box-shadow: var(--shadow-red);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: linear-gradient(135deg, rgba(239, 68, 68, 0.3), rgba(239, 68, 68, 0.15));
|
||||
border-color: rgba(239, 68, 68, 0.6);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
i {
|
||||
font-size: var(--text-md);
|
||||
}
|
||||
}
|
||||
|
||||
.server-control-btn.start-mode {
|
||||
background: linear-gradient(135deg, rgba(16, 185, 129, 0.2), rgba(16, 185, 129, 0.1));
|
||||
border-color: rgba(16, 185, 129, 0.4);
|
||||
color: var(--accent-green);
|
||||
box-shadow: var(--shadow-green);
|
||||
|
||||
&:hover:not(:disabled) {
|
||||
background: linear-gradient(135deg, rgba(16, 185, 129, 0.3), rgba(16, 185, 129, 0.15));
|
||||
border-color: rgba(16, 185, 129, 0.6);
|
||||
}
|
||||
}
|
||||
|
||||
/* Status Toggle Buttons */
|
||||
.status-toggle {
|
||||
display: flex;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.status-btn {
|
||||
flex: 1;
|
||||
padding: 10px var(--space-md);
|
||||
background: rgba(100, 116, 139, 0.1);
|
||||
border: 1px solid rgba(100, 116, 139, 0.3);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: var(--space-sm);
|
||||
|
||||
&:hover {
|
||||
background: rgba(100, 116, 139, 0.15);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: rgba(16, 185, 129, 0.2);
|
||||
border-color: rgba(16, 185, 129, 0.5);
|
||||
color: var(--accent-green);
|
||||
box-shadow: 0 0 12px rgba(16, 185, 129, 0.2);
|
||||
}
|
||||
|
||||
&:last-child.active {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
border-color: rgba(239, 68, 68, 0.5);
|
||||
color: var(--accent-red);
|
||||
}
|
||||
}
|
||||
|
||||
/* Navigation Buttons */
|
||||
.nav-item {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: var(--radius-lg);
|
||||
color: var(--text-secondary);
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
position: relative;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -16px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 3px;
|
||||
height: 0;
|
||||
background: linear-gradient(180deg, var(--accent-purple), var(--accent-purple-light));
|
||||
border-radius: 0 2px 2px 0;
|
||||
transition: height var(--transition-base);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
color: var(--accent-purple-light);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: rgba(139, 92, 246, 0.15);
|
||||
color: var(--accent-purple-light);
|
||||
|
||||
&::before {
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Breadcrumb Buttons */
|
||||
.breadcrumb-item {
|
||||
font-size: var(--text-md);
|
||||
color: var(--text-muted);
|
||||
background: none;
|
||||
border: none;
|
||||
padding: var(--space-sm) var(--space-lg);
|
||||
border-radius: var(--radius-md);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
|
||||
&:hover {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
color: var(--accent-purple-light);
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: var(--text-primary);
|
||||
font-weight: var(--font-medium);
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tab Buttons */
|
||||
.vaccess-tab {
|
||||
flex: 0 0 auto;
|
||||
padding: 10px 18px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--text-muted);
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-medium);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
|
||||
&:hover {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: var(--accent-purple);
|
||||
color: white;
|
||||
box-shadow: 0 4px 12px rgba(139, 92, 246, 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
191
Backend/admin/frontend/assets/css/components/cards.css
Normal file
191
Backend/admin/frontend/assets/css/components/cards.css
Normal file
@@ -0,0 +1,191 @@
|
||||
/* ============================================
|
||||
Cards Component
|
||||
Единая система карточек
|
||||
============================================ */
|
||||
|
||||
/* Service Card */
|
||||
.service-card {
|
||||
background: var(--glass-bg-light);
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-2xl);
|
||||
padding: var(--space-lg);
|
||||
transition: all var(--transition-bounce);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
/* Градиентная линия сверху */
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: linear-gradient(90deg, var(--accent-purple), var(--accent-purple-light), var(--accent-cyan));
|
||||
opacity: 0;
|
||||
transition: opacity var(--transition-slow);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
box-shadow: var(--shadow-purple);
|
||||
border-color: var(--glass-border-hover);
|
||||
background: rgba(20, 20, 40, 0.5);
|
||||
|
||||
&::before {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.service-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.service-name {
|
||||
font-size: var(--text-md);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
|
||||
i {
|
||||
color: var(--accent-purple-light);
|
||||
font-size: var(--text-lg);
|
||||
}
|
||||
}
|
||||
|
||||
.service-info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-secondary);
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 12px;
|
||||
color: var(--text-primary);
|
||||
font-weight: var(--font-semibold);
|
||||
}
|
||||
|
||||
/* Settings Card */
|
||||
.settings-card {
|
||||
background: var(--glass-bg-light);
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-xl);
|
||||
padding: var(--space-lg);
|
||||
}
|
||||
|
||||
.settings-card-title {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
|
||||
i {
|
||||
color: var(--accent-purple-light);
|
||||
}
|
||||
}
|
||||
|
||||
/* vAccess Rule Card */
|
||||
.vaccess-rule-card {
|
||||
background: rgba(10, 14, 26, 0.4);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-lg);
|
||||
padding: var(--space-lg);
|
||||
}
|
||||
|
||||
.rule-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-md);
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.rule-number {
|
||||
font-size: var(--text-md);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--accent-purple-light);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.rule-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
|
||||
> .form-group:first-child {
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Help Cards */
|
||||
.help-card {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-radius: var(--radius-xl);
|
||||
padding: var(--space-xl);
|
||||
border: 1px solid var(--glass-border);
|
||||
transition: all var(--transition-slow);
|
||||
|
||||
&:hover {
|
||||
border-color: var(--glass-border-hover);
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--accent-purple-light);
|
||||
margin: 0 0 20px 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
padding: 12px 0;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
border-bottom: 1px solid rgba(139, 92, 246, 0.05);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.help-examples {
|
||||
background: linear-gradient(135deg, rgba(139, 92, 246, 0.05) 0%, rgba(99, 102, 241, 0.05) 100%);
|
||||
}
|
||||
|
||||
219
Backend/admin/frontend/assets/css/components/forms.css
Normal file
219
Backend/admin/frontend/assets/css/components/forms.css
Normal file
@@ -0,0 +1,219 @@
|
||||
/* ============================================
|
||||
Forms Component
|
||||
Единая система форм
|
||||
============================================ */
|
||||
|
||||
/* Form Container */
|
||||
.settings-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
/* Form Group */
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
/* Form Row (2 columns) */
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
/* Form Label */
|
||||
.form-label {
|
||||
font-size: 12px;
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.field-hint {
|
||||
font-size: var(--text-xs);
|
||||
color: var(--text-muted);
|
||||
font-weight: var(--font-normal);
|
||||
text-transform: none;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
/* Form Input */
|
||||
.form-input {
|
||||
padding: 10px 14px;
|
||||
background: var(--glass-bg-dark);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--text-primary);
|
||||
font-size: var(--text-base);
|
||||
outline: none;
|
||||
transition: all var(--transition-base);
|
||||
|
||||
&:focus {
|
||||
border-color: rgba(139, 92, 246, 0.5);
|
||||
box-shadow: 0 0 12px rgba(139, 92, 246, 0.2);
|
||||
}
|
||||
|
||||
&::placeholder {
|
||||
color: var(--text-muted);
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
/* Form Info */
|
||||
.form-info {
|
||||
font-size: var(--text-sm);
|
||||
color: var(--text-muted);
|
||||
line-height: 1.5;
|
||||
padding: 12px;
|
||||
background: rgba(139, 92, 246, 0.05);
|
||||
border-radius: var(--radius-md);
|
||||
border-left: 3px solid var(--accent-purple);
|
||||
}
|
||||
|
||||
/* Toggle Switch */
|
||||
.toggle-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.toggle-switch {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 26px;
|
||||
|
||||
input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
|
||||
&:checked + .toggle-slider {
|
||||
background: rgba(16, 185, 129, 0.2);
|
||||
border-color: rgba(16, 185, 129, 0.5);
|
||||
box-shadow: 0 0 16px rgba(16, 185, 129, 0.3);
|
||||
|
||||
&::before {
|
||||
transform: translateX(24px);
|
||||
background: var(--accent-green);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
border: 1px solid rgba(239, 68, 68, 0.4);
|
||||
transition: all var(--transition-slow);
|
||||
border-radius: 26px;
|
||||
|
||||
&::before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background: rgba(239, 68, 68, 0.8);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: all var(--transition-slow);
|
||||
border-radius: var(--radius-full);
|
||||
}
|
||||
}
|
||||
|
||||
.toggle-label {
|
||||
font-size: var(--text-md);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Checkbox */
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
cursor: pointer;
|
||||
font-size: var(--text-base);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
/* Tags Container */
|
||||
.tags-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--space-sm);
|
||||
padding: 12px;
|
||||
background: var(--glass-bg-dark);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-md);
|
||||
min-height: 48px;
|
||||
margin-top: var(--space-sm);
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
padding: 4px 10px;
|
||||
background: rgba(139, 92, 246, 0.2);
|
||||
border: 1px solid rgba(139, 92, 246, 0.4);
|
||||
border-radius: 16px;
|
||||
color: var(--text-primary);
|
||||
font-size: 12px;
|
||||
font-weight: var(--font-medium);
|
||||
}
|
||||
|
||||
.tag-remove {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: var(--accent-red);
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: var(--radius-full);
|
||||
transition: all var(--transition-base);
|
||||
|
||||
&:hover {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
}
|
||||
}
|
||||
|
||||
.tag-input-wrapper {
|
||||
display: flex;
|
||||
gap: var(--space-sm);
|
||||
|
||||
.form-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Field Editor */
|
||||
.field-editor {
|
||||
padding: 20px;
|
||||
|
||||
h3 {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--accent-purple-light);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
223
Backend/admin/frontend/assets/css/components/modals.css
Normal file
223
Backend/admin/frontend/assets/css/components/modals.css
Normal file
@@ -0,0 +1,223 @@
|
||||
/* ============================================
|
||||
Modals Component
|
||||
Единая система модальных окон
|
||||
============================================ */
|
||||
|
||||
/* Modal Overlay */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: var(--backdrop-blur-light);
|
||||
z-index: var(--z-modal);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity var(--transition-slow);
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
|
||||
.modal-window {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal Window */
|
||||
.modal-window {
|
||||
background: rgba(20, 20, 40, 0.95);
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border: 1px solid var(--glass-border-hover);
|
||||
border-radius: var(--radius-2xl);
|
||||
box-shadow: var(--shadow-lg);
|
||||
min-width: 900px;
|
||||
max-width: 1200px;
|
||||
max-height: 85vh;
|
||||
overflow: hidden;
|
||||
transform: scale(0.9);
|
||||
transition: transform var(--transition-slow);
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px var(--space-lg);
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.modal-title {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.modal-close-btn {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
border-radius: var(--radius-md);
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&:hover {
|
||||
background: rgba(239, 68, 68, 0.2);
|
||||
color: var(--accent-red);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
padding: var(--space-lg);
|
||||
max-height: 60vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: var(--space-lg);
|
||||
padding: 20px var(--space-lg);
|
||||
border-top: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
/* Notification Modal */
|
||||
.notification {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
backdrop-filter: var(--backdrop-blur-light);
|
||||
z-index: var(--z-notification);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity var(--transition-slow);
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
min-width: 400px;
|
||||
max-width: 500px;
|
||||
padding: var(--space-xl) 40px;
|
||||
background: rgba(20, 20, 40, 0.95);
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border: 1px solid var(--glass-border-hover);
|
||||
border-radius: var(--radius-2xl);
|
||||
box-shadow: var(--shadow-lg), 0 0 40px rgba(139, 92, 246, 0.3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
text-align: center;
|
||||
transform: scale(0.9);
|
||||
transition: transform var(--transition-bounce);
|
||||
}
|
||||
|
||||
.notification.success .notification-content {
|
||||
border-color: rgba(16, 185, 129, 0.4);
|
||||
box-shadow: var(--shadow-lg), 0 0 40px rgba(16, 185, 129, 0.3);
|
||||
}
|
||||
|
||||
.notification.error .notification-content {
|
||||
border-color: rgba(239, 68, 68, 0.4);
|
||||
box-shadow: var(--shadow-lg), 0 0 40px rgba(239, 68, 68, 0.3);
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
font-size: 56px;
|
||||
|
||||
i {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.notification.success .notification-icon {
|
||||
color: var(--accent-green);
|
||||
}
|
||||
|
||||
.notification.error .notification-icon {
|
||||
color: var(--accent-red);
|
||||
}
|
||||
|
||||
.notification-text {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* App Loader */
|
||||
.app-loader {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(135deg, var(--bg-primary) 0%, var(--bg-secondary) 50%, var(--bg-tertiary) 100%);
|
||||
z-index: var(--z-loader);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 1;
|
||||
transition: opacity 0.5s ease;
|
||||
|
||||
&.hide {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.loader-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.loader-icon {
|
||||
font-size: 64px;
|
||||
animation: bounce 1.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.loader-text {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-semibold);
|
||||
background: linear-gradient(135deg, #7c3aed 0%, #a78bfa 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
|
||||
.loader-spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 3px solid rgba(139, 92, 246, 0.2);
|
||||
border-top-color: var(--accent-purple);
|
||||
border-radius: var(--radius-full);
|
||||
animation: spin 0.8s linear infinite;
|
||||
}
|
||||
|
||||
236
Backend/admin/frontend/assets/css/components/tables.css
Normal file
236
Backend/admin/frontend/assets/css/components/tables.css
Normal file
@@ -0,0 +1,236 @@
|
||||
/* ============================================
|
||||
Tables Component
|
||||
ЕДИНАЯ система таблиц для всего приложения
|
||||
============================================ */
|
||||
|
||||
/* Table Container */
|
||||
.table-container {
|
||||
background: var(--glass-bg-light);
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border: 1px solid var(--glass-border);
|
||||
border-radius: var(--radius-2xl);
|
||||
overflow: hidden;
|
||||
box-shadow: var(--shadow-md);
|
||||
}
|
||||
|
||||
/* Base Table */
|
||||
.data-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
thead {
|
||||
background: rgba(139, 92, 246, 0.12);
|
||||
backdrop-filter: var(--backdrop-blur-light);
|
||||
|
||||
tr {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 18px 20px;
|
||||
text-align: left;
|
||||
font-size: var(--text-sm);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--text-secondary);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.2px;
|
||||
|
||||
&:last-child {
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
transition: all var(--transition-base);
|
||||
|
||||
&:hover {
|
||||
background: rgba(139, 92, 246, 0.08);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 16px 20px;
|
||||
font-size: var(--text-base);
|
||||
color: var(--text-primary);
|
||||
|
||||
&:last-child {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* vAccess Table - использует те же стили что и data-table */
|
||||
.vaccess-table-container {
|
||||
margin-bottom: 20px;
|
||||
max-height: 55vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.vaccess-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
|
||||
thead {
|
||||
tr {
|
||||
background: rgba(139, 92, 246, 0.05);
|
||||
display: table-row;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
th {
|
||||
padding: 16px;
|
||||
text-align: left;
|
||||
font-size: var(--text-base);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
border: none;
|
||||
display: table-cell;
|
||||
box-sizing: content-box;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
transition: all var(--transition-slow);
|
||||
cursor: grab;
|
||||
display: table-row;
|
||||
width: 100%;
|
||||
|
||||
&:hover {
|
||||
background: rgba(139, 92, 246, 0.08);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
&[draggable="true"] {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
td {
|
||||
padding: 20px 16px;
|
||||
font-size: var(--text-md);
|
||||
color: var(--text-primary);
|
||||
border-top: 1px solid rgba(139, 92, 246, 0.05);
|
||||
border-bottom: 1px solid rgba(139, 92, 246, 0.05);
|
||||
cursor: pointer;
|
||||
display: table-cell;
|
||||
box-sizing: content-box;
|
||||
|
||||
}
|
||||
|
||||
/* Принудительно растягиваем на всю ширину */
|
||||
thead, tbody {
|
||||
display: table-row-group;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Table Column Sizing - адаптивные размеры */
|
||||
.col-drag {
|
||||
width: 3%;
|
||||
min-width: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.col-type {
|
||||
width: 8%;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.col-files {
|
||||
width: 15%;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.col-paths {
|
||||
width: 18%;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.col-ips {
|
||||
width: 15%;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.col-exceptions {
|
||||
width: 15%;
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.col-error {
|
||||
width: 10%;
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.col-actions {
|
||||
width: 5%;
|
||||
min-width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Drag Handle */
|
||||
.drag-handle {
|
||||
color: var(--text-muted);
|
||||
opacity: 0.3;
|
||||
transition: all var(--transition-base);
|
||||
cursor: grab;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
opacity: 1;
|
||||
color: var(--accent-purple-light);
|
||||
}
|
||||
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
|
||||
/* Empty Field */
|
||||
.empty-field {
|
||||
color: var(--text-muted);
|
||||
opacity: 0.4;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* Clickable Link in Tables */
|
||||
.clickable-link {
|
||||
color: var(--accent-purple-light);
|
||||
cursor: pointer;
|
||||
transition: all var(--transition-base);
|
||||
text-decoration: none;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: var(--space-xs);
|
||||
|
||||
&:hover {
|
||||
color: var(--accent-purple);
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
/* Responsive Table */
|
||||
@media (max-width: 600px) {
|
||||
.table-container {
|
||||
overflow-x: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
66
Backend/admin/frontend/assets/css/layout/container.css
Normal file
66
Backend/admin/frontend/assets/css/layout/container.css
Normal file
@@ -0,0 +1,66 @@
|
||||
/* ============================================
|
||||
Container Layout
|
||||
Основной контейнер контента
|
||||
============================================ */
|
||||
|
||||
.container {
|
||||
height: calc(100vh - var(--header-height));
|
||||
margin-top: var(--header-height);
|
||||
margin-left: var(--sidebar-width);
|
||||
padding: 40px var(--space-3xl);
|
||||
position: relative;
|
||||
z-index: var(--z-base);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Main Content */
|
||||
.main-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-2xl);
|
||||
}
|
||||
|
||||
/* Section */
|
||||
.section {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: var(--text-md);
|
||||
font-weight: var(--font-bold);
|
||||
color: var(--text-primary);
|
||||
margin-bottom: var(--space-lg);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 1.5px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-lg);
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 16px;
|
||||
background: linear-gradient(180deg, var(--accent-purple), var(--accent-purple-light));
|
||||
border-radius: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
margin-top: var(--space-3xl);
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: var(--text-sm);
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 600px) {
|
||||
.header {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
76
Backend/admin/frontend/assets/css/layout/header.css
Normal file
76
Backend/admin/frontend/assets/css/layout/header.css
Normal file
@@ -0,0 +1,76 @@
|
||||
/* ============================================
|
||||
Header Layout
|
||||
Window controls и title bar
|
||||
============================================ */
|
||||
|
||||
.window-controls {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
background: rgba(10, 14, 26, 0.9);
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border-bottom: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.title-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: var(--header-height);
|
||||
padding: 0 var(--space-xl);
|
||||
--wails-draggable: drag;
|
||||
}
|
||||
|
||||
.title-bar-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.app-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.logo-icon {
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-size: var(--text-xl);
|
||||
font-weight: var(--font-bold);
|
||||
color: #ffffff;
|
||||
user-select: none;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.title-bar-right {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-md);
|
||||
--wails-draggable: no-drag;
|
||||
}
|
||||
|
||||
/* Server Status */
|
||||
.server-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
background: linear-gradient(135deg, rgba(16, 185, 129, 0.15), rgba(16, 185, 129, 0.05));
|
||||
backdrop-filter: var(--backdrop-blur-light);
|
||||
border-radius: 20px;
|
||||
border: 1px solid rgba(16, 185, 129, 0.3);
|
||||
box-shadow: var(--shadow-green);
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-size: 12px;
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
27
Backend/admin/frontend/assets/css/layout/sidebar.css
Normal file
27
Backend/admin/frontend/assets/css/layout/sidebar.css
Normal file
@@ -0,0 +1,27 @@
|
||||
/* ============================================
|
||||
Sidebar Layout
|
||||
Боковая навигация
|
||||
============================================ */
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: var(--header-height);
|
||||
width: var(--sidebar-width);
|
||||
height: calc(100vh - var(--header-height));
|
||||
background: rgba(10, 14, 26, 0.95);
|
||||
backdrop-filter: var(--backdrop-blur);
|
||||
border-right: 1px solid var(--glass-border);
|
||||
z-index: 100;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.sidebar-nav {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
padding: 0 var(--space-md);
|
||||
}
|
||||
|
||||
29
Backend/admin/frontend/assets/css/local_lib/all.min.css
vendored
Normal file
29
Backend/admin/frontend/assets/css/local_lib/all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
26
Backend/admin/frontend/assets/css/main.css
Normal file
26
Backend/admin/frontend/assets/css/main.css
Normal file
@@ -0,0 +1,26 @@
|
||||
/* ============================================
|
||||
vServer Admin Panel - Main CSS
|
||||
Профессиональная модульная архитектура
|
||||
============================================ */
|
||||
|
||||
/* 1. Variables & Base */
|
||||
@import 'variables.css';
|
||||
@import 'base.css';
|
||||
|
||||
/* 2. Components */
|
||||
@import 'components/buttons.css';
|
||||
@import 'components/badges.css';
|
||||
@import 'components/cards.css';
|
||||
@import 'components/forms.css';
|
||||
@import 'components/tables.css';
|
||||
@import 'components/modals.css';
|
||||
|
||||
/* 3. Layout */
|
||||
@import 'layout/header.css';
|
||||
@import 'layout/sidebar.css';
|
||||
@import 'layout/container.css';
|
||||
|
||||
/* 4. Pages */
|
||||
@import 'pages/dashboard.css';
|
||||
@import 'pages/vaccess.css';
|
||||
|
||||
62
Backend/admin/frontend/assets/css/pages/dashboard.css
Normal file
62
Backend/admin/frontend/assets/css/pages/dashboard.css
Normal file
@@ -0,0 +1,62 @@
|
||||
/* ============================================
|
||||
Dashboard Page
|
||||
Главная страница с сервисами и таблицами
|
||||
============================================ */
|
||||
|
||||
/* Services Grid */
|
||||
.services-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
/* Settings Header */
|
||||
.settings-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: var(--space-lg);
|
||||
|
||||
.section-title {
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Settings Grid */
|
||||
.settings-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: var(--space-lg);
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
/* Settings Actions */
|
||||
.settings-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
/* Responsive Grid */
|
||||
@media (max-width: 1200px) {
|
||||
.services-grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.services-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.services-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.settings-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
264
Backend/admin/frontend/assets/css/pages/vaccess.css
Normal file
264
Backend/admin/frontend/assets/css/pages/vaccess.css
Normal file
@@ -0,0 +1,264 @@
|
||||
/* ============================================
|
||||
vAccess Editor Page
|
||||
Страница редактора правил доступа
|
||||
============================================ */
|
||||
|
||||
.vaccess-page {
|
||||
animation: fadeIn var(--transition-slow);
|
||||
}
|
||||
|
||||
/* Breadcrumbs */
|
||||
.breadcrumbs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: var(--space-lg);
|
||||
margin-bottom: var(--space-md);
|
||||
padding: var(--space-md) 20px;
|
||||
background: rgba(139, 92, 246, 0.05);
|
||||
border-radius: var(--radius-lg);
|
||||
border: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.breadcrumbs-left {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.breadcrumbs-tabs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
.breadcrumb-separator {
|
||||
color: var(--text-muted);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
/* vAccess Header */
|
||||
.vaccess-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: var(--space-md);
|
||||
padding: var(--space-lg);
|
||||
background: rgba(139, 92, 246, 0.03);
|
||||
border-radius: var(--radius-xl);
|
||||
border: 1px solid var(--glass-border);
|
||||
}
|
||||
|
||||
.vaccess-title-block {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.vaccess-actions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
min-width: 200px;
|
||||
|
||||
.action-btn {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
padding: 10px var(--space-md);
|
||||
font-size: var(--text-base);
|
||||
}
|
||||
}
|
||||
|
||||
.vaccess-title {
|
||||
font-size: var(--text-3xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
margin: 0 0 var(--space-sm) 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-lg);
|
||||
|
||||
i {
|
||||
color: var(--accent-purple-light);
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.vaccess-subtitle {
|
||||
font-size: var(--text-md);
|
||||
color: var(--text-muted);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* vAccess Tab Content */
|
||||
.vaccess-tab-content {
|
||||
animation: fadeIn var(--transition-slow);
|
||||
}
|
||||
|
||||
/* vAccess Rules Container */
|
||||
.vaccess-rules-container {
|
||||
/* Контейнер без padding чтобы таблица была на всю ширину */
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* vAccess Rules List */
|
||||
.vaccess-rules-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
max-height: 55vh;
|
||||
overflow-y: auto;
|
||||
padding-right: var(--space-sm);
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.vaccess-empty {
|
||||
text-align: center;
|
||||
padding: 80px 40px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 64px;
|
||||
margin-bottom: var(--space-lg);
|
||||
opacity: 0.3;
|
||||
}
|
||||
|
||||
.vaccess-empty h3 {
|
||||
font-size: var(--text-2xl);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.vaccess-empty p {
|
||||
font-size: var(--text-md);
|
||||
margin-bottom: var(--space-lg);
|
||||
}
|
||||
|
||||
/* vAccess Help */
|
||||
.vaccess-help {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
/* Help Parameters */
|
||||
.help-params {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-lg);
|
||||
}
|
||||
|
||||
.help-param {
|
||||
padding: 20px;
|
||||
background: rgba(139, 92, 246, 0.03);
|
||||
border-radius: var(--radius-lg);
|
||||
border-left: 3px solid var(--accent-purple);
|
||||
|
||||
strong {
|
||||
display: block;
|
||||
font-size: 15px;
|
||||
color: var(--accent-purple-light);
|
||||
margin-bottom: var(--space-sm);
|
||||
}
|
||||
|
||||
p {
|
||||
margin: var(--space-sm) 0 0 0;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin: 12px 0 0 20px;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 3px 8px;
|
||||
background: rgba(139, 92, 246, 0.15);
|
||||
border-radius: var(--radius-sm);
|
||||
font-size: var(--text-base);
|
||||
color: var(--accent-purple-light);
|
||||
}
|
||||
}
|
||||
|
||||
.help-warning {
|
||||
color: rgba(251, 191, 36, 0.9) !important;
|
||||
margin-top: var(--space-sm);
|
||||
font-size: var(--text-base);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--space-sm);
|
||||
}
|
||||
|
||||
/* Help Patterns */
|
||||
.help-patterns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||||
gap: var(--space-md);
|
||||
}
|
||||
|
||||
.pattern-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--space-sm);
|
||||
padding: var(--space-md);
|
||||
background: rgba(139, 92, 246, 0.05);
|
||||
border-radius: 10px;
|
||||
transition: all var(--transition-base);
|
||||
|
||||
&:hover {
|
||||
background: rgba(139, 92, 246, 0.1);
|
||||
}
|
||||
|
||||
code {
|
||||
font-size: var(--text-md);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--accent-purple-light);
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: var(--text-base);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* Help Examples */
|
||||
.help-example {
|
||||
margin-bottom: var(--space-xl);
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
font-size: var(--text-lg);
|
||||
font-weight: var(--font-semibold);
|
||||
color: var(--text-primary);
|
||||
margin-bottom: var(--space-md);
|
||||
}
|
||||
}
|
||||
|
||||
.example-rule {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
background: rgba(255, 255, 255, 0.02);
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--glass-border);
|
||||
|
||||
div {
|
||||
font-size: var(--text-md);
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
strong {
|
||||
color: var(--text-muted);
|
||||
margin-right: var(--space-sm);
|
||||
}
|
||||
|
||||
code {
|
||||
color: var(--accent-purple-light);
|
||||
}
|
||||
}
|
||||
|
||||
100
Backend/admin/frontend/assets/css/variables.css
Normal file
100
Backend/admin/frontend/assets/css/variables.css
Normal file
@@ -0,0 +1,100 @@
|
||||
/* ============================================
|
||||
CSS Custom Properties (Design Tokens)
|
||||
Профессиональная система переменных
|
||||
============================================ */
|
||||
|
||||
:root {
|
||||
/* Colors - Background */
|
||||
--bg-primary: #0b101f;
|
||||
--bg-secondary: #121420;
|
||||
--bg-tertiary: #0d0f1c;
|
||||
|
||||
/* Colors - Glass Effect */
|
||||
--glass-bg: rgba(20, 20, 40, 0.4);
|
||||
--glass-bg-light: rgba(20, 20, 40, 0.3);
|
||||
--glass-bg-dark: rgba(10, 14, 26, 0.5);
|
||||
--glass-border: rgba(139, 92, 246, 0.15);
|
||||
--glass-border-hover: rgba(139, 92, 246, 0.3);
|
||||
|
||||
/* Colors - Accent */
|
||||
--accent-blue: #5b21b6;
|
||||
--accent-blue-light: #7c3aed;
|
||||
--accent-purple: #8b5cf6;
|
||||
--accent-purple-light: #a78bfa;
|
||||
--accent-cyan: #06b6d4;
|
||||
--accent-green: #10b981;
|
||||
--accent-red: #ef4444;
|
||||
--accent-yellow: #f59e0b;
|
||||
|
||||
/* Colors - Text */
|
||||
--text-primary: #e2e8f0;
|
||||
--text-secondary: #94a3b8;
|
||||
--text-muted: #64748b;
|
||||
|
||||
/* Shadows */
|
||||
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
--shadow-md: 0 8px 32px rgba(0, 0, 0, 0.5);
|
||||
--shadow-lg: 0 12px 40px rgba(0, 0, 0, 0.7);
|
||||
--shadow-purple: 0 8px 32px rgba(139, 92, 246, 0.3);
|
||||
--shadow-green: 0 4px 12px rgba(16, 185, 129, 0.3);
|
||||
--shadow-red: 0 4px 12px rgba(239, 68, 68, 0.3);
|
||||
|
||||
/* Spacing */
|
||||
--space-xs: 4px;
|
||||
--space-sm: 8px;
|
||||
--space-md: 16px;
|
||||
--space-lg: 24px;
|
||||
--space-xl: 32px;
|
||||
--space-2xl: 48px;
|
||||
--space-3xl: 60px;
|
||||
|
||||
/* Border Radius */
|
||||
--radius-sm: 6px;
|
||||
--radius-md: 8px;
|
||||
--radius-lg: 12px;
|
||||
--radius-xl: 16px;
|
||||
--radius-2xl: 10px;
|
||||
--radius-full: 50%;
|
||||
|
||||
/* Transitions */
|
||||
--transition-fast: 0.15s ease;
|
||||
--transition-base: 0.2s ease;
|
||||
--transition-slow: 0.3s ease;
|
||||
--transition-bounce: 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
|
||||
/* Typography */
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-mono: 'Consolas', 'Courier New', monospace;
|
||||
|
||||
/* Font Sizes */
|
||||
--text-xs: 10px;
|
||||
--text-sm: 11px;
|
||||
--text-base: 13px;
|
||||
--text-md: 14px;
|
||||
--text-lg: 16px;
|
||||
--text-xl: 18px;
|
||||
--text-2xl: 20px;
|
||||
--text-3xl: 28px;
|
||||
|
||||
/* Font Weights */
|
||||
--font-normal: 400;
|
||||
--font-medium: 500;
|
||||
--font-semibold: 600;
|
||||
--font-bold: 700;
|
||||
|
||||
/* Z-Index Scale */
|
||||
--z-base: 1;
|
||||
--z-dropdown: 100;
|
||||
--z-modal: 9998;
|
||||
--z-notification: 9999;
|
||||
--z-loader: 10000;
|
||||
|
||||
/* Layout */
|
||||
--header-height: 60px;
|
||||
--sidebar-width: 80px;
|
||||
|
||||
/* Backdrop Filter */
|
||||
--backdrop-blur: blur(20px) saturate(180%);
|
||||
--backdrop-blur-light: blur(10px);
|
||||
}
|
||||
|
||||
129
Backend/admin/frontend/assets/js/api/config.js
Normal file
129
Backend/admin/frontend/assets/js/api/config.js
Normal file
@@ -0,0 +1,129 @@
|
||||
/* ============================================
|
||||
Config API
|
||||
Работа с конфигурацией
|
||||
============================================ */
|
||||
|
||||
import { isWailsAvailable, log } from '../utils/helpers.js';
|
||||
|
||||
/**
|
||||
* Класс для работы с конфигурацией
|
||||
*/
|
||||
class ConfigAPI {
|
||||
constructor() {
|
||||
this.available = isWailsAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить конфигурацию
|
||||
*/
|
||||
async getConfig() {
|
||||
if (!this.available) return null;
|
||||
try {
|
||||
return await window.go.admin.App.GetConfig();
|
||||
} catch (error) {
|
||||
log(`Ошибка получения конфигурации: ${error.message}`, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить конфигурацию
|
||||
*/
|
||||
async saveConfig(configJSON) {
|
||||
if (!this.available) return 'Error: API недоступен';
|
||||
try {
|
||||
return await window.go.admin.App.SaveConfig(configJSON);
|
||||
} catch (error) {
|
||||
log(`Ошибка сохранения конфигурации: ${error.message}`, 'error');
|
||||
return `Error: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Включить Proxy Service
|
||||
*/
|
||||
async enableProxyService() {
|
||||
if (!this.available) return;
|
||||
try {
|
||||
await window.go.admin.App.EnableProxyService();
|
||||
} catch (error) {
|
||||
log(`Ошибка включения Proxy: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Отключить Proxy Service
|
||||
*/
|
||||
async disableProxyService() {
|
||||
if (!this.available) return;
|
||||
try {
|
||||
await window.go.admin.App.DisableProxyService();
|
||||
} catch (error) {
|
||||
log(`Ошибка отключения Proxy: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Перезапустить все сервисы
|
||||
*/
|
||||
async restartAllServices() {
|
||||
if (!this.available) return;
|
||||
try {
|
||||
await window.go.admin.App.RestartAllServices();
|
||||
} catch (error) {
|
||||
log(`Ошибка перезапуска сервисов: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить HTTP Service
|
||||
*/
|
||||
async startHTTPService() {
|
||||
if (!this.available) return;
|
||||
try {
|
||||
await window.go.admin.App.StartHTTPService();
|
||||
} catch (error) {
|
||||
log(`Ошибка запуска HTTP: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Остановить HTTP Service
|
||||
*/
|
||||
async stopHTTPService() {
|
||||
if (!this.available) return;
|
||||
try {
|
||||
await window.go.admin.App.StopHTTPService();
|
||||
} catch (error) {
|
||||
log(`Ошибка остановки HTTP: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить HTTPS Service
|
||||
*/
|
||||
async startHTTPSService() {
|
||||
if (!this.available) return;
|
||||
try {
|
||||
await window.go.admin.App.StartHTTPSService();
|
||||
} catch (error) {
|
||||
log(`Ошибка запуска HTTPS: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Остановить HTTPS Service
|
||||
*/
|
||||
async stopHTTPSService() {
|
||||
if (!this.available) return;
|
||||
try {
|
||||
await window.go.admin.App.StopHTTPSService();
|
||||
} catch (error) {
|
||||
log(`Ошибка остановки HTTPS: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Экспортируем единственный экземпляр
|
||||
export const configAPI = new ConfigAPI();
|
||||
|
||||
143
Backend/admin/frontend/assets/js/api/wails.js
Normal file
143
Backend/admin/frontend/assets/js/api/wails.js
Normal file
@@ -0,0 +1,143 @@
|
||||
/* ============================================
|
||||
Wails API Wrapper
|
||||
Обёртка над Wails API
|
||||
============================================ */
|
||||
|
||||
import { isWailsAvailable, log } from '../utils/helpers.js';
|
||||
|
||||
/**
|
||||
* Базовый класс для работы с Wails API
|
||||
*/
|
||||
class WailsAPI {
|
||||
constructor() {
|
||||
this.available = isWailsAvailable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверка доступности API
|
||||
*/
|
||||
checkAvailability() {
|
||||
if (!this.available) {
|
||||
log('Wails API недоступен', 'warn');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить статус всех сервисов
|
||||
*/
|
||||
async getAllServicesStatus() {
|
||||
if (!this.checkAvailability()) return null;
|
||||
try {
|
||||
return await window.go.admin.App.GetAllServicesStatus();
|
||||
} catch (error) {
|
||||
log(`Ошибка получения статуса сервисов: ${error.message}`, 'error');
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить список сайтов
|
||||
*/
|
||||
async getSitesList() {
|
||||
if (!this.checkAvailability()) return [];
|
||||
try {
|
||||
return await window.go.admin.App.GetSitesList();
|
||||
} catch (error) {
|
||||
log(`Ошибка получения списка сайтов: ${error.message}`, 'error');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить список прокси
|
||||
*/
|
||||
async getProxyList() {
|
||||
if (!this.checkAvailability()) return [];
|
||||
try {
|
||||
return await window.go.admin.App.GetProxyList();
|
||||
} catch (error) {
|
||||
log(`Ошибка получения списка прокси: ${error.message}`, 'error');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить правила vAccess
|
||||
*/
|
||||
async getVAccessRules(host, isProxy) {
|
||||
if (!this.checkAvailability()) return { rules: [] };
|
||||
try {
|
||||
return await window.go.admin.App.GetVAccessRules(host, isProxy);
|
||||
} catch (error) {
|
||||
log(`Ошибка получения правил vAccess: ${error.message}`, 'error');
|
||||
return { rules: [] };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить правила vAccess
|
||||
*/
|
||||
async saveVAccessRules(host, isProxy, configJSON) {
|
||||
if (!this.checkAvailability()) return 'Error: API недоступен';
|
||||
try {
|
||||
return await window.go.admin.App.SaveVAccessRules(host, isProxy, configJSON);
|
||||
} catch (error) {
|
||||
log(`Ошибка сохранения правил vAccess: ${error.message}`, 'error');
|
||||
return `Error: ${error.message}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить сервер
|
||||
*/
|
||||
async startServer() {
|
||||
if (!this.checkAvailability()) return;
|
||||
try {
|
||||
await window.go.admin.App.StartServer();
|
||||
} catch (error) {
|
||||
log(`Ошибка запуска сервера: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Остановить сервер
|
||||
*/
|
||||
async stopServer() {
|
||||
if (!this.checkAvailability()) return;
|
||||
try {
|
||||
await window.go.admin.App.StopServer();
|
||||
} catch (error) {
|
||||
log(`Ошибка остановки сервера: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить готовность сервисов
|
||||
*/
|
||||
async checkServicesReady() {
|
||||
if (!this.checkAvailability()) return false;
|
||||
try {
|
||||
return await window.go.admin.App.CheckServicesReady();
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Открыть папку сайта
|
||||
*/
|
||||
async openSiteFolder(host) {
|
||||
if (!this.checkAvailability()) return;
|
||||
try {
|
||||
await window.go.admin.App.OpenSiteFolder(host);
|
||||
} catch (error) {
|
||||
log(`Ошибка открытия папки: ${error.message}`, 'error');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Экспортируем единственный экземпляр
|
||||
export const api = new WailsAPI();
|
||||
|
||||
151
Backend/admin/frontend/assets/js/components/proxy.js
Normal file
151
Backend/admin/frontend/assets/js/components/proxy.js
Normal file
@@ -0,0 +1,151 @@
|
||||
/* ============================================
|
||||
Proxy Component
|
||||
Управление прокси
|
||||
============================================ */
|
||||
|
||||
import { api } from '../api/wails.js';
|
||||
import { isWailsAvailable } from '../utils/helpers.js';
|
||||
import { $ } from '../utils/dom.js';
|
||||
|
||||
/**
|
||||
* Класс для управления прокси
|
||||
*/
|
||||
export class ProxyManager {
|
||||
constructor() {
|
||||
this.proxiesData = [];
|
||||
this.mockData = [
|
||||
{
|
||||
enable: true,
|
||||
external_domain: 'git.example.ru',
|
||||
local_address: '127.0.0.1',
|
||||
local_port: '3333',
|
||||
service_https_use: false,
|
||||
auto_https: true,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
enable: true,
|
||||
external_domain: 'api.example.com',
|
||||
local_address: '127.0.0.1',
|
||||
local_port: '8080',
|
||||
service_https_use: true,
|
||||
auto_https: false,
|
||||
status: 'active'
|
||||
},
|
||||
{
|
||||
enable: false,
|
||||
external_domain: 'test.example.net',
|
||||
local_address: '127.0.0.1',
|
||||
local_port: '5000',
|
||||
service_https_use: false,
|
||||
auto_https: false,
|
||||
status: 'disabled'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузить список прокси
|
||||
*/
|
||||
async load() {
|
||||
if (isWailsAvailable()) {
|
||||
this.proxiesData = await api.getProxyList();
|
||||
} else {
|
||||
// Используем тестовые данные если Wails недоступен
|
||||
this.proxiesData = this.mockData;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Отрисовать список прокси
|
||||
*/
|
||||
render() {
|
||||
const tbody = $('proxyTable')?.querySelector('tbody');
|
||||
if (!tbody) return;
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
this.proxiesData.forEach((proxy, index) => {
|
||||
const row = document.createElement('tr');
|
||||
const statusBadge = proxy.status === 'active' ? 'badge-online' : 'badge-offline';
|
||||
const httpsBadge = proxy.service_https_use ? 'badge-yes">HTTPS' : 'badge-no">HTTP';
|
||||
const autoHttpsBadge = proxy.auto_https ? 'badge-yes">Да' : 'badge-no">Нет';
|
||||
const protocol = proxy.auto_https ? 'https' : 'http';
|
||||
|
||||
row.innerHTML = `
|
||||
<td><code class="clickable-link" data-url="${protocol}://${proxy.external_domain}">${proxy.external_domain} <i class="fas fa-external-link-alt"></i></code></td>
|
||||
<td><code>${proxy.local_address}:${proxy.local_port}</code></td>
|
||||
<td><span class="badge ${httpsBadge}</span></td>
|
||||
<td><span class="badge ${autoHttpsBadge}</span></td>
|
||||
<td><span class="badge ${statusBadge}">${proxy.status}</span></td>
|
||||
<td>
|
||||
<button class="icon-btn" data-action="edit-vaccess" data-host="${proxy.external_domain}" data-is-proxy="true" title="vAccess"><i class="fas fa-shield-alt"></i></button>
|
||||
<button class="icon-btn" data-action="edit-proxy" data-index="${index}" title="Редактировать"><i class="fas fa-edit"></i></button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
// Добавляем обработчики событий
|
||||
this.attachEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить обработчики событий
|
||||
*/
|
||||
attachEventListeners() {
|
||||
// Кликабельные ссылки
|
||||
const links = document.querySelectorAll('.clickable-link[data-url]');
|
||||
links.forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
const url = link.getAttribute('data-url');
|
||||
this.openLink(url);
|
||||
});
|
||||
});
|
||||
|
||||
// Кнопки действий
|
||||
const buttons = document.querySelectorAll('[data-action]');
|
||||
buttons.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const action = btn.getAttribute('data-action');
|
||||
this.handleAction(action, btn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик действий
|
||||
*/
|
||||
handleAction(action, btn) {
|
||||
const host = btn.getAttribute('data-host');
|
||||
const index = parseInt(btn.getAttribute('data-index'));
|
||||
const isProxy = btn.getAttribute('data-is-proxy') === 'true';
|
||||
|
||||
switch (action) {
|
||||
case 'edit-vaccess':
|
||||
if (window.editVAccess) {
|
||||
window.editVAccess(host, isProxy);
|
||||
}
|
||||
break;
|
||||
case 'edit-proxy':
|
||||
if (window.editProxy) {
|
||||
window.editProxy(index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Открыть ссылку
|
||||
*/
|
||||
openLink(url) {
|
||||
if (window.runtime?.BrowserOpenURL) {
|
||||
window.runtime.BrowserOpenURL(url);
|
||||
} else {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
191
Backend/admin/frontend/assets/js/components/services.js
Normal file
191
Backend/admin/frontend/assets/js/components/services.js
Normal file
@@ -0,0 +1,191 @@
|
||||
/* ============================================
|
||||
Services Component
|
||||
Управление сервисами
|
||||
============================================ */
|
||||
|
||||
import { api } from '../api/wails.js';
|
||||
import { $, $$, addClass, removeClass } from '../utils/dom.js';
|
||||
import { notification } from '../ui/notification.js';
|
||||
import { sleep, isWailsAvailable } from '../utils/helpers.js';
|
||||
|
||||
/**
|
||||
* Класс для управления сервисами
|
||||
*/
|
||||
export class ServicesManager {
|
||||
constructor() {
|
||||
this.serverRunning = true;
|
||||
this.isOperating = false;
|
||||
this.controlBtn = $('serverControlBtn');
|
||||
this.statusIndicator = document.querySelector('.status-indicator');
|
||||
this.statusText = document.querySelector('.status-text');
|
||||
this.btnText = document.querySelector('.btn-text');
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.controlBtn) {
|
||||
this.controlBtn.addEventListener('click', () => this.toggleServer());
|
||||
}
|
||||
|
||||
// Подписка на события
|
||||
if (window.runtime?.EventsOn) {
|
||||
window.runtime.EventsOn('service:changed', (status) => {
|
||||
this.renderServices(status);
|
||||
});
|
||||
|
||||
window.runtime.EventsOn('server:already_running', () => {
|
||||
notification.error('vServer уже запущен!<br><br>Закройте другой экземпляр перед запуском нового.', 5000);
|
||||
this.setServerStatus(false, 'Уже запущен в другом процессе');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Переключить состояние сервера
|
||||
*/
|
||||
async toggleServer() {
|
||||
if (this.serverRunning) {
|
||||
await this.stopServer();
|
||||
} else {
|
||||
await this.startServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить сервер
|
||||
*/
|
||||
async startServer() {
|
||||
this.isOperating = true;
|
||||
this.controlBtn.disabled = true;
|
||||
this.statusText.textContent = 'Запускается...';
|
||||
this.btnText.textContent = 'Ожидайте...';
|
||||
this.setAllServicesPending('Запуск');
|
||||
|
||||
await api.startServer();
|
||||
|
||||
// Ждём пока все запустятся
|
||||
let attempts = 0;
|
||||
while (attempts < 20) {
|
||||
await sleep(500);
|
||||
if (await api.checkServicesReady()) {
|
||||
break;
|
||||
}
|
||||
attempts++;
|
||||
}
|
||||
|
||||
this.isOperating = false;
|
||||
this.setServerStatus(true, 'Сервер запущен');
|
||||
removeClass(this.controlBtn, 'start-mode');
|
||||
this.btnText.textContent = 'Остановить';
|
||||
this.controlBtn.disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Остановить сервер
|
||||
*/
|
||||
async stopServer() {
|
||||
this.isOperating = true;
|
||||
this.controlBtn.disabled = true;
|
||||
this.statusText.textContent = 'Выключается...';
|
||||
this.btnText.textContent = 'Ожидайте...';
|
||||
this.setAllServicesPending('Остановка');
|
||||
|
||||
await api.stopServer();
|
||||
await sleep(1500);
|
||||
|
||||
this.isOperating = false;
|
||||
this.setServerStatus(false, 'Сервер остановлен');
|
||||
addClass(this.controlBtn, 'start-mode');
|
||||
this.btnText.textContent = 'Запустить';
|
||||
this.controlBtn.disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить статус сервера
|
||||
*/
|
||||
setServerStatus(isOnline, text) {
|
||||
this.serverRunning = isOnline;
|
||||
|
||||
if (isOnline) {
|
||||
removeClass(this.statusIndicator, 'status-offline');
|
||||
addClass(this.statusIndicator, 'status-online');
|
||||
} else {
|
||||
removeClass(this.statusIndicator, 'status-online');
|
||||
addClass(this.statusIndicator, 'status-offline');
|
||||
}
|
||||
|
||||
this.statusText.textContent = text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить всем сервисам статус pending
|
||||
*/
|
||||
setAllServicesPending(text) {
|
||||
const badges = $$('.service-card .badge');
|
||||
badges.forEach(badge => {
|
||||
badge.className = 'badge badge-pending';
|
||||
badge.textContent = text;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Отрисовать статусы сервисов
|
||||
*/
|
||||
renderServices(data) {
|
||||
const services = [data.http, data.https, data.mysql, data.php, data.proxy];
|
||||
const cards = $$('.service-card');
|
||||
|
||||
services.forEach((service, index) => {
|
||||
const card = cards[index];
|
||||
if (!card) return;
|
||||
|
||||
const badge = card.querySelector('.badge');
|
||||
const infoValues = card.querySelectorAll('.info-value');
|
||||
|
||||
// Обновляем badge только если НЕ в процессе операции
|
||||
if (badge && !this.isOperating) {
|
||||
if (service.status) {
|
||||
badge.className = 'badge badge-online';
|
||||
badge.textContent = 'Активен';
|
||||
} else {
|
||||
badge.className = 'badge badge-offline';
|
||||
badge.textContent = 'Остановлен';
|
||||
}
|
||||
}
|
||||
|
||||
// Обновляем значения
|
||||
if (service.name === 'Proxy') {
|
||||
if (infoValues[0] && service.info) {
|
||||
infoValues[0].textContent = service.info;
|
||||
}
|
||||
} else {
|
||||
if (infoValues[0]) {
|
||||
infoValues[0].textContent = service.port;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузить статусы сервисов
|
||||
*/
|
||||
async loadStatus() {
|
||||
if (isWailsAvailable()) {
|
||||
const data = await api.getAllServicesStatus();
|
||||
if (data) {
|
||||
this.renderServices(data);
|
||||
}
|
||||
} else {
|
||||
// Используем тестовые данные если Wails недоступен
|
||||
const mockData = {
|
||||
http: { name: 'HTTP', status: true, port: '80' },
|
||||
https: { name: 'HTTPS', status: true, port: '443' },
|
||||
mysql: { name: 'MySQL', status: true, port: '3306' },
|
||||
php: { name: 'PHP', status: true, port: '8000-8003' },
|
||||
proxy: { name: 'Proxy', status: true, port: '', info: '1 из 3' }
|
||||
};
|
||||
this.renderServices(mockData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
150
Backend/admin/frontend/assets/js/components/sites.js
Normal file
150
Backend/admin/frontend/assets/js/components/sites.js
Normal file
@@ -0,0 +1,150 @@
|
||||
/* ============================================
|
||||
Sites Component
|
||||
Управление сайтами
|
||||
============================================ */
|
||||
|
||||
import { api } from '../api/wails.js';
|
||||
import { isWailsAvailable } from '../utils/helpers.js';
|
||||
import { $ } from '../utils/dom.js';
|
||||
|
||||
/**
|
||||
* Класс для управления сайтами
|
||||
*/
|
||||
export class SitesManager {
|
||||
constructor() {
|
||||
this.sitesData = [];
|
||||
this.mockData = [
|
||||
{
|
||||
name: 'Локальный сайт',
|
||||
host: '127.0.0.1',
|
||||
alias: ['localhost'],
|
||||
status: 'active',
|
||||
root_file: 'index.html',
|
||||
root_file_routing: true
|
||||
},
|
||||
{
|
||||
name: 'Тестовый проект',
|
||||
host: 'test.local',
|
||||
alias: ['*.test.local', 'test.com'],
|
||||
status: 'active',
|
||||
root_file: 'index.php',
|
||||
root_file_routing: false
|
||||
},
|
||||
{
|
||||
name: 'API сервис',
|
||||
host: 'api.example.com',
|
||||
alias: ['*.api.example.com'],
|
||||
status: 'inactive',
|
||||
root_file: 'index.php',
|
||||
root_file_routing: true
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузить список сайтов
|
||||
*/
|
||||
async load() {
|
||||
if (isWailsAvailable()) {
|
||||
this.sitesData = await api.getSitesList();
|
||||
} else {
|
||||
// Используем тестовые данные если Wails недоступен
|
||||
this.sitesData = this.mockData;
|
||||
}
|
||||
this.render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Отрисовать список сайтов
|
||||
*/
|
||||
render() {
|
||||
const tbody = $('sitesTable')?.querySelector('tbody');
|
||||
if (!tbody) return;
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
this.sitesData.forEach((site, index) => {
|
||||
const row = document.createElement('tr');
|
||||
const statusBadge = site.status === 'active' ? 'badge-online' : 'badge-offline';
|
||||
const aliases = site.alias.join(', ');
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${site.name}</td>
|
||||
<td><code class="clickable-link" data-url="http://${site.host}">${site.host} <i class="fas fa-external-link-alt"></i></code></td>
|
||||
<td><code>${aliases}</code></td>
|
||||
<td><span class="badge ${statusBadge}">${site.status}</span></td>
|
||||
<td><code>${site.root_file}</code></td>
|
||||
<td>
|
||||
<button class="icon-btn" data-action="open-folder" data-host="${site.host}" title="Открыть папку"><i class="fas fa-folder-open"></i></button>
|
||||
<button class="icon-btn" data-action="edit-vaccess" data-host="${site.host}" data-is-proxy="false" title="vAccess"><i class="fas fa-shield-alt"></i></button>
|
||||
<button class="icon-btn" data-action="edit-site" data-index="${index}" title="Редактировать"><i class="fas fa-edit"></i></button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
|
||||
// Добавляем обработчики событий
|
||||
this.attachEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить обработчики событий
|
||||
*/
|
||||
attachEventListeners() {
|
||||
// Кликабельные ссылки
|
||||
const links = document.querySelectorAll('.clickable-link[data-url]');
|
||||
links.forEach(link => {
|
||||
link.addEventListener('click', () => {
|
||||
const url = link.getAttribute('data-url');
|
||||
this.openLink(url);
|
||||
});
|
||||
});
|
||||
|
||||
// Кнопки действий
|
||||
const buttons = document.querySelectorAll('[data-action]');
|
||||
buttons.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const action = btn.getAttribute('data-action');
|
||||
this.handleAction(action, btn);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработчик действий
|
||||
*/
|
||||
async handleAction(action, btn) {
|
||||
const host = btn.getAttribute('data-host');
|
||||
const index = parseInt(btn.getAttribute('data-index'));
|
||||
const isProxy = btn.getAttribute('data-is-proxy') === 'true';
|
||||
|
||||
switch (action) {
|
||||
case 'open-folder':
|
||||
await api.openSiteFolder(host);
|
||||
break;
|
||||
case 'edit-vaccess':
|
||||
if (window.editVAccess) {
|
||||
window.editVAccess(host, isProxy);
|
||||
}
|
||||
break;
|
||||
case 'edit-site':
|
||||
if (window.editSite) {
|
||||
window.editSite(index);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Открыть ссылку
|
||||
*/
|
||||
openLink(url) {
|
||||
if (window.runtime?.BrowserOpenURL) {
|
||||
window.runtime.BrowserOpenURL(url);
|
||||
} else {
|
||||
window.open(url, '_blank');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
396
Backend/admin/frontend/assets/js/components/vaccess.js
Normal file
396
Backend/admin/frontend/assets/js/components/vaccess.js
Normal file
@@ -0,0 +1,396 @@
|
||||
/* ============================================
|
||||
vAccess Component
|
||||
Управление правилами доступа
|
||||
============================================ */
|
||||
|
||||
import { api } from '../api/wails.js';
|
||||
import { $, hide, show } from '../utils/dom.js';
|
||||
import { notification } from '../ui/notification.js';
|
||||
import { modal } from '../ui/modal.js';
|
||||
import { isWailsAvailable } from '../utils/helpers.js';
|
||||
|
||||
/**
|
||||
* Класс для управления vAccess правилами
|
||||
*/
|
||||
export class VAccessManager {
|
||||
constructor() {
|
||||
this.vAccessHost = '';
|
||||
this.vAccessIsProxy = false;
|
||||
this.vAccessRules = [];
|
||||
this.vAccessReturnSection = 'sectionSites';
|
||||
this.draggedIndex = null;
|
||||
this.editingField = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Открыть редактор vAccess
|
||||
*/
|
||||
async open(host, isProxy) {
|
||||
this.vAccessHost = host;
|
||||
this.vAccessIsProxy = isProxy;
|
||||
|
||||
// Запоминаем откуда пришли
|
||||
if ($('sectionSites').style.display !== 'none') {
|
||||
this.vAccessReturnSection = 'sectionSites';
|
||||
} else if ($('sectionProxy').style.display !== 'none') {
|
||||
this.vAccessReturnSection = 'sectionProxy';
|
||||
}
|
||||
|
||||
// Загружаем правила
|
||||
if (isWailsAvailable()) {
|
||||
const config = await api.getVAccessRules(host, isProxy);
|
||||
this.vAccessRules = config.rules || [];
|
||||
} else {
|
||||
// Тестовые данные для браузерного режима
|
||||
this.vAccessRules = [
|
||||
{
|
||||
type: 'Disable',
|
||||
type_file: ['*.php'],
|
||||
path_access: ['/uploads/*'],
|
||||
ip_list: [],
|
||||
exceptions_dir: [],
|
||||
url_error: '404'
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
// Обновляем UI
|
||||
const subtitle = isProxy
|
||||
? 'Управление правилами доступа для прокси-сервиса'
|
||||
: 'Управление правилами доступа для сайта';
|
||||
|
||||
$('breadcrumbHost').textContent = host;
|
||||
$('vAccessSubtitle').textContent = subtitle;
|
||||
|
||||
// Переключаем на страницу редактора
|
||||
this.hideAllSections();
|
||||
show($('sectionVAccessEditor'));
|
||||
|
||||
// Рендерим правила и показываем правильную вкладку
|
||||
this.renderRulesList();
|
||||
this.switchTab('rules');
|
||||
|
||||
// Привязываем кнопку сохранения
|
||||
const saveBtn = $('saveVAccessBtn');
|
||||
if (saveBtn) {
|
||||
saveBtn.onclick = async () => await this.save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Скрыть все секции
|
||||
*/
|
||||
hideAllSections() {
|
||||
hide($('sectionServices'));
|
||||
hide($('sectionSites'));
|
||||
hide($('sectionProxy'));
|
||||
hide($('sectionSettings'));
|
||||
hide($('sectionVAccessEditor'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Вернуться на главную
|
||||
*/
|
||||
backToMain() {
|
||||
this.hideAllSections();
|
||||
show($('sectionServices'));
|
||||
show($('sectionSites'));
|
||||
show($('sectionProxy'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Переключить вкладку
|
||||
*/
|
||||
switchTab(tab) {
|
||||
const tabs = document.querySelectorAll('.vaccess-tab[data-tab]');
|
||||
tabs.forEach(t => {
|
||||
if (t.dataset.tab === tab) {
|
||||
t.classList.add('active');
|
||||
} else {
|
||||
t.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
if (tab === 'rules') {
|
||||
show($('vAccessRulesTab'));
|
||||
hide($('vAccessHelpTab'));
|
||||
} else {
|
||||
hide($('vAccessRulesTab'));
|
||||
show($('vAccessHelpTab'));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить изменения
|
||||
*/
|
||||
async save() {
|
||||
if (isWailsAvailable()) {
|
||||
const config = { rules: this.vAccessRules };
|
||||
const configJSON = JSON.stringify(config);
|
||||
const result = await api.saveVAccessRules(this.vAccessHost, this.vAccessIsProxy, configJSON);
|
||||
|
||||
if (result.startsWith('Error')) {
|
||||
notification.error(result, 2000);
|
||||
} else {
|
||||
notification.success('✅ Правила vAccess успешно сохранены', 1000);
|
||||
}
|
||||
} else {
|
||||
// Браузерный режим - просто показываем уведомление
|
||||
notification.success('Данные сохранены (тестовый режим)');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Отрисовать список правил
|
||||
*/
|
||||
renderRulesList() {
|
||||
const tbody = $('vAccessTableBody');
|
||||
const emptyState = $('vAccessEmpty');
|
||||
const table = document.querySelector('.vaccess-table');
|
||||
|
||||
if (!tbody) return;
|
||||
|
||||
// Показываем/скрываем пустое состояние
|
||||
if (this.vAccessRules.length === 0) {
|
||||
if (table) hide(table);
|
||||
if (emptyState) show(emptyState);
|
||||
return;
|
||||
} else {
|
||||
if (table) show(table);
|
||||
if (emptyState) hide(emptyState);
|
||||
}
|
||||
|
||||
tbody.innerHTML = this.vAccessRules.map((rule, index) => `
|
||||
<tr draggable="true" data-index="${index}">
|
||||
<td class="drag-handle"><i class="fas fa-grip-vertical"></i></td>
|
||||
<td data-field="type" data-index="${index}">
|
||||
<span class="badge ${rule.type === 'Allow' ? 'badge-yes' : 'badge-no'}">${rule.type}</span>
|
||||
</td>
|
||||
<td data-field="type_file" data-index="${index}">
|
||||
${(rule.type_file || []).length > 0 ? (rule.type_file || []).map(f => `<code class="mini-tag">${f}</code>`).join(' ') : '<span class="empty-field">-</span>'}
|
||||
</td>
|
||||
<td data-field="path_access" data-index="${index}">
|
||||
${(rule.path_access || []).length > 0 ? (rule.path_access || []).map(p => `<code class="mini-tag">${p}</code>`).join(' ') : '<span class="empty-field">-</span>'}
|
||||
</td>
|
||||
<td data-field="ip_list" data-index="${index}">
|
||||
${(rule.ip_list || []).length > 0 ? (rule.ip_list || []).map(ip => `<code class="mini-tag">${ip}</code>`).join(' ') : '<span class="empty-field">-</span>'}
|
||||
</td>
|
||||
<td data-field="exceptions_dir" data-index="${index}">
|
||||
${(rule.exceptions_dir || []).length > 0 ? (rule.exceptions_dir || []).map(e => `<code class="mini-tag">${e}</code>`).join(' ') : '<span class="empty-field">-</span>'}
|
||||
</td>
|
||||
<td data-field="url_error" data-index="${index}">
|
||||
<code class="mini-tag">${rule.url_error || '404'}</code>
|
||||
</td>
|
||||
<td>
|
||||
<button class="icon-btn-small" data-action="remove-rule" data-index="${index}" title="Удалить"><i class="fas fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
`).join('');
|
||||
|
||||
// Добавляем обработчики
|
||||
this.attachRulesEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить обработчики событий для правил
|
||||
*/
|
||||
attachRulesEventListeners() {
|
||||
// Drag & Drop
|
||||
const rows = document.querySelectorAll('#vAccessTableBody tr[draggable]');
|
||||
rows.forEach(row => {
|
||||
row.addEventListener('dragstart', (e) => this.onDragStart(e));
|
||||
row.addEventListener('dragover', (e) => this.onDragOver(e));
|
||||
row.addEventListener('drop', (e) => this.onDrop(e));
|
||||
});
|
||||
|
||||
// Клик по ячейкам для редактирования
|
||||
const cells = document.querySelectorAll('#vAccessTableBody td[data-field]');
|
||||
cells.forEach(cell => {
|
||||
cell.addEventListener('click', () => {
|
||||
const field = cell.getAttribute('data-field');
|
||||
const index = parseInt(cell.getAttribute('data-index'));
|
||||
this.editRuleField(index, field);
|
||||
});
|
||||
});
|
||||
|
||||
// Кнопки удаления
|
||||
const removeButtons = document.querySelectorAll('[data-action="remove-rule"]');
|
||||
removeButtons.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const index = parseInt(btn.getAttribute('data-index'));
|
||||
this.removeRule(index);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить новое правило
|
||||
*/
|
||||
addRule() {
|
||||
this.vAccessRules.push({
|
||||
type: 'Disable',
|
||||
type_file: [],
|
||||
path_access: [],
|
||||
ip_list: [],
|
||||
exceptions_dir: [],
|
||||
url_error: '404'
|
||||
});
|
||||
|
||||
this.switchTab('rules');
|
||||
this.renderRulesList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить правило
|
||||
*/
|
||||
removeRule(index) {
|
||||
this.vAccessRules.splice(index, 1);
|
||||
this.renderRulesList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Редактировать поле правила
|
||||
*/
|
||||
editRuleField(index, field) {
|
||||
const rule = this.vAccessRules[index];
|
||||
|
||||
if (field === 'type') {
|
||||
// Переключаем тип
|
||||
rule.type = rule.type === 'Allow' ? 'Disable' : 'Allow';
|
||||
this.renderRulesList();
|
||||
} else if (field === 'url_error') {
|
||||
// Простой prompt для ошибки
|
||||
const value = prompt('Страница ошибки:', rule.url_error || '404');
|
||||
if (value !== null) {
|
||||
rule.url_error = value;
|
||||
this.renderRulesList();
|
||||
}
|
||||
} else {
|
||||
// Для массивов - показываем форму редактирования
|
||||
this.showFieldEditor(index, field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Показать редактор поля
|
||||
*/
|
||||
showFieldEditor(index, field) {
|
||||
const rule = this.vAccessRules[index];
|
||||
const fieldNames = {
|
||||
'type_file': 'Расширения файлов',
|
||||
'path_access': 'Пути доступа',
|
||||
'ip_list': 'IP адреса',
|
||||
'exceptions_dir': 'Исключения'
|
||||
};
|
||||
|
||||
const placeholders = {
|
||||
'type_file': '*.php',
|
||||
'path_access': '/admin/*',
|
||||
'ip_list': '127.0.0.1',
|
||||
'exceptions_dir': '/public/*'
|
||||
};
|
||||
|
||||
const content = `
|
||||
<div class="field-editor">
|
||||
<div class="tag-input-wrapper" style="margin-bottom: 16px;">
|
||||
<input type="text" class="form-input" id="fieldInput" placeholder="${placeholders[field]}">
|
||||
<button class="action-btn" id="addFieldValueBtn"><i class="fas fa-plus"></i> Добавить</button>
|
||||
</div>
|
||||
<div class="tags-container" id="fieldTags">
|
||||
${(rule[field] || []).map(value => `
|
||||
<span class="tag">
|
||||
${value}
|
||||
<button class="tag-remove" data-value="${value}"><i class="fas fa-times"></i></button>
|
||||
</span>
|
||||
`).join('')}
|
||||
</div>
|
||||
<button class="action-btn" id="closeFieldEditorBtn" style="margin-top: 20px;">
|
||||
<i class="fas fa-check"></i> Готово
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.editingField = { index, field };
|
||||
modal.openFieldEditor(fieldNames[field], content);
|
||||
|
||||
// Добавляем обработчики
|
||||
setTimeout(() => {
|
||||
$('addFieldValueBtn')?.addEventListener('click', () => this.addFieldValue());
|
||||
$('closeFieldEditorBtn')?.addEventListener('click', () => this.closeFieldEditor());
|
||||
|
||||
const removeButtons = document.querySelectorAll('#fieldTags .tag-remove');
|
||||
removeButtons.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const value = btn.getAttribute('data-value');
|
||||
this.removeFieldValue(value);
|
||||
});
|
||||
});
|
||||
}, 100);
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить значение в поле
|
||||
*/
|
||||
addFieldValue() {
|
||||
const input = $('fieldInput');
|
||||
const value = input?.value.trim();
|
||||
|
||||
if (value && this.editingField) {
|
||||
const { index, field } = this.editingField;
|
||||
if (!this.vAccessRules[index][field]) {
|
||||
this.vAccessRules[index][field] = [];
|
||||
}
|
||||
this.vAccessRules[index][field].push(value);
|
||||
input.value = '';
|
||||
this.showFieldEditor(index, field);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить значение из поля
|
||||
*/
|
||||
removeFieldValue(value) {
|
||||
if (this.editingField) {
|
||||
const { index, field } = this.editingField;
|
||||
const arr = this.vAccessRules[index][field];
|
||||
const idx = arr.indexOf(value);
|
||||
if (idx > -1) {
|
||||
arr.splice(idx, 1);
|
||||
this.showFieldEditor(index, field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Закрыть редактор поля
|
||||
*/
|
||||
closeFieldEditor() {
|
||||
modal.closeFieldEditor();
|
||||
this.renderRulesList();
|
||||
}
|
||||
|
||||
// Drag & Drop handlers
|
||||
onDragStart(event) {
|
||||
this.draggedIndex = parseInt(event.target.getAttribute('data-index'));
|
||||
event.target.style.opacity = '0.5';
|
||||
}
|
||||
|
||||
onDragOver(event) {
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
onDrop(event) {
|
||||
event.preventDefault();
|
||||
const dropIndex = parseInt(event.target.closest('tr').getAttribute('data-index'));
|
||||
|
||||
if (this.draggedIndex === null || this.draggedIndex === dropIndex) return;
|
||||
|
||||
const draggedRule = this.vAccessRules[this.draggedIndex];
|
||||
this.vAccessRules.splice(this.draggedIndex, 1);
|
||||
this.vAccessRules.splice(dropIndex, 0, draggedRule);
|
||||
|
||||
this.draggedIndex = null;
|
||||
this.renderRulesList();
|
||||
}
|
||||
}
|
||||
|
||||
586
Backend/admin/frontend/assets/js/main.js
Normal file
586
Backend/admin/frontend/assets/js/main.js
Normal file
@@ -0,0 +1,586 @@
|
||||
/* ============================================
|
||||
vServer Admin Panel - Main Entry Point
|
||||
Точка входа приложения
|
||||
============================================ */
|
||||
|
||||
import { log, isWailsAvailable, sleep } from './utils/helpers.js';
|
||||
import { WindowControls } from './ui/window.js';
|
||||
import { Navigation } from './ui/navigation.js';
|
||||
import { notification } from './ui/notification.js';
|
||||
import { modal } from './ui/modal.js';
|
||||
import { ServicesManager } from './components/services.js';
|
||||
import { SitesManager } from './components/sites.js';
|
||||
import { ProxyManager } from './components/proxy.js';
|
||||
import { VAccessManager } from './components/vaccess.js';
|
||||
import { configAPI } from './api/config.js';
|
||||
import { $ } from './utils/dom.js';
|
||||
|
||||
/**
|
||||
* Главный класс приложения
|
||||
*/
|
||||
class App {
|
||||
constructor() {
|
||||
this.windowControls = new WindowControls();
|
||||
this.navigation = new Navigation();
|
||||
this.servicesManager = new ServicesManager();
|
||||
this.sitesManager = new SitesManager();
|
||||
this.proxyManager = new ProxyManager();
|
||||
this.vAccessManager = new VAccessManager();
|
||||
|
||||
this.isWails = isWailsAvailable();
|
||||
|
||||
log('Приложение инициализировано');
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить приложение
|
||||
*/
|
||||
async start() {
|
||||
log('Запуск приложения...');
|
||||
|
||||
// Скрываем loader если не в Wails
|
||||
if (!this.isWails) {
|
||||
notification.hideLoader();
|
||||
}
|
||||
|
||||
// Ждём немного перед загрузкой данных
|
||||
await sleep(1000);
|
||||
|
||||
if (this.isWails) {
|
||||
log('Wails API доступен', 'info');
|
||||
} else {
|
||||
log('Wails API недоступен (браузерный режим)', 'warn');
|
||||
}
|
||||
|
||||
// Загружаем начальные данные
|
||||
await this.loadInitialData();
|
||||
|
||||
// Запускаем автообновление
|
||||
this.startAutoRefresh();
|
||||
|
||||
// Скрываем loader после загрузки
|
||||
if (this.isWails) {
|
||||
notification.hideLoader();
|
||||
}
|
||||
|
||||
// Настраиваем глобальные функции для совместимости
|
||||
this.setupGlobalHandlers();
|
||||
|
||||
// Привязываем кнопки
|
||||
this.setupButtons();
|
||||
|
||||
log('Приложение запущено');
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузить начальные данные
|
||||
*/
|
||||
async loadInitialData() {
|
||||
await Promise.all([
|
||||
this.servicesManager.loadStatus(),
|
||||
this.sitesManager.load(),
|
||||
this.proxyManager.load()
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Запустить автообновление
|
||||
*/
|
||||
startAutoRefresh() {
|
||||
setInterval(async () => {
|
||||
await this.loadInitialData();
|
||||
}, 5000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Привязать кнопки
|
||||
*/
|
||||
setupButtons() {
|
||||
// Кнопка сохранения настроек
|
||||
const saveSettingsBtn = $('saveSettingsBtn');
|
||||
if (saveSettingsBtn) {
|
||||
saveSettingsBtn.addEventListener('click', async () => {
|
||||
await this.saveConfigSettings();
|
||||
});
|
||||
}
|
||||
|
||||
// Кнопка сохранения vAccess (добавляем обработчик динамически при открытии vAccess)
|
||||
// Обработчик будет добавлен в VAccessManager.open()
|
||||
|
||||
// Моментальное переключение Proxy без перезапуска
|
||||
const proxyCheckbox = $('proxyEnabled');
|
||||
if (proxyCheckbox) {
|
||||
proxyCheckbox.addEventListener('change', async (e) => {
|
||||
const isEnabled = e.target.checked;
|
||||
|
||||
if (isEnabled) {
|
||||
await configAPI.enableProxyService();
|
||||
notification.success('Proxy Manager включен', 1000);
|
||||
} else {
|
||||
await configAPI.disableProxyService();
|
||||
notification.success('Proxy Manager отключен', 1000);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Настроить глобальные обработчики
|
||||
*/
|
||||
setupGlobalHandlers() {
|
||||
// Для vAccess
|
||||
window.editVAccess = (host, isProxy) => {
|
||||
this.vAccessManager.open(host, isProxy);
|
||||
};
|
||||
|
||||
window.backToMain = () => {
|
||||
this.vAccessManager.backToMain();
|
||||
};
|
||||
|
||||
window.switchVAccessTab = (tab) => {
|
||||
this.vAccessManager.switchTab(tab);
|
||||
};
|
||||
|
||||
window.saveVAccessChanges = async () => {
|
||||
await this.vAccessManager.save();
|
||||
};
|
||||
|
||||
window.addVAccessRule = () => {
|
||||
this.vAccessManager.addRule();
|
||||
};
|
||||
|
||||
// Для Settings
|
||||
window.loadConfig = async () => {
|
||||
await this.loadConfigSettings();
|
||||
};
|
||||
|
||||
window.saveSettings = async () => {
|
||||
await this.saveConfigSettings();
|
||||
};
|
||||
|
||||
// Для модальных окон
|
||||
window.editSite = (index) => {
|
||||
this.editSite(index);
|
||||
};
|
||||
|
||||
window.editProxy = (index) => {
|
||||
this.editProxy(index);
|
||||
};
|
||||
|
||||
window.setStatus = (status) => {
|
||||
this.setModalStatus(status);
|
||||
};
|
||||
|
||||
window.setProxyStatus = (status) => {
|
||||
this.setModalStatus(status);
|
||||
};
|
||||
|
||||
window.addAliasTag = () => {
|
||||
this.addAliasTag();
|
||||
};
|
||||
|
||||
window.removeAliasTag = (btn) => {
|
||||
btn.parentElement.remove();
|
||||
};
|
||||
|
||||
window.saveModalData = async () => {
|
||||
await this.saveModalData();
|
||||
};
|
||||
|
||||
// Drag & Drop для vAccess
|
||||
window.dragStart = (event, index) => {
|
||||
this.vAccessManager.onDragStart(event);
|
||||
};
|
||||
|
||||
window.dragOver = (event) => {
|
||||
this.vAccessManager.onDragOver(event);
|
||||
};
|
||||
|
||||
window.drop = (event, index) => {
|
||||
this.vAccessManager.onDrop(event);
|
||||
};
|
||||
|
||||
window.editRuleField = (index, field) => {
|
||||
this.vAccessManager.editRuleField(index, field);
|
||||
};
|
||||
|
||||
window.removeVAccessRule = (index) => {
|
||||
this.vAccessManager.removeRule(index);
|
||||
};
|
||||
|
||||
window.closeFieldEditor = () => {
|
||||
this.vAccessManager.closeFieldEditor();
|
||||
};
|
||||
|
||||
window.addFieldValue = () => {
|
||||
this.vAccessManager.addFieldValue();
|
||||
};
|
||||
|
||||
window.removeFieldValue = (value) => {
|
||||
this.vAccessManager.removeFieldValue(value);
|
||||
};
|
||||
|
||||
// Тестовые функции (для браузерного режима)
|
||||
window.editTestSite = (index) => {
|
||||
const testSites = [
|
||||
{name: 'Локальный сайт', host: '127.0.0.1', alias: ['localhost'], status: 'active', root_file: 'index.html', root_file_routing: true},
|
||||
{name: 'Тестовый проект', host: 'test.local', alias: ['*.test.local', 'test.com'], status: 'active', root_file: 'index.php', root_file_routing: false},
|
||||
{name: 'API сервис', host: 'api.example.com', alias: ['*.api.example.com'], status: 'inactive', root_file: 'index.php', root_file_routing: true}
|
||||
];
|
||||
this.sitesManager.sitesData = testSites;
|
||||
this.editSite(index);
|
||||
};
|
||||
|
||||
window.editTestProxy = (index) => {
|
||||
const testProxies = [
|
||||
{enable: true, external_domain: 'git.example.ru', local_address: '127.0.0.1', local_port: '3333', service_https_use: false, auto_https: true},
|
||||
{enable: true, external_domain: 'api.example.com', local_address: '127.0.0.1', local_port: '8080', service_https_use: true, auto_https: false},
|
||||
{enable: false, external_domain: 'test.example.net', local_address: '127.0.0.1', local_port: '5000', service_https_use: false, auto_https: false}
|
||||
];
|
||||
this.proxyManager.proxiesData = testProxies;
|
||||
this.editProxy(index);
|
||||
};
|
||||
|
||||
window.openTestLink = (url) => {
|
||||
this.sitesManager.openLink(url);
|
||||
};
|
||||
|
||||
window.openSiteFolder = async (host) => {
|
||||
await this.sitesManager.handleAction('open-folder', { getAttribute: () => host });
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузить настройки конфигурации
|
||||
*/
|
||||
async loadConfigSettings() {
|
||||
if (!isWailsAvailable()) {
|
||||
// Тестовые данные для браузерного режима
|
||||
$('mysqlHost').value = '127.0.0.1';
|
||||
$('mysqlPort').value = 3306;
|
||||
$('phpHost').value = 'localhost';
|
||||
$('phpPort').value = 8000;
|
||||
$('proxyEnabled').checked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const config = await configAPI.getConfig();
|
||||
if (!config) return;
|
||||
|
||||
$('mysqlHost').value = config.Soft_Settings?.mysql_host || '127.0.0.1';
|
||||
$('mysqlPort').value = config.Soft_Settings?.mysql_port || 3306;
|
||||
$('phpHost').value = config.Soft_Settings?.php_host || 'localhost';
|
||||
$('phpPort').value = config.Soft_Settings?.php_port || 8000;
|
||||
$('proxyEnabled').checked = config.Soft_Settings?.proxy_enabled !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить настройки конфигурации
|
||||
*/
|
||||
async saveConfigSettings() {
|
||||
const saveBtn = $('saveSettingsBtn');
|
||||
const originalText = saveBtn.querySelector('span').textContent;
|
||||
|
||||
if (!isWailsAvailable()) {
|
||||
notification.success('Настройки сохранены (тестовый режим)', 1000);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.querySelector('span').textContent = 'Сохранение...';
|
||||
|
||||
const config = await configAPI.getConfig();
|
||||
config.Soft_Settings.mysql_host = $('mysqlHost').value;
|
||||
config.Soft_Settings.mysql_port = parseInt($('mysqlPort').value);
|
||||
config.Soft_Settings.php_host = $('phpHost').value;
|
||||
config.Soft_Settings.php_port = parseInt($('phpPort').value);
|
||||
config.Soft_Settings.proxy_enabled = $('proxyEnabled').checked;
|
||||
|
||||
const configJSON = JSON.stringify(config, null, 4);
|
||||
const result = await configAPI.saveConfig(configJSON);
|
||||
|
||||
if (result.startsWith('Error')) {
|
||||
notification.error(result);
|
||||
return;
|
||||
}
|
||||
|
||||
saveBtn.querySelector('span').textContent = 'Перезапуск сервисов...';
|
||||
await configAPI.restartAllServices();
|
||||
|
||||
notification.success('Настройки сохранены и сервисы перезапущены!', 1500);
|
||||
} catch (error) {
|
||||
notification.error('Ошибка: ' + error.message);
|
||||
} finally {
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.querySelector('span').textContent = originalText;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Редактировать сайт
|
||||
*/
|
||||
editSite(index) {
|
||||
const site = this.sitesManager.sitesData[index];
|
||||
if (!site) return;
|
||||
|
||||
const content = `
|
||||
<div class="settings-form">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Статус сайта:</label>
|
||||
<div class="status-toggle">
|
||||
<button class="status-btn ${site.status === 'active' ? 'active' : ''}" onclick="setStatus('active')" data-value="active">
|
||||
<i class="fas fa-check-circle"></i> Active
|
||||
</button>
|
||||
<button class="status-btn ${site.status === 'inactive' ? 'active' : ''}" onclick="setStatus('inactive')" data-value="inactive">
|
||||
<i class="fas fa-times-circle"></i> Inactive
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Название сайта:</label>
|
||||
<input type="text" class="form-input" id="editName" value="${site.name}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Host:</label>
|
||||
<input type="text" class="form-input" id="editHost" value="${site.host}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Alias:</label>
|
||||
<div class="tag-input-wrapper">
|
||||
<input type="text" class="form-input" id="editAliasInput" placeholder="Введите alias и нажмите Добавить...">
|
||||
<button class="action-btn" onclick="addAliasTag()"><i class="fas fa-plus"></i> Добавить</button>
|
||||
</div>
|
||||
<div class="tags-container" id="aliasTagsContainer">
|
||||
${site.alias.map(alias => `
|
||||
<span class="tag">
|
||||
${alias}
|
||||
<button class="tag-remove" onclick="removeAliasTag(this)"><i class="fas fa-times"></i></button>
|
||||
</span>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Root файл:</label>
|
||||
<input type="text" class="form-input" id="editRootFile" value="${site.root_file}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Роутинг:</label>
|
||||
<div class="toggle-wrapper">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="editRouting" ${site.root_file_routing ? 'checked' : ''}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<span class="toggle-label">Включён</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.open('Редактировать сайт', content);
|
||||
window.currentEditType = 'site';
|
||||
window.currentEditIndex = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Редактировать прокси
|
||||
*/
|
||||
editProxy(index) {
|
||||
const proxy = this.proxyManager.proxiesData[index];
|
||||
if (!proxy) return;
|
||||
|
||||
const content = `
|
||||
<div class="settings-form">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Статус прокси:</label>
|
||||
<div class="status-toggle">
|
||||
<button class="status-btn ${proxy.enable ? 'active' : ''}" onclick="setProxyStatus('enable')" data-value="enable">
|
||||
<i class="fas fa-check-circle"></i> Включён
|
||||
</button>
|
||||
<button class="status-btn ${!proxy.enable ? 'active' : ''}" onclick="setProxyStatus('disable')" data-value="disable">
|
||||
<i class="fas fa-times-circle"></i> Отключён
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Внешний домен:</label>
|
||||
<input type="text" class="form-input" id="editDomain" value="${proxy.external_domain}">
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Локальный адрес:</label>
|
||||
<input type="text" class="form-input" id="editLocalAddr" value="${proxy.local_address}">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Локальный порт:</label>
|
||||
<input type="text" class="form-input" id="editLocalPort" value="${proxy.local_port}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label class="form-label">HTTPS к сервису:</label>
|
||||
<div class="toggle-wrapper">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="editServiceHTTPS" ${proxy.service_https_use ? 'checked' : ''}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<span class="toggle-label">Включён</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Авто HTTPS:</label>
|
||||
<div class="toggle-wrapper">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="editAutoHTTPS" ${proxy.auto_https ? 'checked' : ''}>
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<span class="toggle-label">Включён</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.open('Редактировать прокси', content);
|
||||
window.currentEditType = 'proxy';
|
||||
window.currentEditIndex = index;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить статус в модальном окне
|
||||
*/
|
||||
setModalStatus(status) {
|
||||
const buttons = document.querySelectorAll('.status-btn');
|
||||
buttons.forEach(btn => {
|
||||
btn.classList.remove('active');
|
||||
if (btn.dataset.value === status) {
|
||||
btn.classList.add('active');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить alias tag
|
||||
*/
|
||||
addAliasTag() {
|
||||
const input = $('editAliasInput');
|
||||
const value = input?.value.trim();
|
||||
|
||||
if (value) {
|
||||
const container = $('aliasTagsContainer');
|
||||
const tag = document.createElement('span');
|
||||
tag.className = 'tag';
|
||||
tag.innerHTML = `
|
||||
${value}
|
||||
<button class="tag-remove" onclick="removeAliasTag(this)"><i class="fas fa-times"></i></button>
|
||||
`;
|
||||
container.appendChild(tag);
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить данные модального окна
|
||||
*/
|
||||
async saveModalData() {
|
||||
if (!isWailsAvailable()) {
|
||||
notification.success('Данные сохранены (тестовый режим)', 1000);
|
||||
modal.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.currentEditType === 'site') {
|
||||
await this.saveSiteData();
|
||||
} else if (window.currentEditType === 'proxy') {
|
||||
await this.saveProxyData();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить данные сайта
|
||||
*/
|
||||
async saveSiteData() {
|
||||
const index = window.currentEditIndex;
|
||||
const tags = document.querySelectorAll('#aliasTagsContainer .tag');
|
||||
const aliases = Array.from(tags).map(tag => tag.textContent.trim());
|
||||
const statusBtn = document.querySelector('.status-btn.active');
|
||||
|
||||
const config = await configAPI.getConfig();
|
||||
config.Site_www[index] = {
|
||||
name: $('editName').value,
|
||||
host: $('editHost').value,
|
||||
alias: aliases,
|
||||
status: statusBtn ? statusBtn.dataset.value : 'active',
|
||||
root_file: $('editRootFile').value,
|
||||
root_file_routing: $('editRouting').checked
|
||||
};
|
||||
|
||||
const configJSON = JSON.stringify(config, null, 4);
|
||||
const result = await configAPI.saveConfig(configJSON);
|
||||
|
||||
if (result.startsWith('Error')) {
|
||||
notification.error(result);
|
||||
} else {
|
||||
notification.show('Перезапуск HTTP/HTTPS...', 'success', 800);
|
||||
|
||||
await configAPI.stopHTTPService();
|
||||
await configAPI.stopHTTPSService();
|
||||
await sleep(500);
|
||||
await configAPI.startHTTPService();
|
||||
await configAPI.startHTTPSService();
|
||||
|
||||
notification.success('Изменения сохранены и применены!', 1000);
|
||||
await this.sitesManager.load();
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранить данные прокси
|
||||
*/
|
||||
async saveProxyData() {
|
||||
const index = window.currentEditIndex;
|
||||
const statusBtn = document.querySelector('.status-btn.active');
|
||||
const isEnabled = statusBtn && statusBtn.dataset.value === 'enable';
|
||||
|
||||
const config = await configAPI.getConfig();
|
||||
config.Proxy_Service[index] = {
|
||||
Enable: isEnabled,
|
||||
ExternalDomain: $('editDomain').value,
|
||||
LocalAddress: $('editLocalAddr').value,
|
||||
LocalPort: $('editLocalPort').value,
|
||||
ServiceHTTPSuse: $('editServiceHTTPS').checked,
|
||||
AutoHTTPS: $('editAutoHTTPS').checked
|
||||
};
|
||||
|
||||
const configJSON = JSON.stringify(config, null, 4);
|
||||
const result = await configAPI.saveConfig(configJSON);
|
||||
|
||||
if (result.startsWith('Error')) {
|
||||
notification.error(result);
|
||||
} else {
|
||||
notification.show('Перезапуск HTTP/HTTPS...', 'success', 800);
|
||||
|
||||
await configAPI.stopHTTPService();
|
||||
await configAPI.stopHTTPSService();
|
||||
await sleep(500);
|
||||
await configAPI.startHTTPService();
|
||||
await configAPI.startHTTPSService();
|
||||
|
||||
notification.success('Изменения сохранены и применены!', 1000);
|
||||
await this.proxyManager.load();
|
||||
modal.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Инициализация приложения при загрузке DOM
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const app = new App();
|
||||
app.start();
|
||||
});
|
||||
|
||||
log('vServer Admin Panel загружен');
|
||||
|
||||
101
Backend/admin/frontend/assets/js/ui/modal.js
Normal file
101
Backend/admin/frontend/assets/js/ui/modal.js
Normal file
@@ -0,0 +1,101 @@
|
||||
/* ============================================
|
||||
Modal Manager
|
||||
Управление модальными окнами
|
||||
============================================ */
|
||||
|
||||
import { $, addClass, removeClass } from '../utils/dom.js';
|
||||
|
||||
/**
|
||||
* Класс для управления модальными окнами
|
||||
*/
|
||||
export class Modal {
|
||||
constructor() {
|
||||
this.overlay = $('modalOverlay');
|
||||
this.title = $('modalTitle');
|
||||
this.content = $('modalContent');
|
||||
this.closeBtn = $('modalCloseBtn');
|
||||
this.cancelBtn = $('modalCancelBtn');
|
||||
this.saveBtn = $('modalSaveBtn');
|
||||
this.fieldEditorOverlay = $('fieldEditorOverlay');
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.closeBtn) {
|
||||
this.closeBtn.addEventListener('click', () => this.close());
|
||||
}
|
||||
|
||||
if (this.cancelBtn) {
|
||||
this.cancelBtn.addEventListener('click', () => this.close());
|
||||
}
|
||||
|
||||
if (this.saveBtn) {
|
||||
this.saveBtn.addEventListener('click', () => {
|
||||
if (window.saveModalData) {
|
||||
window.saveModalData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (this.overlay) {
|
||||
this.overlay.addEventListener('click', (e) => {
|
||||
if (e.target === this.overlay) {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Открыть модальное окно
|
||||
* @param {string} title - Заголовок
|
||||
* @param {string} htmlContent - HTML контент
|
||||
*/
|
||||
open(title, htmlContent) {
|
||||
if (this.title) this.title.textContent = title;
|
||||
if (this.content) this.content.innerHTML = htmlContent;
|
||||
if (this.overlay) addClass(this.overlay, 'show');
|
||||
}
|
||||
|
||||
/**
|
||||
* Закрыть модальное окно
|
||||
*/
|
||||
close() {
|
||||
if (this.overlay) removeClass(this.overlay, 'show');
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить обработчик сохранения
|
||||
* @param {Function} callback - Функция обратного вызова
|
||||
*/
|
||||
onSave(callback) {
|
||||
if (this.saveBtn) {
|
||||
this.saveBtn.onclick = callback;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Открыть редактор поля
|
||||
* @param {string} title - Заголовок
|
||||
* @param {string} htmlContent - HTML контент
|
||||
*/
|
||||
openFieldEditor(title, htmlContent) {
|
||||
const fieldTitle = $('fieldEditorTitle');
|
||||
const fieldContent = $('fieldEditorContent');
|
||||
|
||||
if (fieldTitle) fieldTitle.textContent = title;
|
||||
if (fieldContent) fieldContent.innerHTML = htmlContent;
|
||||
if (this.fieldEditorOverlay) addClass(this.fieldEditorOverlay, 'show');
|
||||
}
|
||||
|
||||
/**
|
||||
* Закрыть редактор поля
|
||||
*/
|
||||
closeFieldEditor() {
|
||||
if (this.fieldEditorOverlay) removeClass(this.fieldEditorOverlay, 'show');
|
||||
}
|
||||
}
|
||||
|
||||
// Глобальный экземпляр модального окна
|
||||
export const modal = new Modal();
|
||||
|
||||
67
Backend/admin/frontend/assets/js/ui/navigation.js
Normal file
67
Backend/admin/frontend/assets/js/ui/navigation.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/* ============================================
|
||||
Navigation
|
||||
Управление навигацией
|
||||
============================================ */
|
||||
|
||||
import { $, $$, hide, show, removeClass, addClass } from '../utils/dom.js';
|
||||
|
||||
/**
|
||||
* Класс для управления навигацией
|
||||
*/
|
||||
export class Navigation {
|
||||
constructor() {
|
||||
this.navItems = $$('.nav-item');
|
||||
this.sections = {
|
||||
services: $('sectionServices'),
|
||||
sites: $('sectionSites'),
|
||||
proxy: $('sectionProxy'),
|
||||
settings: $('sectionSettings'),
|
||||
vaccess: $('sectionVAccessEditor')
|
||||
};
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
this.navItems.forEach((item, index) => {
|
||||
item.addEventListener('click', () => this.navigate(index));
|
||||
});
|
||||
}
|
||||
|
||||
navigate(index) {
|
||||
// Убираем active со всех навигационных элементов
|
||||
this.navItems.forEach(nav => removeClass(nav, 'active'));
|
||||
addClass(this.navItems[index], 'active');
|
||||
|
||||
// Скрываем все секции
|
||||
this.hideAllSections();
|
||||
|
||||
// Показываем нужные секции
|
||||
if (index === 0) {
|
||||
// Главная - всё кроме настроек
|
||||
show(this.sections.services);
|
||||
show(this.sections.sites);
|
||||
show(this.sections.proxy);
|
||||
} else if (index === 3) {
|
||||
// Настройки
|
||||
show(this.sections.settings);
|
||||
// Загружаем конфигурацию при открытии
|
||||
if (window.loadConfig) {
|
||||
window.loadConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hideAllSections() {
|
||||
Object.values(this.sections).forEach(section => {
|
||||
if (section) hide(section);
|
||||
});
|
||||
}
|
||||
|
||||
showDashboard() {
|
||||
this.hideAllSections();
|
||||
show(this.sections.services);
|
||||
show(this.sections.sites);
|
||||
show(this.sections.proxy);
|
||||
}
|
||||
}
|
||||
|
||||
81
Backend/admin/frontend/assets/js/ui/notification.js
Normal file
81
Backend/admin/frontend/assets/js/ui/notification.js
Normal file
@@ -0,0 +1,81 @@
|
||||
/* ============================================
|
||||
Notification System
|
||||
Система уведомлений
|
||||
============================================ */
|
||||
|
||||
import { $, addClass, removeClass } from '../utils/dom.js';
|
||||
|
||||
/**
|
||||
* Класс для управления уведомлениями
|
||||
*/
|
||||
export class NotificationManager {
|
||||
constructor() {
|
||||
this.container = $('notification');
|
||||
this.loader = $('appLoader');
|
||||
}
|
||||
|
||||
/**
|
||||
* Показать уведомление
|
||||
* @param {string} message - Текст сообщения
|
||||
* @param {string} type - Тип (success, error)
|
||||
* @param {number} duration - Длительность показа (мс)
|
||||
*/
|
||||
show(message, type = 'success', duration = 1000) {
|
||||
if (!this.container) return;
|
||||
|
||||
const icon = type === 'success'
|
||||
? '<i class="fas fa-check-circle"></i>'
|
||||
: '<i class="fas fa-exclamation-circle"></i>';
|
||||
|
||||
this.container.innerHTML = `
|
||||
<div class="notification-content">
|
||||
<div class="notification-icon">${icon}</div>
|
||||
<div class="notification-text">${message}</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
this.container.className = `notification show ${type}`;
|
||||
|
||||
setTimeout(() => {
|
||||
removeClass(this.container, 'show');
|
||||
}, duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Показать успешное уведомление
|
||||
* @param {string} message - Текст сообщения
|
||||
* @param {number} duration - Длительность
|
||||
*/
|
||||
success(message, duration = 1000) {
|
||||
this.show(message, 'success', duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Показать уведомление об ошибке
|
||||
* @param {string} message - Текст сообщения
|
||||
* @param {number} duration - Длительность
|
||||
*/
|
||||
error(message, duration = 2000) {
|
||||
this.show(message, 'error', duration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Скрыть загрузчик приложения
|
||||
*/
|
||||
hideLoader() {
|
||||
if (!this.loader) return;
|
||||
|
||||
setTimeout(() => {
|
||||
addClass(this.loader, 'hide');
|
||||
setTimeout(() => {
|
||||
if (this.loader.parentNode) {
|
||||
this.loader.remove();
|
||||
}
|
||||
}, 500);
|
||||
}, 1500);
|
||||
}
|
||||
}
|
||||
|
||||
// Глобальный экземпляр менеджера уведомлений
|
||||
export const notification = new NotificationManager();
|
||||
|
||||
51
Backend/admin/frontend/assets/js/ui/window.js
Normal file
51
Backend/admin/frontend/assets/js/ui/window.js
Normal file
@@ -0,0 +1,51 @@
|
||||
/* ============================================
|
||||
Window Controls
|
||||
Управление окном приложения
|
||||
============================================ */
|
||||
|
||||
import { $, addClass } from '../utils/dom.js';
|
||||
|
||||
/**
|
||||
* Класс для управления окном
|
||||
*/
|
||||
export class WindowControls {
|
||||
constructor() {
|
||||
this.minimizeBtn = $('minimizeBtn');
|
||||
this.maximizeBtn = $('maximizeBtn');
|
||||
this.closeBtn = $('closeBtn');
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.minimizeBtn) {
|
||||
this.minimizeBtn.addEventListener('click', () => this.minimize());
|
||||
}
|
||||
|
||||
if (this.maximizeBtn) {
|
||||
this.maximizeBtn.addEventListener('click', () => this.maximize());
|
||||
}
|
||||
|
||||
if (this.closeBtn) {
|
||||
this.closeBtn.addEventListener('click', () => this.close());
|
||||
}
|
||||
}
|
||||
|
||||
minimize() {
|
||||
if (window.runtime?.WindowMinimise) {
|
||||
window.runtime.WindowMinimise();
|
||||
}
|
||||
}
|
||||
|
||||
maximize() {
|
||||
if (window.runtime?.WindowToggleMaximise) {
|
||||
window.runtime.WindowToggleMaximise();
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
if (window.runtime?.Quit) {
|
||||
window.runtime.Quit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
83
Backend/admin/frontend/assets/js/utils/dom.js
Normal file
83
Backend/admin/frontend/assets/js/utils/dom.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/* ============================================
|
||||
DOM Utilities
|
||||
Утилиты для работы с DOM
|
||||
============================================ */
|
||||
|
||||
/**
|
||||
* Получить элемент по ID
|
||||
* @param {string} id - ID элемента
|
||||
* @returns {HTMLElement|null}
|
||||
*/
|
||||
export function $(id) {
|
||||
return document.getElementById(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить все элементы по селектору
|
||||
* @param {string} selector - CSS селектор
|
||||
* @param {HTMLElement} parent - Родительский элемент
|
||||
* @returns {NodeList}
|
||||
*/
|
||||
export function $$(selector, parent = document) {
|
||||
return parent.querySelectorAll(selector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Показать элемент
|
||||
* @param {HTMLElement|string} element - Элемент или ID
|
||||
*/
|
||||
export function show(element) {
|
||||
const el = typeof element === 'string' ? $(element) : element;
|
||||
if (el) el.style.display = 'block';
|
||||
}
|
||||
|
||||
/**
|
||||
* Скрыть элемент
|
||||
* @param {HTMLElement|string} element - Элемент или ID
|
||||
*/
|
||||
export function hide(element) {
|
||||
const el = typeof element === 'string' ? $(element) : element;
|
||||
if (el) el.style.display = 'none';
|
||||
}
|
||||
|
||||
/**
|
||||
* Переключить видимость элемента
|
||||
* @param {HTMLElement|string} element - Элемент или ID
|
||||
*/
|
||||
export function toggle(element) {
|
||||
const el = typeof element === 'string' ? $(element) : element;
|
||||
if (el) {
|
||||
el.style.display = el.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить класс
|
||||
* @param {HTMLElement|string} element - Элемент или ID
|
||||
* @param {string} className - Имя класса
|
||||
*/
|
||||
export function addClass(element, className) {
|
||||
const el = typeof element === 'string' ? $(element) : element;
|
||||
if (el) el.classList.add(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить класс
|
||||
* @param {HTMLElement|string} element - Элемент или ID
|
||||
* @param {string} className - Имя класса
|
||||
*/
|
||||
export function removeClass(element, className) {
|
||||
const el = typeof element === 'string' ? $(element) : element;
|
||||
if (el) el.classList.remove(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Переключить класс
|
||||
* @param {HTMLElement|string} element - Элемент или ID
|
||||
* @param {string} className - Имя класса
|
||||
*/
|
||||
export function toggleClass(element, className) {
|
||||
const el = typeof element === 'string' ? $(element) : element;
|
||||
if (el) el.classList.toggle(className);
|
||||
}
|
||||
|
||||
57
Backend/admin/frontend/assets/js/utils/helpers.js
Normal file
57
Backend/admin/frontend/assets/js/utils/helpers.js
Normal file
@@ -0,0 +1,57 @@
|
||||
/* ============================================
|
||||
Helper Utilities
|
||||
Вспомогательные функции
|
||||
============================================ */
|
||||
|
||||
/**
|
||||
* Ждёт указанное время
|
||||
* @param {number} ms - Миллисекунды
|
||||
* @returns {Promise}
|
||||
*/
|
||||
export function sleep(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/**
|
||||
* Debounce функция
|
||||
* @param {Function} func - Функция для debounce
|
||||
* @param {number} wait - Время задержки
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function debounce(func, wait) {
|
||||
let timeout;
|
||||
return function executedFunction(...args) {
|
||||
const later = () => {
|
||||
clearTimeout(timeout);
|
||||
func(...args);
|
||||
};
|
||||
clearTimeout(timeout);
|
||||
timeout = setTimeout(later, wait);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверяет доступность Wails API
|
||||
* @returns {boolean}
|
||||
*/
|
||||
export function isWailsAvailable() {
|
||||
return typeof window.go !== 'undefined' &&
|
||||
window.go?.admin?.App !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Логирование с префиксом
|
||||
* @param {string} message - Сообщение
|
||||
* @param {string} type - Тип (log, error, warn, info)
|
||||
*/
|
||||
export function log(message, type = 'log') {
|
||||
const prefix = '🚀 vServer:';
|
||||
const styles = {
|
||||
log: '✅',
|
||||
error: '❌',
|
||||
warn: '⚠️',
|
||||
info: 'ℹ️'
|
||||
};
|
||||
console[type](`${prefix} ${styles[type]} ${message}`);
|
||||
}
|
||||
|
||||
540
Backend/admin/frontend/index.html
Normal file
540
Backend/admin/frontend/index.html
Normal file
@@ -0,0 +1,540 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>vServer Admin Panel</title>
|
||||
<link rel="stylesheet" href="assets/css/local_lib/all.min.css">
|
||||
<link rel="stylesheet" href="assets/css/main.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="window-controls" id="windowControls">
|
||||
<div class="title-bar">
|
||||
<div class="title-bar-left">
|
||||
<div class="app-logo">
|
||||
<span class="logo-icon">🚀</span>
|
||||
<span class="logo-text">vServer</span>
|
||||
</div>
|
||||
<div class="server-status" id="serverStatus">
|
||||
<span class="status-indicator status-online"></span>
|
||||
<span class="status-text">Сервер запущен</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="title-bar-right">
|
||||
<button class="server-control-btn" id="serverControlBtn">
|
||||
<i class="fas fa-power-off"></i>
|
||||
<span class="btn-text">Остановить</span>
|
||||
</button>
|
||||
<button class="window-btn minimize-btn" id="minimizeBtn" title="Свернуть">
|
||||
<i class="fas fa-window-minimize"></i>
|
||||
</button>
|
||||
<button class="window-btn maximize-btn" id="maximizeBtn" title="Развернуть">
|
||||
<i class="far fa-window-maximize"></i>
|
||||
</button>
|
||||
<button class="window-btn close-btn" id="closeBtn" title="Закрыть">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="sidebar">
|
||||
<nav class="sidebar-nav">
|
||||
<button class="nav-item active" title="Главная">
|
||||
<i class="fas fa-home"></i>
|
||||
</button>
|
||||
<button class="nav-item" title="Статистика">
|
||||
<i class="fas fa-chart-bar"></i>
|
||||
</button>
|
||||
<button class="nav-item" title="Сервисы">
|
||||
<i class="fas fa-server"></i>
|
||||
</button>
|
||||
<button class="nav-item" title="Настройки">
|
||||
<i class="fas fa-cog"></i>
|
||||
</button>
|
||||
</nav>
|
||||
</aside>
|
||||
|
||||
<div class="container">
|
||||
|
||||
<main class="main-content">
|
||||
<section class="section" id="sectionServices">
|
||||
<h2 class="section-title">Статус сервисов</h2>
|
||||
<div class="services-grid" id="servicesGrid">
|
||||
<div class="service-card">
|
||||
<div class="service-header">
|
||||
<h3 class="service-name"><i class="fas fa-globe"></i> HTTP</h3>
|
||||
<span class="badge badge-pending">Запуск</span>
|
||||
</div>
|
||||
<div class="service-info">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Порт:</span>
|
||||
<span class="info-value">80</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="service-card">
|
||||
<div class="service-header">
|
||||
<h3 class="service-name"><i class="fas fa-lock"></i> HTTPS</h3>
|
||||
<span class="badge badge-pending">Запуск</span>
|
||||
</div>
|
||||
<div class="service-info">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Порт:</span>
|
||||
<span class="info-value">443</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="service-card">
|
||||
<div class="service-header">
|
||||
<h3 class="service-name"><i class="fas fa-database"></i> MySQL</h3>
|
||||
<span class="badge badge-pending">Запуск</span>
|
||||
</div>
|
||||
<div class="service-info">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Порт:</span>
|
||||
<span class="info-value">3306</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="service-card">
|
||||
<div class="service-header">
|
||||
<h3 class="service-name"><i class="fab fa-php"></i> PHP</h3>
|
||||
<span class="badge badge-pending">Запуск</span>
|
||||
</div>
|
||||
<div class="service-info">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Порты:</span>
|
||||
<span class="info-value">8000-8003</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="service-card">
|
||||
<div class="service-header">
|
||||
<h3 class="service-name"><i class="fas fa-exchange-alt"></i> Proxy</h3>
|
||||
<span class="badge badge-pending">Запуск</span>
|
||||
</div>
|
||||
<div class="service-info">
|
||||
<div class="info-row">
|
||||
<span class="info-label">Правил:</span>
|
||||
<span class="info-value">0 из 1</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="sectionSites">
|
||||
<h2 class="section-title">Список сайтов</h2>
|
||||
<div class="table-container">
|
||||
<table class="data-table" id="sitesTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Имя</th>
|
||||
<th>Host</th>
|
||||
<th>Alias</th>
|
||||
<th>Статус</th>
|
||||
<th>Root File</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Локальный сайт</td>
|
||||
<td><code class="clickable-link" onclick="openTestLink('http://127.0.0.1')">127.0.0.1 <i class="fas fa-external-link-alt"></i></code></td>
|
||||
<td><code>localhost</code></td>
|
||||
<td><span class="badge badge-online">active</span></td>
|
||||
<td><code>index.html</code></td>
|
||||
<td>
|
||||
<button class="icon-btn" onclick="openSiteFolder('127.0.0.1')" title="Открыть папку"><i class="fas fa-folder-open"></i></button>
|
||||
<button class="icon-btn" onclick="editVAccess('127.0.0.1', false)" title="vAccess"><i class="fas fa-shield-alt"></i></button>
|
||||
<button class="icon-btn" onclick="editTestSite(0)" title="Редактировать"><i class="fas fa-edit"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Тестовый проект</td>
|
||||
<td><code class="clickable-link" onclick="openTestLink('http://test.local')">test.local <i class="fas fa-external-link-alt"></i></code></td>
|
||||
<td><code>*.test.local, test.com</code></td>
|
||||
<td><span class="badge badge-online">active</span></td>
|
||||
<td><code>index.php</code></td>
|
||||
<td>
|
||||
<button class="icon-btn" onclick="openSiteFolder('test.local')" title="Открыть папку"><i class="fas fa-folder-open"></i></button>
|
||||
<button class="icon-btn" onclick="editVAccess('test.local', false)" title="vAccess"><i class="fas fa-shield-alt"></i></button>
|
||||
<button class="icon-btn" onclick="editTestSite(1)" title="Редактировать"><i class="fas fa-edit"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>API сервис</td>
|
||||
<td><code class="clickable-link" onclick="openTestLink('http://api.example.com')">api.example.com <i class="fas fa-external-link-alt"></i></code></td>
|
||||
<td><code>*.api.example.com</code></td>
|
||||
<td><span class="badge badge-offline">inactive</span></td>
|
||||
<td><code>index.php</code></td>
|
||||
<td>
|
||||
<button class="icon-btn" onclick="openSiteFolder('api.example.com')" title="Открыть папку"><i class="fas fa-folder-open"></i></button>
|
||||
<button class="icon-btn" onclick="editVAccess('api.example.com', false)" title="vAccess"><i class="fas fa-shield-alt"></i></button>
|
||||
<button class="icon-btn" onclick="editTestSite(2)" title="Редактировать"><i class="fas fa-edit"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="sectionProxy">
|
||||
<h2 class="section-title">Прокси сервисы</h2>
|
||||
<div class="table-container">
|
||||
<table class="data-table" id="proxyTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Внешний домен</th>
|
||||
<th>Локальный адрес</th>
|
||||
<th>HTTPS</th>
|
||||
<th>Auto HTTPS</th>
|
||||
<th>Статус</th>
|
||||
<th>Действия</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><code class="clickable-link" onclick="openTestLink('https://git.example.ru')">git.example.ru <i class="fas fa-external-link-alt"></i></code></td>
|
||||
<td><code>127.0.0.1:3333</code></td>
|
||||
<td><span class="badge badge-no">HTTP</span></td>
|
||||
<td><span class="badge badge-yes">Да</span></td>
|
||||
<td><span class="badge badge-online">active</span></td>
|
||||
<td>
|
||||
<button class="icon-btn" onclick="editVAccess('git.example.ru', true)" title="vAccess"><i class="fas fa-shield-alt"></i></button>
|
||||
<button class="icon-btn" onclick="editTestProxy(0)" title="Редактировать"><i class="fas fa-edit"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code class="clickable-link" onclick="openTestLink('http://api.example.com')">api.example.com <i class="fas fa-external-link-alt"></i></code></td>
|
||||
<td><code>127.0.0.1:8080</code></td>
|
||||
<td><span class="badge badge-yes">HTTPS</span></td>
|
||||
<td><span class="badge badge-no">Нет</span></td>
|
||||
<td><span class="badge badge-online">active</span></td>
|
||||
<td>
|
||||
<button class="icon-btn" onclick="editVAccess('api.example.com', true)" title="vAccess"><i class="fas fa-shield-alt"></i></button>
|
||||
<button class="icon-btn" onclick="editTestProxy(1)" title="Редактировать"><i class="fas fa-edit"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code class="clickable-link" onclick="openTestLink('http://test.example.net')">test.example.net <i class="fas fa-external-link-alt"></i></code></td>
|
||||
<td><code>127.0.0.1:5000</code></td>
|
||||
<td><span class="badge badge-no">HTTP</span></td>
|
||||
<td><span class="badge badge-no">Нет</span></td>
|
||||
<td><span class="badge badge-offline">disabled</span></td>
|
||||
<td>
|
||||
<button class="icon-btn" onclick="editVAccess('test.example.net', true)" title="vAccess"><i class="fas fa-shield-alt"></i></button>
|
||||
<button class="icon-btn" onclick="editTestProxy(2)" title="Редактировать"><i class="fas fa-edit"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="sectionSettings" style="display: none;">
|
||||
<div class="settings-header">
|
||||
<h2 class="section-title">Настройки серверов</h2>
|
||||
<button class="action-btn save-btn" id="saveSettingsBtn">
|
||||
<i class="fas fa-save"></i>
|
||||
<span>Сохранить и перезапустить</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-grid">
|
||||
<div class="settings-card">
|
||||
<h3 class="settings-card-title"><i class="fas fa-database"></i> MySQL сервер</h3>
|
||||
<div class="settings-form">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Host адрес:</label>
|
||||
<input type="text" class="form-input" id="mysqlHost" placeholder="127.0.0.1">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Порт:</label>
|
||||
<input type="number" class="form-input" id="mysqlPort" placeholder="3306">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<h3 class="settings-card-title"><i class="fab fa-php"></i> PHP сервер</h3>
|
||||
<div class="settings-form">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Host адрес:</label>
|
||||
<input type="text" class="form-input" id="phpHost" placeholder="localhost">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label">Порт:</label>
|
||||
<input type="number" class="form-input" id="phpPort" placeholder="8000">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-card">
|
||||
<h3 class="settings-card-title"><i class="fas fa-network-wired"></i> Proxy Manager</h3>
|
||||
<div class="settings-form">
|
||||
<div class="form-group">
|
||||
<label class="form-label">Статус:</label>
|
||||
<div class="toggle-wrapper">
|
||||
<label class="toggle-switch">
|
||||
<input type="checkbox" id="proxyEnabled">
|
||||
<span class="toggle-slider"></span>
|
||||
</label>
|
||||
<span class="toggle-label">Proxy Manager</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-info">
|
||||
⚡ Применяется моментально без перезапуска серверов. При выключении все прокси правила будут отключены.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section" id="sectionVAccessEditor" style="display: none;">
|
||||
<div class="vaccess-page">
|
||||
<!-- Хлебные крошки с вкладками -->
|
||||
<div class="breadcrumbs" id="vAccessBreadcrumbs">
|
||||
<div class="breadcrumbs-left">
|
||||
<button class="breadcrumb-item" onclick="backToMain()">
|
||||
<i class="fas fa-arrow-left"></i> Назад
|
||||
</button>
|
||||
<span class="breadcrumb-separator">/</span>
|
||||
<span class="breadcrumb-item active" id="breadcrumbHost">example.com</span>
|
||||
</div>
|
||||
<div class="breadcrumbs-tabs">
|
||||
<button class="vaccess-tab active" data-tab="rules" onclick="switchVAccessTab('rules')">
|
||||
<i class="fas fa-list"></i>
|
||||
<span>Правила доступа</span>
|
||||
</button>
|
||||
<button class="vaccess-tab" data-tab="help" onclick="switchVAccessTab('help')">
|
||||
<i class="fas fa-question-circle"></i>
|
||||
<span>Инструкция</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Заголовок с кнопками -->
|
||||
<div class="vaccess-header">
|
||||
<div class="vaccess-title-block">
|
||||
<h2 class="vaccess-title">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
<span>Правила доступа vAccess</span>
|
||||
</h2>
|
||||
<p class="vaccess-subtitle" id="vAccessSubtitle">Управление правилами доступа для сайта</p>
|
||||
</div>
|
||||
<div class="vaccess-actions">
|
||||
<button class="action-btn save-btn" id="saveVAccessBtn">
|
||||
<i class="fas fa-save"></i>
|
||||
<span>Сохранить изменения</span>
|
||||
</button>
|
||||
<button class="action-btn" onclick="addVAccessRule()">
|
||||
<i class="fas fa-plus"></i>
|
||||
<span>Добавить правило</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Контент вкладки "Правила" -->
|
||||
<div class="vaccess-tab-content" id="vAccessRulesTab">
|
||||
<div class="vaccess-rules-container">
|
||||
<table class="vaccess-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="col-drag"></th>
|
||||
<th class="col-type">Тип</th>
|
||||
<th class="col-files">Расширения</th>
|
||||
<th class="col-paths">Пути доступа</th>
|
||||
<th class="col-ips">IP адреса</th>
|
||||
<th class="col-exceptions">Исключения</th>
|
||||
<th class="col-error">Ошибка</th>
|
||||
<th class="col-actions"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="vAccessTableBody">
|
||||
<!-- Правила будут добавлены динамически -->
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="vaccess-empty" id="vAccessEmpty" style="display: none;">
|
||||
<div class="empty-icon">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
</div>
|
||||
<h3>Нет правил доступа</h3>
|
||||
<p>Добавьте первое правило, чтобы начать управление доступом</p>
|
||||
<button class="action-btn" onclick="addVAccessRule()">
|
||||
<i class="fas fa-plus"></i>
|
||||
<span>Создать правило</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Контент вкладки "Инструкция" -->
|
||||
<div class="vaccess-tab-content" id="vAccessHelpTab" style="display: none;">
|
||||
<div class="vaccess-help">
|
||||
<div class="help-card">
|
||||
<h3><i class="fas fa-info-circle"></i> Принцип работы</h3>
|
||||
<ul>
|
||||
<li>Правила проверяются <strong>сверху вниз</strong> по порядку</li>
|
||||
<li>Первое подходящее правило срабатывает и завершает проверку</li>
|
||||
<li>Если ни одно правило не сработало - доступ <strong>разрешён</strong></li>
|
||||
<li>Перетаскивайте правила за <i class="fas fa-grip-vertical"></i> чтобы изменить порядок</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="help-card">
|
||||
<h3><i class="fas fa-sliders-h"></i> Параметры правил</h3>
|
||||
<div class="help-params">
|
||||
<div class="help-param">
|
||||
<strong>type:</strong>
|
||||
<p><span class="badge badge-yes">Allow</span> (разрешить) или <span class="badge badge-no">Disable</span> (запретить)</p>
|
||||
</div>
|
||||
<div class="help-param">
|
||||
<strong>Расширения файлов:</strong>
|
||||
<p>Список расширений через запятую (<code>*.php</code>, <code>*.exe</code>)</p>
|
||||
</div>
|
||||
<div class="help-param">
|
||||
<strong>Пути доступа:</strong>
|
||||
<p>Список путей через запятую (<code>/admin/*</code>, <code>/api/*</code>)</p>
|
||||
</div>
|
||||
<div class="help-param">
|
||||
<strong>IP адреса:</strong>
|
||||
<p>Список IP адресов через запятую (<code>192.168.1.1</code>, <code>10.0.0.5</code>)</p>
|
||||
<p class="help-warning"><i class="fas fa-exclamation-triangle"></i> Используется реальный IP соединения (не заголовки прокси!)</p>
|
||||
</div>
|
||||
<div class="help-param">
|
||||
<strong>Исключения:</strong>
|
||||
<p>Пути-исключения через запятую (<code>/bot/*</code>, <code>/public/*</code>). Правило НЕ применяется к этим путям</p>
|
||||
</div>
|
||||
<div class="help-param">
|
||||
<strong>Страница ошибки:</strong>
|
||||
<p>Куда перенаправить при блокировке:</p>
|
||||
<ul>
|
||||
<li><code>404</code> - стандартная страница ошибки</li>
|
||||
<li><code>https://site.com</code> - внешний редирект</li>
|
||||
<li><code>/error.html</code> - локальная страница</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="help-card">
|
||||
<h3><i class="fas fa-search"></i> Паттерны</h3>
|
||||
<div class="help-patterns">
|
||||
<div class="pattern-item">
|
||||
<code>*.ext</code>
|
||||
<span>Любой файл с расширением .ext</span>
|
||||
</div>
|
||||
<div class="pattern-item">
|
||||
<code>no_extension</code>
|
||||
<span>Файлы без расширения (например: /api/users, /admin)</span>
|
||||
</div>
|
||||
<div class="pattern-item">
|
||||
<code>/path/*</code>
|
||||
<span>Все файлы в папке /path/ и подпапках</span>
|
||||
</div>
|
||||
<div class="pattern-item">
|
||||
<code>/file.php</code>
|
||||
<span>Конкретный файл</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="help-card help-examples">
|
||||
<h3><i class="fas fa-lightbulb"></i> Примеры правил</h3>
|
||||
|
||||
<div class="help-example">
|
||||
<h4>1. Запретить выполнение PHP в uploads</h4>
|
||||
<div class="example-rule">
|
||||
<div><strong>Тип:</strong> <span class="badge badge-no">Disable</span></div>
|
||||
<div><strong>Расширения:</strong> <code>*.php</code></div>
|
||||
<div><strong>Пути:</strong> <code>/uploads/*</code></div>
|
||||
<div><strong>Ошибка:</strong> <code>404</code></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="help-example">
|
||||
<h4>2. Разрешить админку только с определённых IP</h4>
|
||||
<div class="example-rule">
|
||||
<div><strong>Тип:</strong> <span class="badge badge-yes">Allow</span></div>
|
||||
<div><strong>Пути:</strong> <code>/admin/*</code></div>
|
||||
<div><strong>IP:</strong> <code>192.168.1.100, 127.0.0.1</code></div>
|
||||
<div><strong>Ошибка:</strong> <code>404</code></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="help-example">
|
||||
<h4>3. Блокировать определённые IP для всего сайта</h4>
|
||||
<div class="example-rule">
|
||||
<div><strong>Тип:</strong> <span class="badge badge-no">Disable</span></div>
|
||||
<div><strong>IP:</strong> <code>192.168.1.50, 10.0.0.99</code></div>
|
||||
<div><strong>Ошибка:</strong> <code>https://google.com</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<p>vServer Admin Panel © 2025 | Автор: Суманеев Роман</p>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<div id="notification" class="notification"></div>
|
||||
|
||||
<div id="appLoader" class="app-loader">
|
||||
<div class="loader-content">
|
||||
<div class="loader-icon">🚀</div>
|
||||
<div class="loader-text">Запуск vServer...</div>
|
||||
<div class="loader-spinner"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="modalOverlay" class="modal-overlay">
|
||||
<div class="modal-window">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" id="modalTitle">Редактирование</h3>
|
||||
<button class="modal-close-btn" id="modalCloseBtn">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-content" id="modalContent"></div>
|
||||
<div class="modal-footer">
|
||||
<button class="action-btn" id="modalCancelBtn">
|
||||
<i class="fas fa-times"></i>
|
||||
<span>Отмена</span>
|
||||
</button>
|
||||
<button class="action-btn save-btn" id="modalSaveBtn">
|
||||
<i class="fas fa-save"></i>
|
||||
<span>Сохранить</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="fieldEditorOverlay" class="modal-overlay">
|
||||
<div class="modal-window" style="max-width: 600px;">
|
||||
<div class="modal-header">
|
||||
<h3 class="modal-title" id="fieldEditorTitle">Редактирование поля</h3>
|
||||
<button class="modal-close-btn" onclick="closeFieldEditor()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-content" id="fieldEditorContent"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="wailsjs/runtime/runtime.js"></script>
|
||||
<script src="wailsjs/go/admin/App.js"></script>
|
||||
<script type="module" src="assets/js/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
17
Backend/admin/frontend/wailsjs/go/admin/App.d.ts
vendored
Normal file
17
Backend/admin/frontend/wailsjs/go/admin/App.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {services} from '../models';
|
||||
import {proxy} from '../models';
|
||||
import {sites} from '../models';
|
||||
|
||||
export function CheckServicesReady():Promise<boolean>;
|
||||
|
||||
export function GetAllServicesStatus():Promise<services.AllServicesStatus>;
|
||||
|
||||
export function GetProxyList():Promise<Array<proxy.ProxyInfo>>;
|
||||
|
||||
export function GetSitesList():Promise<Array<sites.SiteInfo>>;
|
||||
|
||||
export function StartServer():Promise<string>;
|
||||
|
||||
export function StopServer():Promise<string>;
|
||||
27
Backend/admin/frontend/wailsjs/go/admin/App.js
Normal file
27
Backend/admin/frontend/wailsjs/go/admin/App.js
Normal file
@@ -0,0 +1,27 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function CheckServicesReady() {
|
||||
return window['go']['admin']['App']['CheckServicesReady']();
|
||||
}
|
||||
|
||||
export function GetAllServicesStatus() {
|
||||
return window['go']['admin']['App']['GetAllServicesStatus']();
|
||||
}
|
||||
|
||||
export function GetProxyList() {
|
||||
return window['go']['admin']['App']['GetProxyList']();
|
||||
}
|
||||
|
||||
export function GetSitesList() {
|
||||
return window['go']['admin']['App']['GetSitesList']();
|
||||
}
|
||||
|
||||
export function StartServer() {
|
||||
return window['go']['admin']['App']['StartServer']();
|
||||
}
|
||||
|
||||
export function StopServer() {
|
||||
return window['go']['admin']['App']['StopServer']();
|
||||
}
|
||||
119
Backend/admin/frontend/wailsjs/go/models.ts
Normal file
119
Backend/admin/frontend/wailsjs/go/models.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
export namespace proxy {
|
||||
|
||||
export class ProxyInfo {
|
||||
enable: boolean;
|
||||
external_domain: string;
|
||||
local_address: string;
|
||||
local_port: string;
|
||||
service_https_use: boolean;
|
||||
auto_https: boolean;
|
||||
status: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ProxyInfo(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.enable = source["enable"];
|
||||
this.external_domain = source["external_domain"];
|
||||
this.local_address = source["local_address"];
|
||||
this.local_port = source["local_port"];
|
||||
this.service_https_use = source["service_https_use"];
|
||||
this.auto_https = source["auto_https"];
|
||||
this.status = source["status"];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace services {
|
||||
|
||||
export class ServiceStatus {
|
||||
name: string;
|
||||
status: boolean;
|
||||
port: string;
|
||||
requests: number;
|
||||
info: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ServiceStatus(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.status = source["status"];
|
||||
this.port = source["port"];
|
||||
this.requests = source["requests"];
|
||||
this.info = source["info"];
|
||||
}
|
||||
}
|
||||
export class AllServicesStatus {
|
||||
http: ServiceStatus;
|
||||
https: ServiceStatus;
|
||||
mysql: ServiceStatus;
|
||||
php: ServiceStatus;
|
||||
proxy: ServiceStatus;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AllServicesStatus(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.http = this.convertValues(source["http"], ServiceStatus);
|
||||
this.https = this.convertValues(source["https"], ServiceStatus);
|
||||
this.mysql = this.convertValues(source["mysql"], ServiceStatus);
|
||||
this.php = this.convertValues(source["php"], ServiceStatus);
|
||||
this.proxy = this.convertValues(source["proxy"], ServiceStatus);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace sites {
|
||||
|
||||
export class SiteInfo {
|
||||
name: string;
|
||||
host: string;
|
||||
alias: string[];
|
||||
status: string;
|
||||
root_file: string;
|
||||
root_file_routing: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new SiteInfo(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.host = source["host"];
|
||||
this.alias = source["alias"];
|
||||
this.status = source["status"];
|
||||
this.root_file = source["root_file"];
|
||||
this.root_file_routing = source["root_file_routing"];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
24
Backend/admin/frontend/wailsjs/runtime/package.json
Normal file
24
Backend/admin/frontend/wailsjs/runtime/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@wailsapp/runtime",
|
||||
"version": "2.0.0",
|
||||
"description": "Wails Javascript runtime library",
|
||||
"main": "runtime.js",
|
||||
"types": "runtime.d.ts",
|
||||
"scripts": {
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wailsapp/wails.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Wails",
|
||||
"Javascript",
|
||||
"Go"
|
||||
],
|
||||
"author": "Lea Anthony <lea.anthony@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/wailsapp/wails/issues"
|
||||
},
|
||||
"homepage": "https://github.com/wailsapp/wails#readme"
|
||||
}
|
||||
249
Backend/admin/frontend/wailsjs/runtime/runtime.d.ts
vendored
Normal file
249
Backend/admin/frontend/wailsjs/runtime/runtime.d.ts
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Size {
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
export interface Screen {
|
||||
isCurrent: boolean;
|
||||
isPrimary: boolean;
|
||||
width : number
|
||||
height : number
|
||||
}
|
||||
|
||||
// Environment information such as platform, buildtype, ...
|
||||
export interface EnvironmentInfo {
|
||||
buildType: string;
|
||||
platform: string;
|
||||
arch: string;
|
||||
}
|
||||
|
||||
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
|
||||
// emits the given event. Optional data may be passed with the event.
|
||||
// This will trigger any event listeners.
|
||||
export function EventsEmit(eventName: string, ...data: any): void;
|
||||
|
||||
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
|
||||
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
|
||||
|
||||
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
|
||||
// sets up a listener for the given event name, but will only trigger a given number times.
|
||||
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
|
||||
|
||||
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
|
||||
// sets up a listener for the given event name, but will only trigger once.
|
||||
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
|
||||
|
||||
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
|
||||
// unregisters the listener for the given event name.
|
||||
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
|
||||
|
||||
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
|
||||
// unregisters all listeners.
|
||||
export function EventsOffAll(): void;
|
||||
|
||||
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
|
||||
// logs the given message as a raw message
|
||||
export function LogPrint(message: string): void;
|
||||
|
||||
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
|
||||
// logs the given message at the `trace` log level.
|
||||
export function LogTrace(message: string): void;
|
||||
|
||||
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
|
||||
// logs the given message at the `debug` log level.
|
||||
export function LogDebug(message: string): void;
|
||||
|
||||
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
|
||||
// logs the given message at the `error` log level.
|
||||
export function LogError(message: string): void;
|
||||
|
||||
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
|
||||
// logs the given message at the `fatal` log level.
|
||||
// The application will quit after calling this method.
|
||||
export function LogFatal(message: string): void;
|
||||
|
||||
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
|
||||
// logs the given message at the `info` log level.
|
||||
export function LogInfo(message: string): void;
|
||||
|
||||
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
|
||||
// logs the given message at the `warning` log level.
|
||||
export function LogWarning(message: string): void;
|
||||
|
||||
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
|
||||
// Forces a reload by the main application as well as connected browsers.
|
||||
export function WindowReload(): void;
|
||||
|
||||
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
|
||||
// Reloads the application frontend.
|
||||
export function WindowReloadApp(): void;
|
||||
|
||||
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
|
||||
// Sets the window AlwaysOnTop or not on top.
|
||||
export function WindowSetAlwaysOnTop(b: boolean): void;
|
||||
|
||||
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
|
||||
// *Windows only*
|
||||
// Sets window theme to system default (dark/light).
|
||||
export function WindowSetSystemDefaultTheme(): void;
|
||||
|
||||
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
|
||||
// *Windows only*
|
||||
// Sets window to light theme.
|
||||
export function WindowSetLightTheme(): void;
|
||||
|
||||
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
|
||||
// *Windows only*
|
||||
// Sets window to dark theme.
|
||||
export function WindowSetDarkTheme(): void;
|
||||
|
||||
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
|
||||
// Centers the window on the monitor the window is currently on.
|
||||
export function WindowCenter(): void;
|
||||
|
||||
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
|
||||
// Sets the text in the window title bar.
|
||||
export function WindowSetTitle(title: string): void;
|
||||
|
||||
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
|
||||
// Makes the window full screen.
|
||||
export function WindowFullscreen(): void;
|
||||
|
||||
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
|
||||
// Restores the previous window dimensions and position prior to full screen.
|
||||
export function WindowUnfullscreen(): void;
|
||||
|
||||
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
|
||||
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
|
||||
export function WindowIsFullscreen(): Promise<boolean>;
|
||||
|
||||
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
|
||||
// Sets the width and height of the window.
|
||||
export function WindowSetSize(width: number, height: number): void;
|
||||
|
||||
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
|
||||
// Gets the width and height of the window.
|
||||
export function WindowGetSize(): Promise<Size>;
|
||||
|
||||
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
|
||||
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMaxSize(width: number, height: number): void;
|
||||
|
||||
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
|
||||
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMinSize(width: number, height: number): void;
|
||||
|
||||
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
|
||||
// Sets the window position relative to the monitor the window is currently on.
|
||||
export function WindowSetPosition(x: number, y: number): void;
|
||||
|
||||
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
|
||||
// Gets the window position relative to the monitor the window is currently on.
|
||||
export function WindowGetPosition(): Promise<Position>;
|
||||
|
||||
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
|
||||
// Hides the window.
|
||||
export function WindowHide(): void;
|
||||
|
||||
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
|
||||
// Shows the window, if it is currently hidden.
|
||||
export function WindowShow(): void;
|
||||
|
||||
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
|
||||
// Maximises the window to fill the screen.
|
||||
export function WindowMaximise(): void;
|
||||
|
||||
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
|
||||
// Toggles between Maximised and UnMaximised.
|
||||
export function WindowToggleMaximise(): void;
|
||||
|
||||
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
|
||||
// Restores the window to the dimensions and position prior to maximising.
|
||||
export function WindowUnmaximise(): void;
|
||||
|
||||
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
|
||||
// Returns the state of the window, i.e. whether the window is maximised or not.
|
||||
export function WindowIsMaximised(): Promise<boolean>;
|
||||
|
||||
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
|
||||
// Minimises the window.
|
||||
export function WindowMinimise(): void;
|
||||
|
||||
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
|
||||
// Restores the window to the dimensions and position prior to minimising.
|
||||
export function WindowUnminimise(): void;
|
||||
|
||||
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
|
||||
// Returns the state of the window, i.e. whether the window is minimised or not.
|
||||
export function WindowIsMinimised(): Promise<boolean>;
|
||||
|
||||
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
|
||||
// Returns the state of the window, i.e. whether the window is normal or not.
|
||||
export function WindowIsNormal(): Promise<boolean>;
|
||||
|
||||
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
|
||||
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
|
||||
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
|
||||
|
||||
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
|
||||
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
|
||||
export function ScreenGetAll(): Promise<Screen[]>;
|
||||
|
||||
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
|
||||
// Opens the given URL in the system browser.
|
||||
export function BrowserOpenURL(url: string): void;
|
||||
|
||||
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
|
||||
// Returns information about the environment
|
||||
export function Environment(): Promise<EnvironmentInfo>;
|
||||
|
||||
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
|
||||
// Quits the application.
|
||||
export function Quit(): void;
|
||||
|
||||
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
|
||||
// Hides the application.
|
||||
export function Hide(): void;
|
||||
|
||||
// [Show](https://wails.io/docs/reference/runtime/intro#show)
|
||||
// Shows the application.
|
||||
export function Show(): void;
|
||||
|
||||
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
|
||||
// Returns the current text stored on clipboard
|
||||
export function ClipboardGetText(): Promise<string>;
|
||||
|
||||
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
|
||||
// Sets a text on the clipboard
|
||||
export function ClipboardSetText(text: string): Promise<boolean>;
|
||||
|
||||
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
|
||||
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
|
||||
|
||||
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
|
||||
// OnFileDropOff removes the drag and drop listeners and handlers.
|
||||
export function OnFileDropOff() :void
|
||||
|
||||
// Check if the file path resolver is available
|
||||
export function CanResolveFilePaths(): boolean;
|
||||
|
||||
// Resolves file paths for an array of files
|
||||
export function ResolveFilePaths(files: File[]): void
|
||||
242
Backend/admin/frontend/wailsjs/runtime/runtime.js
Normal file
242
Backend/admin/frontend/wailsjs/runtime/runtime.js
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export function LogPrint(message) {
|
||||
window.runtime.LogPrint(message);
|
||||
}
|
||||
|
||||
export function LogTrace(message) {
|
||||
window.runtime.LogTrace(message);
|
||||
}
|
||||
|
||||
export function LogDebug(message) {
|
||||
window.runtime.LogDebug(message);
|
||||
}
|
||||
|
||||
export function LogInfo(message) {
|
||||
window.runtime.LogInfo(message);
|
||||
}
|
||||
|
||||
export function LogWarning(message) {
|
||||
window.runtime.LogWarning(message);
|
||||
}
|
||||
|
||||
export function LogError(message) {
|
||||
window.runtime.LogError(message);
|
||||
}
|
||||
|
||||
export function LogFatal(message) {
|
||||
window.runtime.LogFatal(message);
|
||||
}
|
||||
|
||||
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
|
||||
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
|
||||
}
|
||||
|
||||
export function EventsOn(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, -1);
|
||||
}
|
||||
|
||||
export function EventsOff(eventName, ...additionalEventNames) {
|
||||
return window.runtime.EventsOff(eventName, ...additionalEventNames);
|
||||
}
|
||||
|
||||
export function EventsOffAll() {
|
||||
return window.runtime.EventsOffAll();
|
||||
}
|
||||
|
||||
export function EventsOnce(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, 1);
|
||||
}
|
||||
|
||||
export function EventsEmit(eventName) {
|
||||
let args = [eventName].slice.call(arguments);
|
||||
return window.runtime.EventsEmit.apply(null, args);
|
||||
}
|
||||
|
||||
export function WindowReload() {
|
||||
window.runtime.WindowReload();
|
||||
}
|
||||
|
||||
export function WindowReloadApp() {
|
||||
window.runtime.WindowReloadApp();
|
||||
}
|
||||
|
||||
export function WindowSetAlwaysOnTop(b) {
|
||||
window.runtime.WindowSetAlwaysOnTop(b);
|
||||
}
|
||||
|
||||
export function WindowSetSystemDefaultTheme() {
|
||||
window.runtime.WindowSetSystemDefaultTheme();
|
||||
}
|
||||
|
||||
export function WindowSetLightTheme() {
|
||||
window.runtime.WindowSetLightTheme();
|
||||
}
|
||||
|
||||
export function WindowSetDarkTheme() {
|
||||
window.runtime.WindowSetDarkTheme();
|
||||
}
|
||||
|
||||
export function WindowCenter() {
|
||||
window.runtime.WindowCenter();
|
||||
}
|
||||
|
||||
export function WindowSetTitle(title) {
|
||||
window.runtime.WindowSetTitle(title);
|
||||
}
|
||||
|
||||
export function WindowFullscreen() {
|
||||
window.runtime.WindowFullscreen();
|
||||
}
|
||||
|
||||
export function WindowUnfullscreen() {
|
||||
window.runtime.WindowUnfullscreen();
|
||||
}
|
||||
|
||||
export function WindowIsFullscreen() {
|
||||
return window.runtime.WindowIsFullscreen();
|
||||
}
|
||||
|
||||
export function WindowGetSize() {
|
||||
return window.runtime.WindowGetSize();
|
||||
}
|
||||
|
||||
export function WindowSetSize(width, height) {
|
||||
window.runtime.WindowSetSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetMaxSize(width, height) {
|
||||
window.runtime.WindowSetMaxSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetMinSize(width, height) {
|
||||
window.runtime.WindowSetMinSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetPosition(x, y) {
|
||||
window.runtime.WindowSetPosition(x, y);
|
||||
}
|
||||
|
||||
export function WindowGetPosition() {
|
||||
return window.runtime.WindowGetPosition();
|
||||
}
|
||||
|
||||
export function WindowHide() {
|
||||
window.runtime.WindowHide();
|
||||
}
|
||||
|
||||
export function WindowShow() {
|
||||
window.runtime.WindowShow();
|
||||
}
|
||||
|
||||
export function WindowMaximise() {
|
||||
window.runtime.WindowMaximise();
|
||||
}
|
||||
|
||||
export function WindowToggleMaximise() {
|
||||
window.runtime.WindowToggleMaximise();
|
||||
}
|
||||
|
||||
export function WindowUnmaximise() {
|
||||
window.runtime.WindowUnmaximise();
|
||||
}
|
||||
|
||||
export function WindowIsMaximised() {
|
||||
return window.runtime.WindowIsMaximised();
|
||||
}
|
||||
|
||||
export function WindowMinimise() {
|
||||
window.runtime.WindowMinimise();
|
||||
}
|
||||
|
||||
export function WindowUnminimise() {
|
||||
window.runtime.WindowUnminimise();
|
||||
}
|
||||
|
||||
export function WindowSetBackgroundColour(R, G, B, A) {
|
||||
window.runtime.WindowSetBackgroundColour(R, G, B, A);
|
||||
}
|
||||
|
||||
export function ScreenGetAll() {
|
||||
return window.runtime.ScreenGetAll();
|
||||
}
|
||||
|
||||
export function WindowIsMinimised() {
|
||||
return window.runtime.WindowIsMinimised();
|
||||
}
|
||||
|
||||
export function WindowIsNormal() {
|
||||
return window.runtime.WindowIsNormal();
|
||||
}
|
||||
|
||||
export function BrowserOpenURL(url) {
|
||||
window.runtime.BrowserOpenURL(url);
|
||||
}
|
||||
|
||||
export function Environment() {
|
||||
return window.runtime.Environment();
|
||||
}
|
||||
|
||||
export function Quit() {
|
||||
window.runtime.Quit();
|
||||
}
|
||||
|
||||
export function Hide() {
|
||||
window.runtime.Hide();
|
||||
}
|
||||
|
||||
export function Show() {
|
||||
window.runtime.Show();
|
||||
}
|
||||
|
||||
export function ClipboardGetText() {
|
||||
return window.runtime.ClipboardGetText();
|
||||
}
|
||||
|
||||
export function ClipboardSetText(text) {
|
||||
return window.runtime.ClipboardSetText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||
*
|
||||
* @export
|
||||
* @callback OnFileDropCallback
|
||||
* @param {number} x - x coordinate of the drop
|
||||
* @param {number} y - y coordinate of the drop
|
||||
* @param {string[]} paths - A list of file paths.
|
||||
*/
|
||||
|
||||
/**
|
||||
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||
*
|
||||
* @export
|
||||
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
|
||||
*/
|
||||
export function OnFileDrop(callback, useDropTarget) {
|
||||
return window.runtime.OnFileDrop(callback, useDropTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* OnFileDropOff removes the drag and drop listeners and handlers.
|
||||
*/
|
||||
export function OnFileDropOff() {
|
||||
return window.runtime.OnFileDropOff();
|
||||
}
|
||||
|
||||
export function CanResolveFilePaths() {
|
||||
return window.runtime.CanResolveFilePaths();
|
||||
}
|
||||
|
||||
export function ResolveFilePaths(files) {
|
||||
return window.runtime.ResolveFilePaths(files);
|
||||
}
|
||||
54
Backend/admin/frontend/wailsjs/wailsjs/go/admin/App.d.ts
vendored
Normal file
54
Backend/admin/frontend/wailsjs/wailsjs/go/admin/App.d.ts
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {services} from '../models';
|
||||
import {proxy} from '../models';
|
||||
import {sites} from '../models';
|
||||
import {vaccess} from '../models';
|
||||
|
||||
export function CheckServicesReady():Promise<boolean>;
|
||||
|
||||
export function DisableProxyService():Promise<string>;
|
||||
|
||||
export function EnableProxyService():Promise<string>;
|
||||
|
||||
export function GetAllServicesStatus():Promise<services.AllServicesStatus>;
|
||||
|
||||
export function GetConfig():Promise<any>;
|
||||
|
||||
export function GetProxyList():Promise<Array<proxy.ProxyInfo>>;
|
||||
|
||||
export function GetSitesList():Promise<Array<sites.SiteInfo>>;
|
||||
|
||||
export function GetVAccessRules(arg1:string,arg2:boolean):Promise<vaccess.VAccessConfig>;
|
||||
|
||||
export function OpenSiteFolder(arg1:string):Promise<string>;
|
||||
|
||||
export function ReloadConfig():Promise<string>;
|
||||
|
||||
export function RestartAllServices():Promise<string>;
|
||||
|
||||
export function SaveConfig(arg1:string):Promise<string>;
|
||||
|
||||
export function SaveVAccessRules(arg1:string,arg2:boolean,arg3:string):Promise<string>;
|
||||
|
||||
export function StartHTTPSService():Promise<string>;
|
||||
|
||||
export function StartHTTPService():Promise<string>;
|
||||
|
||||
export function StartMySQLService():Promise<string>;
|
||||
|
||||
export function StartPHPService():Promise<string>;
|
||||
|
||||
export function StartServer():Promise<string>;
|
||||
|
||||
export function StopHTTPSService():Promise<string>;
|
||||
|
||||
export function StopHTTPService():Promise<string>;
|
||||
|
||||
export function StopMySQLService():Promise<string>;
|
||||
|
||||
export function StopPHPService():Promise<string>;
|
||||
|
||||
export function StopServer():Promise<string>;
|
||||
|
||||
export function UpdateSiteCache():Promise<string>;
|
||||
99
Backend/admin/frontend/wailsjs/wailsjs/go/admin/App.js
Normal file
99
Backend/admin/frontend/wailsjs/wailsjs/go/admin/App.js
Normal file
@@ -0,0 +1,99 @@
|
||||
// @ts-check
|
||||
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
|
||||
export function CheckServicesReady() {
|
||||
return window['go']['admin']['App']['CheckServicesReady']();
|
||||
}
|
||||
|
||||
export function DisableProxyService() {
|
||||
return window['go']['admin']['App']['DisableProxyService']();
|
||||
}
|
||||
|
||||
export function EnableProxyService() {
|
||||
return window['go']['admin']['App']['EnableProxyService']();
|
||||
}
|
||||
|
||||
export function GetAllServicesStatus() {
|
||||
return window['go']['admin']['App']['GetAllServicesStatus']();
|
||||
}
|
||||
|
||||
export function GetConfig() {
|
||||
return window['go']['admin']['App']['GetConfig']();
|
||||
}
|
||||
|
||||
export function GetProxyList() {
|
||||
return window['go']['admin']['App']['GetProxyList']();
|
||||
}
|
||||
|
||||
export function GetSitesList() {
|
||||
return window['go']['admin']['App']['GetSitesList']();
|
||||
}
|
||||
|
||||
export function GetVAccessRules(arg1, arg2) {
|
||||
return window['go']['admin']['App']['GetVAccessRules'](arg1, arg2);
|
||||
}
|
||||
|
||||
export function OpenSiteFolder(arg1) {
|
||||
return window['go']['admin']['App']['OpenSiteFolder'](arg1);
|
||||
}
|
||||
|
||||
export function ReloadConfig() {
|
||||
return window['go']['admin']['App']['ReloadConfig']();
|
||||
}
|
||||
|
||||
export function RestartAllServices() {
|
||||
return window['go']['admin']['App']['RestartAllServices']();
|
||||
}
|
||||
|
||||
export function SaveConfig(arg1) {
|
||||
return window['go']['admin']['App']['SaveConfig'](arg1);
|
||||
}
|
||||
|
||||
export function SaveVAccessRules(arg1, arg2, arg3) {
|
||||
return window['go']['admin']['App']['SaveVAccessRules'](arg1, arg2, arg3);
|
||||
}
|
||||
|
||||
export function StartHTTPSService() {
|
||||
return window['go']['admin']['App']['StartHTTPSService']();
|
||||
}
|
||||
|
||||
export function StartHTTPService() {
|
||||
return window['go']['admin']['App']['StartHTTPService']();
|
||||
}
|
||||
|
||||
export function StartMySQLService() {
|
||||
return window['go']['admin']['App']['StartMySQLService']();
|
||||
}
|
||||
|
||||
export function StartPHPService() {
|
||||
return window['go']['admin']['App']['StartPHPService']();
|
||||
}
|
||||
|
||||
export function StartServer() {
|
||||
return window['go']['admin']['App']['StartServer']();
|
||||
}
|
||||
|
||||
export function StopHTTPSService() {
|
||||
return window['go']['admin']['App']['StopHTTPSService']();
|
||||
}
|
||||
|
||||
export function StopHTTPService() {
|
||||
return window['go']['admin']['App']['StopHTTPService']();
|
||||
}
|
||||
|
||||
export function StopMySQLService() {
|
||||
return window['go']['admin']['App']['StopMySQLService']();
|
||||
}
|
||||
|
||||
export function StopPHPService() {
|
||||
return window['go']['admin']['App']['StopPHPService']();
|
||||
}
|
||||
|
||||
export function StopServer() {
|
||||
return window['go']['admin']['App']['StopServer']();
|
||||
}
|
||||
|
||||
export function UpdateSiteCache() {
|
||||
return window['go']['admin']['App']['UpdateSiteCache']();
|
||||
}
|
||||
174
Backend/admin/frontend/wailsjs/wailsjs/go/models.ts
Normal file
174
Backend/admin/frontend/wailsjs/wailsjs/go/models.ts
Normal file
@@ -0,0 +1,174 @@
|
||||
export namespace proxy {
|
||||
|
||||
export class ProxyInfo {
|
||||
enable: boolean;
|
||||
external_domain: string;
|
||||
local_address: string;
|
||||
local_port: string;
|
||||
service_https_use: boolean;
|
||||
auto_https: boolean;
|
||||
status: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ProxyInfo(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.enable = source["enable"];
|
||||
this.external_domain = source["external_domain"];
|
||||
this.local_address = source["local_address"];
|
||||
this.local_port = source["local_port"];
|
||||
this.service_https_use = source["service_https_use"];
|
||||
this.auto_https = source["auto_https"];
|
||||
this.status = source["status"];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace services {
|
||||
|
||||
export class ServiceStatus {
|
||||
name: string;
|
||||
status: boolean;
|
||||
port: string;
|
||||
info: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new ServiceStatus(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.status = source["status"];
|
||||
this.port = source["port"];
|
||||
this.info = source["info"];
|
||||
}
|
||||
}
|
||||
export class AllServicesStatus {
|
||||
http: ServiceStatus;
|
||||
https: ServiceStatus;
|
||||
mysql: ServiceStatus;
|
||||
php: ServiceStatus;
|
||||
proxy: ServiceStatus;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new AllServicesStatus(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.http = this.convertValues(source["http"], ServiceStatus);
|
||||
this.https = this.convertValues(source["https"], ServiceStatus);
|
||||
this.mysql = this.convertValues(source["mysql"], ServiceStatus);
|
||||
this.php = this.convertValues(source["php"], ServiceStatus);
|
||||
this.proxy = this.convertValues(source["proxy"], ServiceStatus);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace sites {
|
||||
|
||||
export class SiteInfo {
|
||||
name: string;
|
||||
host: string;
|
||||
alias: string[];
|
||||
status: string;
|
||||
root_file: string;
|
||||
root_file_routing: boolean;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new SiteInfo(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.name = source["name"];
|
||||
this.host = source["host"];
|
||||
this.alias = source["alias"];
|
||||
this.status = source["status"];
|
||||
this.root_file = source["root_file"];
|
||||
this.root_file_routing = source["root_file_routing"];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export namespace vaccess {
|
||||
|
||||
export class VAccessRule {
|
||||
type: string;
|
||||
type_file: string[];
|
||||
path_access: string[];
|
||||
ip_list: string[];
|
||||
exceptions_dir: string[];
|
||||
url_error: string;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new VAccessRule(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.type = source["type"];
|
||||
this.type_file = source["type_file"];
|
||||
this.path_access = source["path_access"];
|
||||
this.ip_list = source["ip_list"];
|
||||
this.exceptions_dir = source["exceptions_dir"];
|
||||
this.url_error = source["url_error"];
|
||||
}
|
||||
}
|
||||
export class VAccessConfig {
|
||||
rules: VAccessRule[];
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new VAccessConfig(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.rules = this.convertValues(source["rules"], VAccessRule);
|
||||
}
|
||||
|
||||
convertValues(a: any, classs: any, asMap: boolean = false): any {
|
||||
if (!a) {
|
||||
return a;
|
||||
}
|
||||
if (a.slice && a.map) {
|
||||
return (a as any[]).map(elem => this.convertValues(elem, classs));
|
||||
} else if ("object" === typeof a) {
|
||||
if (asMap) {
|
||||
for (const key of Object.keys(a)) {
|
||||
a[key] = new classs(a[key]);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
return new classs(a);
|
||||
}
|
||||
return a;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
24
Backend/admin/frontend/wailsjs/wailsjs/runtime/package.json
Normal file
24
Backend/admin/frontend/wailsjs/wailsjs/runtime/package.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "@wailsapp/runtime",
|
||||
"version": "2.0.0",
|
||||
"description": "Wails Javascript runtime library",
|
||||
"main": "runtime.js",
|
||||
"types": "runtime.d.ts",
|
||||
"scripts": {
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/wailsapp/wails.git"
|
||||
},
|
||||
"keywords": [
|
||||
"Wails",
|
||||
"Javascript",
|
||||
"Go"
|
||||
],
|
||||
"author": "Lea Anthony <lea.anthony@gmail.com>",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/wailsapp/wails/issues"
|
||||
},
|
||||
"homepage": "https://github.com/wailsapp/wails#readme"
|
||||
}
|
||||
249
Backend/admin/frontend/wailsjs/wailsjs/runtime/runtime.d.ts
vendored
Normal file
249
Backend/admin/frontend/wailsjs/wailsjs/runtime/runtime.d.ts
vendored
Normal file
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export interface Position {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export interface Size {
|
||||
w: number;
|
||||
h: number;
|
||||
}
|
||||
|
||||
export interface Screen {
|
||||
isCurrent: boolean;
|
||||
isPrimary: boolean;
|
||||
width : number
|
||||
height : number
|
||||
}
|
||||
|
||||
// Environment information such as platform, buildtype, ...
|
||||
export interface EnvironmentInfo {
|
||||
buildType: string;
|
||||
platform: string;
|
||||
arch: string;
|
||||
}
|
||||
|
||||
// [EventsEmit](https://wails.io/docs/reference/runtime/events#eventsemit)
|
||||
// emits the given event. Optional data may be passed with the event.
|
||||
// This will trigger any event listeners.
|
||||
export function EventsEmit(eventName: string, ...data: any): void;
|
||||
|
||||
// [EventsOn](https://wails.io/docs/reference/runtime/events#eventson) sets up a listener for the given event name.
|
||||
export function EventsOn(eventName: string, callback: (...data: any) => void): () => void;
|
||||
|
||||
// [EventsOnMultiple](https://wails.io/docs/reference/runtime/events#eventsonmultiple)
|
||||
// sets up a listener for the given event name, but will only trigger a given number times.
|
||||
export function EventsOnMultiple(eventName: string, callback: (...data: any) => void, maxCallbacks: number): () => void;
|
||||
|
||||
// [EventsOnce](https://wails.io/docs/reference/runtime/events#eventsonce)
|
||||
// sets up a listener for the given event name, but will only trigger once.
|
||||
export function EventsOnce(eventName: string, callback: (...data: any) => void): () => void;
|
||||
|
||||
// [EventsOff](https://wails.io/docs/reference/runtime/events#eventsoff)
|
||||
// unregisters the listener for the given event name.
|
||||
export function EventsOff(eventName: string, ...additionalEventNames: string[]): void;
|
||||
|
||||
// [EventsOffAll](https://wails.io/docs/reference/runtime/events#eventsoffall)
|
||||
// unregisters all listeners.
|
||||
export function EventsOffAll(): void;
|
||||
|
||||
// [LogPrint](https://wails.io/docs/reference/runtime/log#logprint)
|
||||
// logs the given message as a raw message
|
||||
export function LogPrint(message: string): void;
|
||||
|
||||
// [LogTrace](https://wails.io/docs/reference/runtime/log#logtrace)
|
||||
// logs the given message at the `trace` log level.
|
||||
export function LogTrace(message: string): void;
|
||||
|
||||
// [LogDebug](https://wails.io/docs/reference/runtime/log#logdebug)
|
||||
// logs the given message at the `debug` log level.
|
||||
export function LogDebug(message: string): void;
|
||||
|
||||
// [LogError](https://wails.io/docs/reference/runtime/log#logerror)
|
||||
// logs the given message at the `error` log level.
|
||||
export function LogError(message: string): void;
|
||||
|
||||
// [LogFatal](https://wails.io/docs/reference/runtime/log#logfatal)
|
||||
// logs the given message at the `fatal` log level.
|
||||
// The application will quit after calling this method.
|
||||
export function LogFatal(message: string): void;
|
||||
|
||||
// [LogInfo](https://wails.io/docs/reference/runtime/log#loginfo)
|
||||
// logs the given message at the `info` log level.
|
||||
export function LogInfo(message: string): void;
|
||||
|
||||
// [LogWarning](https://wails.io/docs/reference/runtime/log#logwarning)
|
||||
// logs the given message at the `warning` log level.
|
||||
export function LogWarning(message: string): void;
|
||||
|
||||
// [WindowReload](https://wails.io/docs/reference/runtime/window#windowreload)
|
||||
// Forces a reload by the main application as well as connected browsers.
|
||||
export function WindowReload(): void;
|
||||
|
||||
// [WindowReloadApp](https://wails.io/docs/reference/runtime/window#windowreloadapp)
|
||||
// Reloads the application frontend.
|
||||
export function WindowReloadApp(): void;
|
||||
|
||||
// [WindowSetAlwaysOnTop](https://wails.io/docs/reference/runtime/window#windowsetalwaysontop)
|
||||
// Sets the window AlwaysOnTop or not on top.
|
||||
export function WindowSetAlwaysOnTop(b: boolean): void;
|
||||
|
||||
// [WindowSetSystemDefaultTheme](https://wails.io/docs/next/reference/runtime/window#windowsetsystemdefaulttheme)
|
||||
// *Windows only*
|
||||
// Sets window theme to system default (dark/light).
|
||||
export function WindowSetSystemDefaultTheme(): void;
|
||||
|
||||
// [WindowSetLightTheme](https://wails.io/docs/next/reference/runtime/window#windowsetlighttheme)
|
||||
// *Windows only*
|
||||
// Sets window to light theme.
|
||||
export function WindowSetLightTheme(): void;
|
||||
|
||||
// [WindowSetDarkTheme](https://wails.io/docs/next/reference/runtime/window#windowsetdarktheme)
|
||||
// *Windows only*
|
||||
// Sets window to dark theme.
|
||||
export function WindowSetDarkTheme(): void;
|
||||
|
||||
// [WindowCenter](https://wails.io/docs/reference/runtime/window#windowcenter)
|
||||
// Centers the window on the monitor the window is currently on.
|
||||
export function WindowCenter(): void;
|
||||
|
||||
// [WindowSetTitle](https://wails.io/docs/reference/runtime/window#windowsettitle)
|
||||
// Sets the text in the window title bar.
|
||||
export function WindowSetTitle(title: string): void;
|
||||
|
||||
// [WindowFullscreen](https://wails.io/docs/reference/runtime/window#windowfullscreen)
|
||||
// Makes the window full screen.
|
||||
export function WindowFullscreen(): void;
|
||||
|
||||
// [WindowUnfullscreen](https://wails.io/docs/reference/runtime/window#windowunfullscreen)
|
||||
// Restores the previous window dimensions and position prior to full screen.
|
||||
export function WindowUnfullscreen(): void;
|
||||
|
||||
// [WindowIsFullscreen](https://wails.io/docs/reference/runtime/window#windowisfullscreen)
|
||||
// Returns the state of the window, i.e. whether the window is in full screen mode or not.
|
||||
export function WindowIsFullscreen(): Promise<boolean>;
|
||||
|
||||
// [WindowSetSize](https://wails.io/docs/reference/runtime/window#windowsetsize)
|
||||
// Sets the width and height of the window.
|
||||
export function WindowSetSize(width: number, height: number): void;
|
||||
|
||||
// [WindowGetSize](https://wails.io/docs/reference/runtime/window#windowgetsize)
|
||||
// Gets the width and height of the window.
|
||||
export function WindowGetSize(): Promise<Size>;
|
||||
|
||||
// [WindowSetMaxSize](https://wails.io/docs/reference/runtime/window#windowsetmaxsize)
|
||||
// Sets the maximum window size. Will resize the window if the window is currently larger than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMaxSize(width: number, height: number): void;
|
||||
|
||||
// [WindowSetMinSize](https://wails.io/docs/reference/runtime/window#windowsetminsize)
|
||||
// Sets the minimum window size. Will resize the window if the window is currently smaller than the given dimensions.
|
||||
// Setting a size of 0,0 will disable this constraint.
|
||||
export function WindowSetMinSize(width: number, height: number): void;
|
||||
|
||||
// [WindowSetPosition](https://wails.io/docs/reference/runtime/window#windowsetposition)
|
||||
// Sets the window position relative to the monitor the window is currently on.
|
||||
export function WindowSetPosition(x: number, y: number): void;
|
||||
|
||||
// [WindowGetPosition](https://wails.io/docs/reference/runtime/window#windowgetposition)
|
||||
// Gets the window position relative to the monitor the window is currently on.
|
||||
export function WindowGetPosition(): Promise<Position>;
|
||||
|
||||
// [WindowHide](https://wails.io/docs/reference/runtime/window#windowhide)
|
||||
// Hides the window.
|
||||
export function WindowHide(): void;
|
||||
|
||||
// [WindowShow](https://wails.io/docs/reference/runtime/window#windowshow)
|
||||
// Shows the window, if it is currently hidden.
|
||||
export function WindowShow(): void;
|
||||
|
||||
// [WindowMaximise](https://wails.io/docs/reference/runtime/window#windowmaximise)
|
||||
// Maximises the window to fill the screen.
|
||||
export function WindowMaximise(): void;
|
||||
|
||||
// [WindowToggleMaximise](https://wails.io/docs/reference/runtime/window#windowtogglemaximise)
|
||||
// Toggles between Maximised and UnMaximised.
|
||||
export function WindowToggleMaximise(): void;
|
||||
|
||||
// [WindowUnmaximise](https://wails.io/docs/reference/runtime/window#windowunmaximise)
|
||||
// Restores the window to the dimensions and position prior to maximising.
|
||||
export function WindowUnmaximise(): void;
|
||||
|
||||
// [WindowIsMaximised](https://wails.io/docs/reference/runtime/window#windowismaximised)
|
||||
// Returns the state of the window, i.e. whether the window is maximised or not.
|
||||
export function WindowIsMaximised(): Promise<boolean>;
|
||||
|
||||
// [WindowMinimise](https://wails.io/docs/reference/runtime/window#windowminimise)
|
||||
// Minimises the window.
|
||||
export function WindowMinimise(): void;
|
||||
|
||||
// [WindowUnminimise](https://wails.io/docs/reference/runtime/window#windowunminimise)
|
||||
// Restores the window to the dimensions and position prior to minimising.
|
||||
export function WindowUnminimise(): void;
|
||||
|
||||
// [WindowIsMinimised](https://wails.io/docs/reference/runtime/window#windowisminimised)
|
||||
// Returns the state of the window, i.e. whether the window is minimised or not.
|
||||
export function WindowIsMinimised(): Promise<boolean>;
|
||||
|
||||
// [WindowIsNormal](https://wails.io/docs/reference/runtime/window#windowisnormal)
|
||||
// Returns the state of the window, i.e. whether the window is normal or not.
|
||||
export function WindowIsNormal(): Promise<boolean>;
|
||||
|
||||
// [WindowSetBackgroundColour](https://wails.io/docs/reference/runtime/window#windowsetbackgroundcolour)
|
||||
// Sets the background colour of the window to the given RGBA colour definition. This colour will show through for all transparent pixels.
|
||||
export function WindowSetBackgroundColour(R: number, G: number, B: number, A: number): void;
|
||||
|
||||
// [ScreenGetAll](https://wails.io/docs/reference/runtime/window#screengetall)
|
||||
// Gets the all screens. Call this anew each time you want to refresh data from the underlying windowing system.
|
||||
export function ScreenGetAll(): Promise<Screen[]>;
|
||||
|
||||
// [BrowserOpenURL](https://wails.io/docs/reference/runtime/browser#browseropenurl)
|
||||
// Opens the given URL in the system browser.
|
||||
export function BrowserOpenURL(url: string): void;
|
||||
|
||||
// [Environment](https://wails.io/docs/reference/runtime/intro#environment)
|
||||
// Returns information about the environment
|
||||
export function Environment(): Promise<EnvironmentInfo>;
|
||||
|
||||
// [Quit](https://wails.io/docs/reference/runtime/intro#quit)
|
||||
// Quits the application.
|
||||
export function Quit(): void;
|
||||
|
||||
// [Hide](https://wails.io/docs/reference/runtime/intro#hide)
|
||||
// Hides the application.
|
||||
export function Hide(): void;
|
||||
|
||||
// [Show](https://wails.io/docs/reference/runtime/intro#show)
|
||||
// Shows the application.
|
||||
export function Show(): void;
|
||||
|
||||
// [ClipboardGetText](https://wails.io/docs/reference/runtime/clipboard#clipboardgettext)
|
||||
// Returns the current text stored on clipboard
|
||||
export function ClipboardGetText(): Promise<string>;
|
||||
|
||||
// [ClipboardSetText](https://wails.io/docs/reference/runtime/clipboard#clipboardsettext)
|
||||
// Sets a text on the clipboard
|
||||
export function ClipboardSetText(text: string): Promise<boolean>;
|
||||
|
||||
// [OnFileDrop](https://wails.io/docs/reference/runtime/draganddrop#onfiledrop)
|
||||
// OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||
export function OnFileDrop(callback: (x: number, y: number ,paths: string[]) => void, useDropTarget: boolean) :void
|
||||
|
||||
// [OnFileDropOff](https://wails.io/docs/reference/runtime/draganddrop#dragandddropoff)
|
||||
// OnFileDropOff removes the drag and drop listeners and handlers.
|
||||
export function OnFileDropOff() :void
|
||||
|
||||
// Check if the file path resolver is available
|
||||
export function CanResolveFilePaths(): boolean;
|
||||
|
||||
// Resolves file paths for an array of files
|
||||
export function ResolveFilePaths(files: File[]): void
|
||||
242
Backend/admin/frontend/wailsjs/wailsjs/runtime/runtime.js
Normal file
242
Backend/admin/frontend/wailsjs/wailsjs/runtime/runtime.js
Normal file
@@ -0,0 +1,242 @@
|
||||
/*
|
||||
_ __ _ __
|
||||
| | / /___ _(_) /____
|
||||
| | /| / / __ `/ / / ___/
|
||||
| |/ |/ / /_/ / / (__ )
|
||||
|__/|__/\__,_/_/_/____/
|
||||
The electron alternative for Go
|
||||
(c) Lea Anthony 2019-present
|
||||
*/
|
||||
|
||||
export function LogPrint(message) {
|
||||
window.runtime.LogPrint(message);
|
||||
}
|
||||
|
||||
export function LogTrace(message) {
|
||||
window.runtime.LogTrace(message);
|
||||
}
|
||||
|
||||
export function LogDebug(message) {
|
||||
window.runtime.LogDebug(message);
|
||||
}
|
||||
|
||||
export function LogInfo(message) {
|
||||
window.runtime.LogInfo(message);
|
||||
}
|
||||
|
||||
export function LogWarning(message) {
|
||||
window.runtime.LogWarning(message);
|
||||
}
|
||||
|
||||
export function LogError(message) {
|
||||
window.runtime.LogError(message);
|
||||
}
|
||||
|
||||
export function LogFatal(message) {
|
||||
window.runtime.LogFatal(message);
|
||||
}
|
||||
|
||||
export function EventsOnMultiple(eventName, callback, maxCallbacks) {
|
||||
return window.runtime.EventsOnMultiple(eventName, callback, maxCallbacks);
|
||||
}
|
||||
|
||||
export function EventsOn(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, -1);
|
||||
}
|
||||
|
||||
export function EventsOff(eventName, ...additionalEventNames) {
|
||||
return window.runtime.EventsOff(eventName, ...additionalEventNames);
|
||||
}
|
||||
|
||||
export function EventsOffAll() {
|
||||
return window.runtime.EventsOffAll();
|
||||
}
|
||||
|
||||
export function EventsOnce(eventName, callback) {
|
||||
return EventsOnMultiple(eventName, callback, 1);
|
||||
}
|
||||
|
||||
export function EventsEmit(eventName) {
|
||||
let args = [eventName].slice.call(arguments);
|
||||
return window.runtime.EventsEmit.apply(null, args);
|
||||
}
|
||||
|
||||
export function WindowReload() {
|
||||
window.runtime.WindowReload();
|
||||
}
|
||||
|
||||
export function WindowReloadApp() {
|
||||
window.runtime.WindowReloadApp();
|
||||
}
|
||||
|
||||
export function WindowSetAlwaysOnTop(b) {
|
||||
window.runtime.WindowSetAlwaysOnTop(b);
|
||||
}
|
||||
|
||||
export function WindowSetSystemDefaultTheme() {
|
||||
window.runtime.WindowSetSystemDefaultTheme();
|
||||
}
|
||||
|
||||
export function WindowSetLightTheme() {
|
||||
window.runtime.WindowSetLightTheme();
|
||||
}
|
||||
|
||||
export function WindowSetDarkTheme() {
|
||||
window.runtime.WindowSetDarkTheme();
|
||||
}
|
||||
|
||||
export function WindowCenter() {
|
||||
window.runtime.WindowCenter();
|
||||
}
|
||||
|
||||
export function WindowSetTitle(title) {
|
||||
window.runtime.WindowSetTitle(title);
|
||||
}
|
||||
|
||||
export function WindowFullscreen() {
|
||||
window.runtime.WindowFullscreen();
|
||||
}
|
||||
|
||||
export function WindowUnfullscreen() {
|
||||
window.runtime.WindowUnfullscreen();
|
||||
}
|
||||
|
||||
export function WindowIsFullscreen() {
|
||||
return window.runtime.WindowIsFullscreen();
|
||||
}
|
||||
|
||||
export function WindowGetSize() {
|
||||
return window.runtime.WindowGetSize();
|
||||
}
|
||||
|
||||
export function WindowSetSize(width, height) {
|
||||
window.runtime.WindowSetSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetMaxSize(width, height) {
|
||||
window.runtime.WindowSetMaxSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetMinSize(width, height) {
|
||||
window.runtime.WindowSetMinSize(width, height);
|
||||
}
|
||||
|
||||
export function WindowSetPosition(x, y) {
|
||||
window.runtime.WindowSetPosition(x, y);
|
||||
}
|
||||
|
||||
export function WindowGetPosition() {
|
||||
return window.runtime.WindowGetPosition();
|
||||
}
|
||||
|
||||
export function WindowHide() {
|
||||
window.runtime.WindowHide();
|
||||
}
|
||||
|
||||
export function WindowShow() {
|
||||
window.runtime.WindowShow();
|
||||
}
|
||||
|
||||
export function WindowMaximise() {
|
||||
window.runtime.WindowMaximise();
|
||||
}
|
||||
|
||||
export function WindowToggleMaximise() {
|
||||
window.runtime.WindowToggleMaximise();
|
||||
}
|
||||
|
||||
export function WindowUnmaximise() {
|
||||
window.runtime.WindowUnmaximise();
|
||||
}
|
||||
|
||||
export function WindowIsMaximised() {
|
||||
return window.runtime.WindowIsMaximised();
|
||||
}
|
||||
|
||||
export function WindowMinimise() {
|
||||
window.runtime.WindowMinimise();
|
||||
}
|
||||
|
||||
export function WindowUnminimise() {
|
||||
window.runtime.WindowUnminimise();
|
||||
}
|
||||
|
||||
export function WindowSetBackgroundColour(R, G, B, A) {
|
||||
window.runtime.WindowSetBackgroundColour(R, G, B, A);
|
||||
}
|
||||
|
||||
export function ScreenGetAll() {
|
||||
return window.runtime.ScreenGetAll();
|
||||
}
|
||||
|
||||
export function WindowIsMinimised() {
|
||||
return window.runtime.WindowIsMinimised();
|
||||
}
|
||||
|
||||
export function WindowIsNormal() {
|
||||
return window.runtime.WindowIsNormal();
|
||||
}
|
||||
|
||||
export function BrowserOpenURL(url) {
|
||||
window.runtime.BrowserOpenURL(url);
|
||||
}
|
||||
|
||||
export function Environment() {
|
||||
return window.runtime.Environment();
|
||||
}
|
||||
|
||||
export function Quit() {
|
||||
window.runtime.Quit();
|
||||
}
|
||||
|
||||
export function Hide() {
|
||||
window.runtime.Hide();
|
||||
}
|
||||
|
||||
export function Show() {
|
||||
window.runtime.Show();
|
||||
}
|
||||
|
||||
export function ClipboardGetText() {
|
||||
return window.runtime.ClipboardGetText();
|
||||
}
|
||||
|
||||
export function ClipboardSetText(text) {
|
||||
return window.runtime.ClipboardSetText(text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||
*
|
||||
* @export
|
||||
* @callback OnFileDropCallback
|
||||
* @param {number} x - x coordinate of the drop
|
||||
* @param {number} y - y coordinate of the drop
|
||||
* @param {string[]} paths - A list of file paths.
|
||||
*/
|
||||
|
||||
/**
|
||||
* OnFileDrop listens to drag and drop events and calls the callback with the coordinates of the drop and an array of path strings.
|
||||
*
|
||||
* @export
|
||||
* @param {OnFileDropCallback} callback - Callback for OnFileDrop returns a slice of file path strings when a drop is finished.
|
||||
* @param {boolean} [useDropTarget=true] - Only call the callback when the drop finished on an element that has the drop target style. (--wails-drop-target)
|
||||
*/
|
||||
export function OnFileDrop(callback, useDropTarget) {
|
||||
return window.runtime.OnFileDrop(callback, useDropTarget);
|
||||
}
|
||||
|
||||
/**
|
||||
* OnFileDropOff removes the drag and drop listeners and handlers.
|
||||
*/
|
||||
export function OnFileDropOff() {
|
||||
return window.runtime.OnFileDropOff();
|
||||
}
|
||||
|
||||
export function CanResolveFilePaths() {
|
||||
return window.runtime.CanResolveFilePaths();
|
||||
}
|
||||
|
||||
export function ResolveFilePaths(files) {
|
||||
return window.runtime.ResolveFilePaths(files);
|
||||
}
|
||||
Reference in New Issue
Block a user