Исправление Сервера
This commit is contained in:
@@ -1,190 +1,202 @@
|
|||||||
package webserver
|
package webserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"syscall"
|
"strings"
|
||||||
"time"
|
"syscall"
|
||||||
config "vServer/Backend/config"
|
"time"
|
||||||
tools "vServer/Backend/tools"
|
config "vServer/Backend/config"
|
||||||
)
|
tools "vServer/Backend/tools"
|
||||||
|
)
|
||||||
var mysqlProcess *exec.Cmd
|
|
||||||
var mysql_status bool = false
|
var mysqlProcess *exec.Cmd
|
||||||
var mysql_secure bool = false
|
var mysql_status bool = false
|
||||||
|
var mysql_secure bool = false
|
||||||
// GetMySQLStatus возвращает статус MySQL
|
|
||||||
func GetMySQLStatus() bool {
|
// GetMySQLStatus возвращает статус MySQL
|
||||||
return mysql_status
|
func GetMySQLStatus() bool {
|
||||||
}
|
return mysql_status
|
||||||
|
}
|
||||||
var mysqldPath string
|
|
||||||
var configPath string
|
func normalizeBindAddress(host string) string {
|
||||||
var dataDirAbs string
|
parts := strings.Split(host, ",")
|
||||||
var binDirAbs string
|
cleaned := make([]string, 0, len(parts))
|
||||||
var binPathAbs string
|
for _, part := range parts {
|
||||||
|
if ip := strings.TrimSpace(part); ip != "" {
|
||||||
var mysql_port int
|
cleaned = append(cleaned, ip)
|
||||||
var mysql_ip string
|
}
|
||||||
|
}
|
||||||
var console_mysql bool = false
|
return strings.Join(cleaned, ",")
|
||||||
|
}
|
||||||
func AbsPathMySQL() {
|
|
||||||
|
var mysqldPath string
|
||||||
var err error
|
var configPath string
|
||||||
|
var dataDirAbs string
|
||||||
mysqldPath, err = tools.AbsPath(filepath.Join("WebServer/soft/MySQL/bin", "mysqld.exe"))
|
var binDirAbs string
|
||||||
tools.CheckError(err)
|
var binPathAbs string
|
||||||
|
|
||||||
configPath, err = tools.AbsPath("WebServer/soft/MySQL/my.ini")
|
var mysql_port int
|
||||||
tools.CheckError(err)
|
var mysql_ip string
|
||||||
|
|
||||||
dataDirAbs, err = tools.AbsPath("WebServer/soft/MySQL/bin/data")
|
var console_mysql bool = false
|
||||||
tools.CheckError(err)
|
|
||||||
|
func AbsPathMySQL() {
|
||||||
binDirAbs, err = tools.AbsPath("WebServer/soft/MySQL/bin")
|
|
||||||
tools.CheckError(err)
|
var err error
|
||||||
|
|
||||||
binPathAbs, err = tools.AbsPath("WebServer/soft/MySQL/bin")
|
mysqldPath, err = tools.AbsPath(filepath.Join("WebServer/soft/MySQL/bin", "mysqld.exe"))
|
||||||
tools.CheckError(err)
|
tools.CheckError(err)
|
||||||
|
|
||||||
}
|
configPath, err = tools.AbsPath("WebServer/soft/MySQL/my.ini")
|
||||||
|
tools.CheckError(err)
|
||||||
// config_patch возвращает путь к mysqld, аргументы и бинарную директорию
|
|
||||||
func config_patch(secures bool) (string, []string, string) {
|
dataDirAbs, err = tools.AbsPath("WebServer/soft/MySQL/bin/data")
|
||||||
|
tools.CheckError(err)
|
||||||
// Получаем абсолютные пути
|
|
||||||
AbsPathMySQL()
|
binDirAbs, err = tools.AbsPath("WebServer/soft/MySQL/bin")
|
||||||
|
tools.CheckError(err)
|
||||||
// Объявляем args на уровне функции
|
|
||||||
var args []string
|
binPathAbs, err = tools.AbsPath("WebServer/soft/MySQL/bin")
|
||||||
|
tools.CheckError(err)
|
||||||
if secures {
|
|
||||||
|
}
|
||||||
args = []string{
|
|
||||||
"--defaults-file=" + configPath,
|
// config_patch возвращает путь к mysqld, аргументы и бинарную директорию
|
||||||
"--datadir=" + dataDirAbs,
|
func config_patch(secures bool) (string, []string, string) {
|
||||||
"--shared-memory",
|
|
||||||
"--skip-grant-tables",
|
// Получаем абсолютные пути
|
||||||
"--console",
|
AbsPathMySQL()
|
||||||
}
|
|
||||||
|
// Объявляем args на уровне функции
|
||||||
} else {
|
var args []string
|
||||||
|
|
||||||
args = []string{
|
if secures {
|
||||||
"--defaults-file=" + configPath,
|
|
||||||
"--port=" + fmt.Sprintf("%d", mysql_port),
|
args = []string{
|
||||||
"--bind-address=" + mysql_ip,
|
"--defaults-file=" + configPath,
|
||||||
"--datadir=" + dataDirAbs,
|
"--datadir=" + dataDirAbs,
|
||||||
"--console",
|
"--shared-memory",
|
||||||
}
|
"--skip-grant-tables",
|
||||||
|
"--console",
|
||||||
}
|
}
|
||||||
|
|
||||||
return mysqldPath, args, binDirAbs
|
} else {
|
||||||
}
|
|
||||||
|
args = []string{
|
||||||
// StartMySQLServer запускает MySQL сервер
|
"--defaults-file=" + configPath,
|
||||||
func StartMySQLServer(secure bool) {
|
"--port=" + fmt.Sprintf("%d", mysql_port),
|
||||||
|
"--bind-address=" + mysql_ip,
|
||||||
mysql_port = config.ConfigData.Soft_Settings.Mysql_port
|
"--datadir=" + dataDirAbs,
|
||||||
mysql_ip = config.ConfigData.Soft_Settings.Mysql_host
|
"--console",
|
||||||
|
}
|
||||||
if mysql_status {
|
|
||||||
tools.Logs_file(1, "MySQL", "Сервер MySQL уже запущен", "logs_mysql.log", false)
|
}
|
||||||
return
|
|
||||||
}
|
return mysqldPath, args, binDirAbs
|
||||||
|
}
|
||||||
// Настройка режима
|
|
||||||
mysql_secure = secure
|
// StartMySQLServer запускает MySQL сервер
|
||||||
mysqldPath, args, binDirAbs := config_patch(secure)
|
func StartMySQLServer(secure bool) {
|
||||||
|
|
||||||
// Выбор сообщения
|
mysql_port = config.ConfigData.Soft_Settings.Mysql_port
|
||||||
if secure {
|
mysql_ip = normalizeBindAddress(config.ConfigData.Soft_Settings.Mysql_host)
|
||||||
tools.Logs_file(0, "MySQL", "Запуск сервера MySQL в режиме безопасности", "logs_mysql.log", false)
|
|
||||||
} else {
|
if mysql_status {
|
||||||
tools.Logs_file(0, "MySQL", "Запуск сервера MySQL в обычном режиме", "logs_mysql.log", false)
|
tools.Logs_file(1, "MySQL", "Сервер MySQL уже запущен", "logs_mysql.log", false)
|
||||||
}
|
return
|
||||||
|
}
|
||||||
// Общая логика запуска
|
|
||||||
mysqlProcess = exec.Command(mysqldPath, args...)
|
// Настройка режима
|
||||||
mysqlProcess.Dir = binDirAbs
|
mysql_secure = secure
|
||||||
tools.Logs_console(mysqlProcess, console_mysql)
|
mysqldPath, args, binDirAbs := config_patch(secure)
|
||||||
|
|
||||||
tools.Logs_file(0, "MySQL", fmt.Sprintf("Сервер MySQL запущен на %s:%d", mysql_ip, mysql_port), "logs_mysql.log", false)
|
// Выбор сообщения
|
||||||
|
if secure {
|
||||||
mysql_status = true
|
tools.Logs_file(0, "MySQL", "Запуск сервера MySQL в режиме безопасности", "logs_mysql.log", false)
|
||||||
|
} else {
|
||||||
}
|
tools.Logs_file(0, "MySQL", "Запуск сервера MySQL в обычном режиме", "logs_mysql.log", false)
|
||||||
|
}
|
||||||
// StopMySQLServer останавливает MySQL сервер
|
|
||||||
func StopMySQLServer() {
|
// Общая логика запуска
|
||||||
|
mysqlProcess = exec.Command(mysqldPath, args...)
|
||||||
if !mysql_status {
|
mysqlProcess.Dir = binDirAbs
|
||||||
return // Уже остановлен
|
tools.Logs_console(mysqlProcess, console_mysql)
|
||||||
}
|
|
||||||
|
tools.Logs_file(0, "MySQL", fmt.Sprintf("Сервер MySQL запущен на %s:%d", mysql_ip, mysql_port), "logs_mysql.log", false)
|
||||||
// Сначала пробуем завершить процесс корректно
|
|
||||||
if mysqlProcess != nil && mysqlProcess.Process != nil {
|
mysql_status = true
|
||||||
mysqlProcess.Process.Kill()
|
|
||||||
mysqlProcess = nil
|
}
|
||||||
}
|
|
||||||
|
// StopMySQLServer останавливает MySQL сервер
|
||||||
// Дополнительно убиваем все mysqld.exe процессы
|
func StopMySQLServer() {
|
||||||
cmd := exec.Command("taskkill", "/F", "/IM", "mysqld.exe")
|
|
||||||
|
if !mysql_status {
|
||||||
// Скрываем окно taskkill
|
return // Уже остановлен
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
}
|
||||||
HideWindow: true,
|
|
||||||
CreationFlags: 0x08000000,
|
// Сначала пробуем завершить процесс корректно
|
||||||
}
|
if mysqlProcess != nil && mysqlProcess.Process != nil {
|
||||||
|
mysqlProcess.Process.Kill()
|
||||||
cmd.Run()
|
mysqlProcess = nil
|
||||||
|
}
|
||||||
tools.Logs_file(0, "MySQL", "Сервер MySQL остановлен", "logs_mysql.log", false)
|
|
||||||
mysql_status = false
|
// Дополнительно убиваем все mysqld.exe процессы
|
||||||
|
cmd := exec.Command("taskkill", "/F", "/IM", "mysqld.exe")
|
||||||
}
|
|
||||||
|
// Скрываем окно taskkill
|
||||||
func ResetPasswordMySQL() {
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
|
HideWindow: true,
|
||||||
NewPasswordMySQL := "root"
|
CreationFlags: 0x08000000,
|
||||||
|
}
|
||||||
StopMySQLServer()
|
|
||||||
time.Sleep(2 * time.Second)
|
cmd.Run()
|
||||||
mysql_secure = true
|
|
||||||
StartMySQLServer(true)
|
tools.Logs_file(0, "MySQL", "Сервер MySQL остановлен", "logs_mysql.log", false)
|
||||||
time.Sleep(2 * time.Second)
|
mysql_status = false
|
||||||
query := "FLUSH PRIVILEGES; ALTER USER 'root'@'%' IDENTIFIED BY '" + NewPasswordMySQL + "';"
|
|
||||||
СheckMySQLPassword(query)
|
}
|
||||||
tools.Logs_file(0, "MySQL", "Новый пароль: "+NewPasswordMySQL, "logs_mysql.log", true)
|
|
||||||
println()
|
func ResetPasswordMySQL() {
|
||||||
StopMySQLServer()
|
|
||||||
StartMySQLServer(false)
|
NewPasswordMySQL := "root"
|
||||||
|
|
||||||
}
|
StopMySQLServer()
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
// СheckMySQLPassword проверяет пароль для MySQL
|
mysql_secure = true
|
||||||
func СheckMySQLPassword(query string) {
|
StartMySQLServer(true)
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
AbsPathMySQL()
|
query := "FLUSH PRIVILEGES; ALTER USER 'root'@'%' IDENTIFIED BY '" + NewPasswordMySQL + "';"
|
||||||
|
СheckMySQLPassword(query)
|
||||||
if mysql_secure {
|
tools.Logs_file(0, "MySQL", "Новый пароль: "+NewPasswordMySQL, "logs_mysql.log", true)
|
||||||
|
println()
|
||||||
// В безопасном режиме подключаемся без пароля
|
StopMySQLServer()
|
||||||
cmd := exec.Command(filepath.Join(binPathAbs, "mysql.exe"), "-u", "root", "-pRoot", "-e", query)
|
StartMySQLServer(false)
|
||||||
cmd.Dir = binPathAbs
|
|
||||||
|
}
|
||||||
// Захватываем вывод для логирования
|
|
||||||
err := tools.Logs_console(cmd, false)
|
// СheckMySQLPassword проверяет пароль для MySQL
|
||||||
|
func СheckMySQLPassword(query string) {
|
||||||
if err != nil {
|
|
||||||
tools.Logs_file(1, "MySQL", "Вывод MySQL (stdout/stderr):", "logs_mysql.log", true)
|
AbsPathMySQL()
|
||||||
} else {
|
|
||||||
tools.Logs_file(0, "MySQL", "Команда выполнена успешно", "logs_mysql.log", true)
|
if mysql_secure {
|
||||||
}
|
|
||||||
|
// В безопасном режиме подключаемся без пароля
|
||||||
}
|
cmd := exec.Command(filepath.Join(binPathAbs, "mysql.exe"), "-u", "root", "-pRoot", "-e", query)
|
||||||
|
cmd.Dir = binPathAbs
|
||||||
}
|
|
||||||
|
// Захватываем вывод для логирования
|
||||||
|
err := tools.Logs_console(cmd, false)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
tools.Logs_file(1, "MySQL", "Вывод MySQL (stdout/stderr):", "logs_mysql.log", true)
|
||||||
|
} else {
|
||||||
|
tools.Logs_file(0, "MySQL", "Команда выполнена успешно", "logs_mysql.log", true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
4370
front_vue/package-lock.json
generated
4370
front_vue/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,162 +1,162 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const configStore = useConfigStore()
|
const configStore = useConfigStore()
|
||||||
const servicesStore = useServicesStore()
|
const servicesStore = useServicesStore()
|
||||||
const { success } = useNotification()
|
const { success } = useNotification()
|
||||||
|
|
||||||
const form = reactive({
|
const form = reactive({
|
||||||
mysqlHost: '',
|
mysqlHost: '',
|
||||||
mysqlPort: 3306,
|
mysqlPort: 3306,
|
||||||
phpHost: '',
|
phpHost: '',
|
||||||
phpPort: 8000,
|
phpPort: 8000,
|
||||||
proxyEnabled: false,
|
proxyEnabled: false,
|
||||||
acmeEnabled: false,
|
acmeEnabled: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await configStore.load()
|
await configStore.load()
|
||||||
const s = configStore.softSettings
|
const s = configStore.softSettings
|
||||||
form.mysqlHost = s.mysql_host || '127.0.0.1'
|
form.mysqlHost = s.mysql_host || '127.0.0.1'
|
||||||
form.mysqlPort = s.mysql_port || 3306
|
form.mysqlPort = s.mysql_port || 3306
|
||||||
form.phpHost = s.php_host || 'localhost'
|
form.phpHost = s.php_host || 'localhost'
|
||||||
form.phpPort = s.php_port || 8000
|
form.phpPort = s.php_port || 8000
|
||||||
form.proxyEnabled = s.proxy_enabled || false
|
form.proxyEnabled = s.proxy_enabled || false
|
||||||
form.acmeEnabled = s.ACME_enabled || false
|
form.acmeEnabled = s.ACME_enabled || false
|
||||||
})
|
})
|
||||||
|
|
||||||
const saveSettings = async () => {
|
const saveSettings = async () => {
|
||||||
saving.value = true
|
saving.value = true
|
||||||
const configData = {
|
const configData = {
|
||||||
...configStore.data,
|
...configStore.data,
|
||||||
Soft_Settings: {
|
Soft_Settings: {
|
||||||
mysql_host: form.mysqlHost,
|
mysql_host: form.mysqlHost,
|
||||||
mysql_port: Number(form.mysqlPort),
|
mysql_port: Number(form.mysqlPort),
|
||||||
php_host: form.phpHost,
|
php_host: form.phpHost,
|
||||||
php_port: Number(form.phpPort),
|
php_port: Number(form.phpPort),
|
||||||
proxy_enabled: form.proxyEnabled,
|
proxy_enabled: form.proxyEnabled,
|
||||||
ACME_enabled: form.acmeEnabled,
|
ACME_enabled: form.acmeEnabled,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
await configStore.save(configData)
|
await configStore.save(configData)
|
||||||
await servicesStore.restartAll()
|
await servicesStore.restartAll()
|
||||||
saving.value = false
|
saving.value = false
|
||||||
success(t('notify.settingsSaved'))
|
success(t('notify.settingsSaved'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleProxy = async () => {
|
const toggleProxy = async () => {
|
||||||
form.proxyEnabled = !form.proxyEnabled
|
form.proxyEnabled = !form.proxyEnabled
|
||||||
if (form.proxyEnabled) await servicesStore.enableProxy()
|
if (form.proxyEnabled) await servicesStore.enableProxy()
|
||||||
else await servicesStore.disableProxy()
|
else await servicesStore.disableProxy()
|
||||||
success(form.proxyEnabled ? t('notify.proxyEnabled') : t('notify.proxyDisabled'))
|
success(form.proxyEnabled ? t('notify.proxyEnabled') : t('notify.proxyDisabled'))
|
||||||
}
|
}
|
||||||
|
|
||||||
const toggleAcme = async () => {
|
const toggleAcme = async () => {
|
||||||
form.acmeEnabled = !form.acmeEnabled
|
form.acmeEnabled = !form.acmeEnabled
|
||||||
if (form.acmeEnabled) await servicesStore.enableACME()
|
if (form.acmeEnabled) await servicesStore.enableACME()
|
||||||
else await servicesStore.disableACME()
|
else await servicesStore.disableACME()
|
||||||
success(form.acmeEnabled ? t('notify.certManagerEnabled') : t('notify.certManagerDisabled'))
|
success(form.acmeEnabled ? t('notify.certManagerEnabled') : t('notify.certManagerDisabled'))
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="settings-view">
|
<div class="settings-view">
|
||||||
<div class="settings-header">
|
<div class="settings-header">
|
||||||
<VSectionHeader :title="t('settings.title')" />
|
<VSectionHeader :title="t('settings.title')" />
|
||||||
<VButton variant="success" icon="fas fa-save" :loading="saving" @click="saveSettings">
|
<VButton variant="success" icon="fas fa-save" :loading="saving" @click="saveSettings">
|
||||||
{{ saving ? t('settings.saving') : t('settings.save') }}
|
{{ saving ? t('settings.saving') : t('settings.save') }}
|
||||||
</VButton>
|
</VButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-grid">
|
<div class="settings-grid">
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<h3 class="settings-card-title"><i class="fas fa-database"></i> {{ t('settings.mysql') }}</h3>
|
<h3 class="settings-card-title"><i class="fas fa-database"></i> {{ t('settings.mysql') }}</h3>
|
||||||
<div class="settings-form">
|
<div class="settings-form">
|
||||||
<VInput v-model="form.mysqlHost" :label="t('settings.hostAddr')" placeholder="127.0.0.1" />
|
<VInput v-model="form.mysqlHost" :label="t('settings.hostAddr')" placeholder="127.0.0.1, 192.168.1.9" />
|
||||||
<VInput v-model="form.mysqlPort" :label="t('settings.port')" type="number" placeholder="3306" />
|
<VInput v-model="form.mysqlPort" :label="t('settings.port')" type="number" placeholder="3306" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<h3 class="settings-card-title"><i class="fab fa-php"></i> {{ t('settings.php') }}</h3>
|
<h3 class="settings-card-title"><i class="fab fa-php"></i> {{ t('settings.php') }}</h3>
|
||||||
<div class="settings-form">
|
<div class="settings-form">
|
||||||
<VInput v-model="form.phpHost" :label="t('settings.hostAddr')" placeholder="localhost" />
|
<VInput v-model="form.phpHost" :label="t('settings.hostAddr')" placeholder="localhost" />
|
||||||
<VInput v-model="form.phpPort" :label="t('settings.port')" type="number" placeholder="8000" />
|
<VInput v-model="form.phpPort" :label="t('settings.port')" type="number" placeholder="8000" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<h3 class="settings-card-title">
|
<h3 class="settings-card-title">
|
||||||
<VTooltip :text="t('settings.proxyHint')" />
|
<VTooltip :text="t('settings.proxyHint')" />
|
||||||
<i class="fas fa-network-wired"></i> {{ t('settings.proxyManager') }}
|
<i class="fas fa-network-wired"></i> {{ t('settings.proxyManager') }}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="settings-form">
|
<div class="settings-form">
|
||||||
<VToggle v-model="form.proxyEnabled" :label="t('settings.proxyManager')" @update:model-value="toggleProxy" />
|
<VToggle v-model="form.proxyEnabled" :label="t('settings.proxyManager')" @update:model-value="toggleProxy" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
<h3 class="settings-card-title">
|
<h3 class="settings-card-title">
|
||||||
<VTooltip :text="t('settings.certHint')" />
|
<VTooltip :text="t('settings.certHint')" />
|
||||||
<i class="fas fa-certificate"></i> {{ t('settings.certManager') }}
|
<i class="fas fa-certificate"></i> {{ t('settings.certManager') }}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="settings-form">
|
<div class="settings-form">
|
||||||
<VToggle v-model="form.acmeEnabled" :label="t('settings.certManager')" @update:model-value="toggleAcme" />
|
<VToggle v-model="form.acmeEnabled" :label="t('settings.certManager')" @update:model-value="toggleAcme" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.settings-view {
|
.settings-view {
|
||||||
animation: fadeIn var(--transition-slow);
|
animation: fadeIn var(--transition-slow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-header {
|
.settings-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: var(--space-lg);
|
margin-bottom: var(--space-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-grid {
|
.settings-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, 1fr);
|
grid-template-columns: repeat(2, 1fr);
|
||||||
gap: var(--space-lg);
|
gap: var(--space-lg);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-card {
|
.settings-card {
|
||||||
background: rgba(var(--accent-rgb), 0.02);
|
background: rgba(var(--accent-rgb), 0.02);
|
||||||
border: 1px solid var(--glass-border);
|
border: 1px solid var(--glass-border);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
padding: var(--space-lg);
|
padding: var(--space-lg);
|
||||||
transition: all var(--transition-base);
|
transition: all var(--transition-base);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-card:hover {
|
.settings-card:hover {
|
||||||
background: rgba(var(--accent-rgb), 0.04);
|
background: rgba(var(--accent-rgb), 0.04);
|
||||||
border-color: rgba(var(--accent-rgb), 0.2);
|
border-color: rgba(var(--accent-rgb), 0.2);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-card-title {
|
.settings-card-title {
|
||||||
font-size: var(--text-md);
|
font-size: var(--text-md);
|
||||||
font-weight: var(--font-semibold);
|
font-weight: var(--font-semibold);
|
||||||
color: var(--text-primary);
|
color: var(--text-primary);
|
||||||
margin: 0 0 20px 0;
|
margin: 0 0 20px 0;
|
||||||
padding-bottom: 12px;
|
padding-bottom: 12px;
|
||||||
border-bottom: 1px solid rgba(var(--accent-rgb), 0.1);
|
border-bottom: 1px solid rgba(var(--accent-rgb), 0.1);
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: var(--space-sm);
|
gap: var(--space-sm);
|
||||||
}
|
}
|
||||||
|
|
||||||
.settings-card-title i {
|
.settings-card-title i {
|
||||||
color: var(--accent-purple-light);
|
color: var(--accent-purple-light);
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
@media (max-width: 900px) {
|
||||||
.settings-grid { grid-template-columns: 1fr; }
|
.settings-grid { grid-template-columns: 1fr; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user