"目录不存在: %@" = "El directorio no existe: %@";
"目录不存在: %lld" = "El directorio no existe: %lld";
"新建站点" = "Crear un nuevo sitio";
"忽略错误继续导入" = "Ignorar errores y continuar con la importación";
"保存并重启" = "Guardar y reiniciar";
"%@ 不支持重新加载配置，将使用重启代替" = "%@ no admite la recarga de la configuración; se utilizará el reinicio en su lugar";
"%lld 不支持重新加载配置，将使用重启代替" = "%lld no admite la recarga de la configuración; se utilizará el reinicio en su lugar";
"字符串缓冲" = "Buffer de cadenas";
"有效期" = "Periodo de validez";
"\n                    </FilesMatch>\n\n                    # 访问日志\n                    ErrorLog " = "</FilesMatch>\n\n                    #Registro de accesos\n                    ErrorLog";
"删除选中" = "Eliminar los seleccionados";
"网络读取超时" = "Tiempo de espera agotado al leer de la red";
"纯静态" = "Totalmente estático";
"本地 MySQL" = "MySQL local";
"✓ 私钥文件存在" = "✓ El archivo de clave privada existe";
"❌ 更新失败 %@ 个配置文件:" = "❌ Error al actualizar %@ archivos de configuración:";
"❌ 更新失败 %lld 个配置文件:" = "❌ Error al actualizar %lld archivos de configuración:";
"添加备注信息 (可选)..." = "Agregar notas (opcional)...";
"请输入至少一个域名" = "Introduzca al menos un nombre de dominio";
"端口冲突：与 \\(software.conflictingService ?? " = "Conflicto de puertos: con \\(software.conflictingService ??";
"密码保护" = "Protección mediante contraseña";
"详情" = "Detalles";
"保护模式" = "modo protegido";
"恢复失败: %@" = "Error al recuperar: %@";
"恢复失败: %lld" = "Error al recuperar: %lld";
"Socket 文件路径" = "Ruta del archivo de socket";
"# %@ 配置文件" = "# Archivo de configuración de %@";
"# %lld 配置文件" = "# Archivo de configuración de %lld";
"• 建议设置自动续期" = "• Se recomienda configurar la renovación automática";
"无法启动站点：%@ Web 服务器未安装。请先安装 %@ 后再启动站点。" = "No se puede iniciar el sitio: %@ El servidor web no está instalado. Instale %@ antes de iniciar el sitio.";
"无法启动站点：%lld Web 服务器未安装。请先安装 %lld 后再启动站点。" = "No se puede iniciar el sitio: %lld El servidor web no está instalado. Instale %lld antes de iniciar el sitio.";
"用户数据库" = "base de datos de usuarios";
"用于签发本地开发证书" = "Se utiliza para emitir certificados de desarrollo locales";
"Nginx/Apache 规则互转" = "Conversión de reglas de Nginx/Apache";
"两次输入的密码不一致" = "Las contraseñas introducidas no coinciden";
"确定要卸载 %@ 吗？此操作无法撤销。" = "¿Está seguro de que desea desinstalar %@? Esta acción no se puede deshacer.";
"确定要卸载 %lld 吗？此操作无法撤销。" = "¿Está seguro de que desea desinstalar %lld? Esta acción no se puede deshacer.";
"无法转换文本" = "No se puede convertir el texto";
"比memcache支持更多高级功能" = "Ofrece funciones más avanzadas que memcache";
"重加载" = "Recargar";
"工作空间: 已初始化 (%@)" = "Área de trabajo: Inicializada (%@)";
"工作空间: 已初始化 (%lld)" = "Área de trabajo: Inicializada (%lld)";
"关于" = "Acerca de";
"端口配置失败: %@" = "Error al configurar el puerto: %@";
"端口配置失败: %lld" = "Error al configurar el puerto: %lld";
"取消" = "Cancelar";
"扫描信息" = "Información del escaneo";
"Root密码已复制到剪贴板" = "Contraseña de administrador copiada al portapapeles";
"GD图形库" = "Biblioteca gráfica GD";
"Mac集成开发环境" = "Entorno de desarrollo integrado para Mac";
"接管系统中已安装的 %@，使用 80/443 端口" = "Asumir el control de %@ instalado en el sistema, utilizando los puertos 80 y 443";
"接管系统中已安装的 %lld，使用 80/443 端口" = "Asumir el control de %lld instalado en el sistema, utilizando los puertos 80 y 443";
"设置默认站点功能正在开发中,敬请期待。\\n\\n此功能将允许您设置当访问 localhost 时显示的默认站点。" = "La función para establecer un sitio predeterminado se encuentra actualmente en desarrollo; por favor, manténgase atento. \\n\\nEsta característica le permitirá definir el sitio que se mostrará automáticamente al acceder a «localhost».";
"允许URL包含" = "Permitir que las URL contengan";
"会话最大生命周期" = "Duración máxima de la sesión";
"生成证书失败" = "Error al generar el certificado";
"访问量趋势" = "Tendencias de tráfico";
"保存 Nginx 配置失败: %@" = "Error al guardar la configuración de Nginx: %@";
"保存 Nginx 配置失败: %lld" = "Error al guardar la configuración de Nginx: %lld";
"正在检查系统依赖..." = "Comprobando las dependencias del sistema...";
"安装方法：" = "Método de instalación:";
"正在配置 Nginx..." = "Configurando Nginx...";
"切割大小" = "Tamaño de recorte";
"错误日志" = "Registro de errores";
"证书管理" = "Gestión de certificados";
"修改 hosts 文件需要管理员权限，保存时系统会提示输入密码" = "La modificación del archivo hosts requiere privilegios de administrador; el sistema le solicitará que ingrese una contraseña al guardar.";
"端口号与当前端口相同" = "El número de puerto es el mismo que el puerto actual";
"剩余" = "Restante";
"内存限制" = "Límite de memoria";
"高性能 Web 服务器" = "Servidor web de alto rendimiento";
"数据库" = "Base de datos";
"状态说明: 未配置SSL证书" = "Descripción del estado: certificado SSL no configurado";
"SQL注入攻击检测" = "Detección de ataques de inyección SQL";
"Nginx 端口" = "Puerto de Nginx";
"查询缓存大小" = "Tamaño de la caché de consultas";
"自定义" = "Personalizar";
"工作空间路径" = "Ruta del espacio de trabajo";
"显示密码" = "Mostrar contraseña";
"哈希计算工具" = "Herramienta de cálculo de hash";
"访客 IP 地址和地理位置" = "Dirección IP y ubicación geográfica del visitante";
"错误率" = "tasa de errores";
"默认MIME类型" = "Tipo MIME predeterminado";
"内存淘汰策略" = "Estrategia de eliminación de memoria";
"• 安装到用户目录，无需 sudo 权限" = "• Instalar en el directorio del usuario sin permisos sudo";
"未检测到已安装的 Web 服务器（Nginx 或 Apache）。请先安装至少一个 Web 服务器后再创建站点。" = "No se ha detectado ningún servidor web instalado (Nginx o Apache). Instale al menos un servidor web antes de crear un sitio.";
"允许URL文件打开" = "Permitir la apertura de archivos URL";
"共存模式" = "modo de coexistencia";
"自签名证书" = "certificado autofirmado";
"域名不能为空" = "El nombre de dominio no puede estar vacío";
"避免权限问题" = "Evitar problemas de permisos";
"文档型数据库" = "base de datos de documentos";
"连接队列大小" = "tamaño de la cola de conexiones";
"⚠️ 错误信息:" = "⚠️ Mensaje de error:";
"原始模式" = "modo original";
"提示：本地数据库仅支持修改密码和备注。如需修改端口等配置，请在“高级设置”中调整配置文件。" = "Sugerencia: la base de datos local solo admite cambiar contraseñas y comentarios. Si necesita modificar configuraciones como los puertos, ajuste el archivo de configuración en «Ajustes avanzados».";
"推荐值" = "Valor recomendado";
"日期时间" = "fecha y hora";
"执行失败: %@" = "Error al ejecutar: %@";
"执行失败: %lld" = "Error al ejecutar: %lld";
"%@ 日志" = "registro de %@";
"%lld 日志" = "%lld registro";
"重新扫描" = "Volver a escanear";
"✅ Nginx 反向代理配置文件已生成: %@" = "✅ El archivo de configuración del proxy inverso de Nginx se ha generado correctamente: %@";
"✅ Nginx 反向代理配置文件已生成: %lld" = "✅ El archivo de configuración del proxy inverso de Nginx se ha generado correctamente: %lld";
"✅ 使用 mkcert 生成证书" = "✅ Usar mkcert para generar certificados";
"证书有效但连接失败" = "El certificado es válido, pero la conexión falla";
"Socket通信支持" = "Compatibilidad con comunicación mediante sockets";
"1. 请使用浏览器下载安装包" = "1. Descargue el paquete de instalación mediante su navegador";
"同步二进制日志" = "Sincronizar registros binarios";
"正在重启 Nginx..." = "Reiniciando Nginx...";
"IO 容量" = "Capacidad de E/S";
"50x Server Error (服务器错误)" = "Error del servidor 50x";
"版本" = "Versión";
"未找到 Homebrew，无法重启服务" = "Homebrew no encontrado; no se puede reiniciar el servicio";
"过期时间: %@)" = "Fecha de caducidad: %@)";
"过期时间: %lld)" = "Fecha de caducidad: %lld)";
"更新 Nginx 反向代理配置中的 Apache 后端端口" = "Actualizar el puerto de backend de Apache en la configuración del proxy inverso de Nginx";
"MySQL版本" = "Versión de MySQL";
"✅ Apache 配置文件已生成: %@" = "✅ El archivo de configuración de Apache se ha generado correctamente: %@";
"✅ Apache 配置文件已生成: %lld" = "✅ El archivo de configuración de Apache se ha generado correctamente: %lld";
"验证方式" = "Método de verificación";
"准备安装 %@..." = "Preparando la instalación de %@...";
"准备安装 %lld..." = "Preparando la instalación de %lld...";
"✅ 解析成功: \\(parsed.domains.joined(separator: " = "✅ Analizado correctamente: \\(parsed.domains.joined(separator:";
"恢复失败" = "Recuperación fallida";
"TCP优化" = "Optimización TCP";
"备份成功" = "Copia de seguridad realizada correctamente";
"无法打开根目录" = "No se puede abrir el directorio raíz";
"复制成功" = "Copiado correctamente";
"修改配置后，需重启数据库生效" = "Tras modificar la configuración, debe reiniciar la base de datos para que los cambios surtan efecto.";
"验证时间戳" = "Verificar marca de tiempo";
"已导出到: %@" = "Exportado a: %@";
"已导出到: %lld" = "Exportado a: %lld";
"暂无日志内容" = "Aún no hay contenido en el registro";
"安装后需要手动创建数据库和用户表" = "Tras la instalación, debe crear manualmente la base de datos y las tablas de usuarios";
"IP地址" = "Dirección IP";
"剩余天数: %@)" = "Días restantes: %@)";
"剩余天数: %lld)" = "Días restantes: %lld)";
"域名 %@ 已指向不同的 IP 地址" = "El nombre de dominio %@ ya apunta a una dirección IP distinta";
"域名 %lld 已指向不同的 IP 地址" = "El nombre de dominio %lld ya apunta a una dirección IP distinta";
"端口 %@" = "Puerto %@";
"端口 %lld" = "Puerto %lld";
"提示:" = "Sugerencias:";
"正在备份配置文件..." = "Realizando copia de seguridad de los archivos de configuración...";
"收起详情" = "Contraer detalles";
"默认站点" = "sitio predeterminado";
"过期日期" = "Fecha de caducidad";
"查看日志" = "Ver registro";
"使用 Homebrew 安装：" = "Instalar mediante Homebrew:";
"CA证书路径" = "Ruta del certificado CA";
"文件路径" = "ruta del archivo";
"打开日志按钮" = "Botón para abrir el registro";
"复制密码" = "Copiar contraseña";
"自定义..." = "Personalizar...";
"✓ 已复制错误页面目录到: %@" = "✓ El directorio de páginas de error se ha copiado en: %@";
"✓ 已复制错误页面目录到: %lld" = "✓ El directorio de páginas de error se ha copiado en: %lld";
"复制详情" = "Copiar detalles";
"目录权限正常" = "Los permisos del directorio son normales";
"站点名称不能为空" = "El nombre del sitio no puede estar vacío";
"专用服务器模式" = "Modo de servidor dedicado";
"加载令牌失败: %@" = "Error al cargar el token: %@";
"加载令牌失败: %lld" = "Error al cargar el token: %lld";
"排序缓冲区" = "memoria intermedia para ordenación";
"证书链" = "cadena de certificados";
"查看详情" = "Ver detalles";
"恢复" = "restaurar";
"保存原始内容" = "Guardar el contenido original";
"建议：" = "Sugerencias:";
"加密解密工具" = "Herramientas de cifrado y descifrado";
"强制重置密码失败，请检查 MySQL 服务状态" = "No se ha podido restablecer forzosamente la contraseña; compruebe el estado del servicio MySQL";
"配置已保存，但重载 Apache 失败" = "La configuración se ha guardado, pero no se ha podido volver a cargar Apache";
"重试" = "Volver a intentar";
"安全扫描失败: %@" = "Error en el análisis de seguridad: %@";
"安全扫描失败: %lld" = "Error en el análisis de seguridad: %lld";
"详细统计" = "Estadísticas detalladas";
"推荐" = "Recomendado";
"网络写入超时" = "Tiempo de espera para escritura en la red";
"单连接最大请求数" = "Número máximo de solicitudes por conexión";
"清除所有日志" = "Borrar todos los registros";
"Keychain 状态" = "Estado del llavero";
"✗ 私钥文件不存在: %@" = "✗ El archivo de clave privada no existe: %@";
"✗ 私钥文件不存在: %lld" = "✗ El archivo de clave privada no existe: %lld";
"正在验证安装..." = "Verificando la instalación...";
"概览统计" = "Estadísticas generales";
"刷新权限" = "Actualizar permisos";
"确定要删除数据库「%@」吗？此操作不可恢复！" = "¿Está seguro de que desea eliminar la base de datos \"%@\"? ¡Esta operación no se puede deshacer!";
"确定要删除数据库「%lld」吗？此操作不可恢复！" = "¿Está seguro de que desea eliminar la base de datos \"%lld\"? ¡Esta operación no se puede deshacer!";
"平均响应时间" = "tiempo medio de respuesta";
"日志缓冲大小" = "Tamaño del búfer de registros";
"正在安装..." = "Instalando...";
"保存令牌失败: %@" = "No se pudo guardar el token: %@";
"保存令牌失败: %lld" = "No se pudo guardar el token: %lld";
"自动修复生成" = "Generación automática de reparaciones";
"建议使用 9000-9999 范围内的端口" = "Se recomienda utilizar puertos en el rango 9000-9999";
"端口 %@ 已被占用" = "El puerto %@ ya está ocupado";
"端口 %lld 已被占用" = "El puerto %lld ya está ocupado";
"端口冲突" = "Conflicto de puertos";
"SMTP服务器" = "Servidor SMTP";
"修改 Root 密码" = "Cambiar la contraseña de root";
"稍后设置" = "Configurar más tarde";
"更新文件失败 %@: %@" = "Error al actualizar el archivo %@: %@";
"更新文件失败 %lld: %lld" = "Error al actualizar el archivo %lld: %lld";
"InnoDB缓冲区大小" = "Tamaño del búfer de InnoDB";
"域名无法访问" = "No se puede acceder al nombre de dominio";
"启动 %@ 失败" = "Error al iniciar %@";
"启动 %lld 失败" = "Error al iniciar %lld";
"远程" = "remoto";
"禁用Nagle算法" = "Desactivar el algoritmo de Nagle";
"白名单 (仅允许)" = "Lista blanca (solo permitir)";
"默认端口" = "Puerto predeterminado";
"编辑" = "Editar";
"扫描子目录失败: %@" = "Error al escanear el subdirectorio: %@";
"扫描子目录失败: %lld" = "Error al escanear el subdirectorio: %lld";
"自定义端口" = "Puerto personalizado";
"开放目录限制" = "Restricciones de apertura de directorios";
"已过期" = "Expirado";
"攻击来源 IP (Top 100)" = "Dirección IP de origen del ataque (100 principales)";
"错误日志最大长度" = "Longitud máxima del registro de errores";
"访客来源" = "Origen del visitante";
"手动安装" = "Instalación manual";
"✓ 创建临时测试文件" = "✓ Crear archivos de prueba temporales";
"监听端口" = "puerto de escucha";
"续期证书" = "Renovar certificado";
"删除" = "Eliminar";
"• 密码已更改但未记录" = "• Contraseña modificada, pero no se ha iniciado sesión";
"⚠️ 端口 %@ 已被占用" = "⚠️ El puerto %@ ya está ocupado";
"⚠️ 端口 %lld 已被占用" = "⚠️ El puerto %lld ya está ocupado";
"未找到匹配的端口" = "No se encontró ningún puerto coincidente";
"最大连接错误数" = "Número máximo de errores de conexión";
"可用版本" = "Versiones disponibles";
"✓ 证书文件存在" = "✓ El archivo de certificado existe";
"输出控制" = "Control de salida";
"日志文件" = "archivo de registro";
"已自动填充" = "Rellenado automáticamente";
"SQL注入" = "Inyección SQL";
"跨域配置" = "Configuración de dominios cruzados";
"单文件最大大小" = "Tamaño máximo de un solo archivo";
"常见端口" = "Puertos comunes";
"未找到 Redis 配置文件" = "No se encontró el archivo de configuración de Redis";
"请再次输入密码" = "Vuelva a introducir la contraseña";
"未找到 MySQL 客户端，请先安装 MySQL" = "Cliente MySQL no encontrado; instale MySQL primero";
"记录服务器错误和警告信息" = "Registrar mensajes de error y advertencia del servidor";
"生成加密密码失败" = "Error al generar la contraseña cifrada";
"• Root 密码不是常见密码" = "• La contraseña de root no es una contraseña común";
"批量导入" = "Importación por lotes";
"日志过期秒数" = "Segundos de expiración de los registros";
"✗ %@ 未配置" = "✗ %@ no está configurado";
"✗ %lld 未配置" = "✗ %lld no está configurado";
"Hosts解析" = "Análisis de hosts";
"输入源规则后点击转换按钮" = "Tras introducir las reglas de origen, haga clic en el botón Convertir";
"返回" = "Volver";
"站点统计 - %@" = "Estadísticas del sitio — %@";
"站点统计 - %lld" = "Estadísticas del sitio — %lld";
"线程缓存大小" = "Tamaño de la caché de subprocesos";
"✓ 证书有效" = "✓ El certificado es válido";
"正在恢复配置..." = "Restaurando la configuración...";
"最受欢迎页面" = "Páginas más populares";
"\n        主机: %@\n        端口: %@\n        用户名: root\n        密码: %@\n        " = "Host: %@\n        Puerto: %@\n        Nombre de usuario: root\n        Contraseña: %@";
"\n        主机: %lld\n        端口: %lld\n        用户名: root\n        密码: %lld\n        " = "Host: %lld\n        Puerto: %lld\n        Nombre de usuario: root\n        Contraseña: %lld";
"扫描目录:" = "Examinar directorio:";
"证书文件缺失" = "Falta el archivo de certificado";
"正在安装 %@..." = "Instalando %@...";
"正在安装 %lld..." = "Instalando %lld...";
"高级配置" = "Configuración avanzada";
"分布式内存缓存" = "Caché de memoria distribuida";
"每天切割一次" = "Recortar una vez al día";
"用户名" = "Nombre de usuario";
"未找到匹配的数据库" = "No se encontró ninguna base de datos coincidente";
"用户" = "Usuario";
"PHP-FPM 端口" = "Puerto de PHP-FPM";
"无Web服务" = "Ningún servicio web";
"指定路径不是目录" = "La ruta especificada no es un directorio";
"错误报告级别" = "Nivel de informe de errores";
"创建网站" = "Crear sitio web";
"添加Vary头" = "Agregar encabezado Vary";
"主配置: %@ (普通文件)" = "Configuración principal: %@ (archivo normal)";
"主配置: %lld (普通文件)" = "Configuración principal: %lld (archivo normal)";
"请选择站点" = "Seleccione un sitio";
"新端口配置" = "Nueva configuración de puerto";
"正在检测 IP 地址..." = "Comprobando la dirección IP...";
"修复成功" = "Reparación realizada con éxito";
"配置文件不存在: %@" = "El archivo de configuración no existe: %@";
"配置文件不存在: %lld" = "El archivo de configuración no existe: %lld";
"无配置" = "Sin configuración";
"需人工输入" = "Requiere entrada manual";
"重启失败: %@" = "Error al reiniciar: %@";
"重启失败: %lld" = "Error al reiniciar: %lld";
"配置文件语法错误" = "Error de sintaxis en el archivo de configuración";
"内存键值存储" = "Almacenamiento en memoria de pares clave-valor";
"修改远程数据库连接信息" = "Modificar la información de conexión a la base de datos remota";
"⚠ 证书即将过期" = "⚠ El certificado está a punto de expirar";
"强制 HTTPS" = "Forzar HTTPS";
"重启失败" = "Error al reiniciar";
"安装 %@ 版本" = "Instalar la versión %@";
"安装 %lld 版本" = "Instalar la versión %lld";
"启动中" = "Iniciando";
"慢查询日志" = "Registro de consultas lentas";
"修改密码" = "Cambiar contraseña";
"• 强制重置将停止 MySQL 服务并以安全模式重启" = "• El restablecimiento forzado detendrá el servicio MySQL y lo reiniciará en modo seguro";
"站点配置修复" = "Corrección de la configuración del sitio";
"输入文本" = "Introduzca texto";
"保存注释" = "Guardar anotación";
"证书状态:" = "Estado del certificado:";
"数据处理" = "Procesamiento de datos";
"导出" = "Exportar";
"暂不支持该软件的配置" = "Actualmente no se admite la configuración de este software";
"InnoDB 配置" = "Configuración de InnoDB";
"警告" = "advertencia";
"保存证书数据失败: %@" = "No se pudo guardar los datos del certificado: %@";
"保存证书数据失败: %lld" = "No se pudieron guardar los datos del certificado: %lld";
"无法创建网络连接" = "No se puede establecer una conexión de red";
"JSON/PHP/JS/CSS/HTML 格式化" = "Formato JSON/PHP/JS/CSS/HTML";
"操作: %@" = "Operación: %@";
"操作: %lld" = "Operación: %lld";
"状态说明" = "Descripción del estado";
"正在加载配置..." = "Cargando configuración...";
"秒" = "segundos";
"失败请求" = "solicitud fallida";
"PHP版本" = "Versión de PHP";
"❌ 扫描配置目录失败: %@" = "❌ No se pudo escanear el directorio de configuración: %@";
"❌ 扫描配置目录失败: %lld" = "❌ Error al escanear el directorio de configuración: %lld";
"确定要从备份 %@ 恢复数据库吗？当前数据将被覆盖！" = "¿Está seguro de que desea restaurar la base de datos desde la copia de seguridad %@? ¡Los datos actuales se sobrescribirán!";
"确定要从备份 %lld 恢复数据库吗？当前数据将被覆盖！" = "¿Está seguro de que desea restaurar la base de datos desde la copia de seguridad %lld? ¡Los datos actuales se sobrescribirán!";
"修改后点击保存，原配置文件将自动备份" = "Tras modificar la configuración, haga clic en «Guardar» y el archivo de configuración original se respaldará automáticamente.";
"站点信息" = "Información del sitio";
"PHP 版本不匹配" = "Versión de PHP no coincidente";
"PHP 版本" = "Versión de PHP";
"运行状态" = "Estado de ejecución";
"配置已保存，正在重启..." = "Configuración guardada; reiniciando...";
"检测中..." = "En proceso de detección...";
"默认认证插件" = "Complemento de autenticación predeterminado";
"✓ 第三方源添加成功\\n" = "✓ Origen externo agregado correctamente\n";
"数据库名" = "Nombre de la base de datos";
"权限" = "Permisos";
"最近7天" = "Últimos 7 días";
"日志切割方式" = "Método de rotación de registros";
"访问被拒绝，请检查用户名和密码" = "Acceso denegado; compruebe el nombre de usuario y la contraseña";
"表单模式" = "Modo de formulario";
"在 Finder 中显示" = "Mostrar en el Finder";
"管理员名称" = "Nombre del administrador";
"\n\n        // 5. 执行证书申请\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = "// 5. Ejecutar la solicitud de certificado\n        let proceso = Process()\n        proceso.executableURL = URL(fileURLWithPath:";
"记录错误日志" = "Registrar el registro de errores";
"有效" = "válido";
"回滚前自动备份" = "Copia de seguridad automática antes de la reversión";
"找到 %@ 个候选站点" = "Se encontraron %@ sitios candidatos";
"找到 %lld 个候选站点" = "Se encontraron %lld sitios candidatos";
"使用 MySQL 数据库存储用户信息，适合多用户管理和大型站点" = "Utilice la base de datos MySQL para almacenar la información de los usuarios, adecuada para la gestión multiusuario y sitios grandes";
"端口 %@ 已被运行中的 %@ 占用" = "El puerto %@ ya está ocupado por %@ en ejecución";
"端口 %lld 已被运行中的 %lld 占用" = "El puerto %lld ya está ocupado por %lld en ejecución";
"修改 my.cnf 配置文件，将端口改为 3307" = "Modifique el archivo de configuración my.cnf y cambie el puerto a 3307";
"网站实际访问的目录，限制为网站目录下的一级子目录" = "El directorio al que accede realmente el sitio web se limita al subdirectorio de primer nivel dentro del directorio del sitio web.";
"网站运行正常，没有错误记录" = "El sitio web funciona correctamente sin errores registrados";
"SQL 模式" = "Modo SQL";
"修改端口" = "Modificar puerto";
"\n            throw LetsEncryptError.certificateIssueFailed(message: output)\n        }\n\n        // 6. 安装证书到指定目录\n        let certPath = " = "throw LetsEncryptError.certificateIssueFailed(message: output)\n        }\n\n        // 6. Instalar el certificado en el directorio especificado\n        let certPath =";
"现代加密库" = "Biblioteca de cifrado moderna";
"添加数据库" = "Agregar base de datos";
"导入前清空数据库" = "Borrar la base de datos antes de importar";
"正在测试 PHP 运行..." = "Probando la ejecución de PHP...";
"创建数据库失败: \\(dbResult.error ?? " = "Error al crear la base de datos: \\(dbResult.error ??";
"停止 Nginx 失败" = "Error al detener Nginx";
"确认删除" = "Confirmar eliminación";
"编辑后保持原管理状态" = "Mantener el estado original de administración tras la edición";
"连接配置" = "Configuración de conexión";
"为 PHP %@ 配置新端口，不影响现有服务" = "Configurar un nuevo puerto para PHP %@ sin afectar a los servicios existentes";
"为 PHP %lld 配置新端口，不影响现有服务" = "Configurar un nuevo puerto para PHP %lld sin afectar a los servicios existentes";
"SOAP协议支持" = "Compatibilidad con el protocolo SOAP";
"等待超时" = "Tiempo de espera agotado";
"请输入域名或备注" = "Introduzca un nombre de dominio o comentarios";
"数据目录" = "Directorio de datos";
"设置数据库权限" = "Establecer permisos de la base de datos";
"绑定域名" = "Asociar nombre de dominio";
"未知的认证响应: %@" = "Respuesta de autenticación desconocida: %@";
"未知的认证响应: %lld" = "Respuesta de autenticación desconocida: %lld";
"保存配置失败: %@" = "Error al guardar la configuración: %@";
"保存配置失败: %lld" = "Error al guardar la configuración: %lld";
"当前端口" = "Puerto actual";
"暴露PHP版本" = "Exponer la versión de PHP";
"TRACE 方法" = "Método TRACE";
"错误信息:" = "Mensaje de error:";
"当前端口: %@)" = "Puerto actual: %@)";
"当前端口: %lld)" = "Puerto actual: %lld)";
"到期时间" = "Hora de expiración";
"禁用类" = "Clase deshabilitada";
"请先设置服务工作空间" = "Configure primero el área de trabajo del servicio";
"全部软件" = "Todos los programas";
"\n        # phpMyAdmin Nginx 配置\n        # 由 JihePHPEnv 自动生成\n        # 生成时间: %@.string(from: Date()))\n\n        location /phpmyadmin {\n            alias %@;\n            index index.php;\n\n            # IP 访问限制\n            %@\n            deny all;\n\n            location ~ \\\\.php$ {\n                include fastcgi_params;\n                fastcgi_pass 127.0.0.1:9000;\n                fastcgi_index index.php;\n                fastcgi_param SCRIPT_FILENAME $request_filename;\n            }\n\n            # 禁止访问敏感文件\n            location ~ /\\\\. {\n                deny all;\n            }\n        }\n        " = "# Configuración de Nginx para phpMyAdmin\n        # Generado automáticamente por JihePHPEnv\n        # Hora de generación: %@.string(from: Date()))\n\n        location /phpmyadmin {\n            alias %@;\n            index index.php;\n\n            # Restricciones de acceso por IP\n            %@\n            deny all;\n\n            location ~ \\.php$ {\n                include fastcgi_params;\n                fastcgi_pass 127.0.0.1:9000;\n                fastcgi_index index.php;\n                fastcgi_param SCRIPT_FILENAME $request_filename;\n            }\n\n            # Denegar acceso a archivos sensibles\n            location ~ /\\. {\n                deny all;\n            }\n        }";
"\n        # phpMyAdmin Nginx 配置\n        # 由 JihePHPEnv 自动生成\n        # 生成时间: %lld.string(from: Date()))\n\n        location /phpmyadmin {\n            alias %lld;\n            index index.php;\n\n            # IP 访问限制\n            %lld\n            deny all;\n\n            location ~ \\\\.php$ {\n                include fastcgi_params;\n                fastcgi_pass 127.0.0.1:9000;\n                fastcgi_index index.php;\n                fastcgi_param SCRIPT_FILENAME $request_filename;\n            }\n\n            # 禁止访问敏感文件\n            location ~ /\\\\. {\n                deny all;\n            }\n        }\n        " = "# Configuración de Nginx para phpMyAdmin\n        # Generado automáticamente por JihePHPEnv\n        # Hora de generación: %lld.string(from: Date()))\n\n        location /phpmyadmin {\n            alias %lld;\n            index index.php;\n\n            # Restricciones de acceso por IP\n            %lld\n            deny all;\n\n            location ~ \\.php$ {\n                include fastcgi_params;\n                fastcgi_pass 127.0.0.1:9000;\n                fastcgi_index index.php;\n                fastcgi_param SCRIPT_FILENAME $request_filename;\n            }\n\n            # Denegar acceso a archivos sensibles\n            location ~ /\\. {\n                deny all;\n            }\n        }";
"MongoDB驱动" = "Controlador de MongoDB";
"请先安装 FTP 服务" = "Instale primero el servicio FTP";
"统一查看服务日志" = "Ver los registros de los servicios de forma unificada";
"一键安装" = "Instalación en un solo clic";
"若可用内存小于1G，可能会安装不上" = "Si la memoria disponible es inferior a 1 GB, es posible que no se pueda instalar.";
"✅ .htpasswd 文件已生成: %@" = "✅ Archivo .htpasswd generado correctamente: %@";
"✅ .htpasswd 文件已生成: %lld" = "✅ Archivo .htpasswd generado correctamente: %lld";
"❌ 加载证书数据失败: %@" = "❌ Error al cargar los datos del certificado: %@";
"❌ 加载证书数据失败: %lld" = "❌ Error al cargar los datos del certificado: %lld";
"网站不存在页" = "La página web no existe";
"[DependencyManager] 找到内置 mkcert: %@" = "[DependencyManager] Se ha encontrado mkcert integrado: %@";
"[DependencyManager] 找到内置 mkcert: %lld" = "[DependencyManager] Se ha encontrado mkcert integrado: %lld";
"最大数据包" = "Paquete de datos máximo";
"新建网站默认页面" = "Crear una página predeterminada nueva para el sitio web";
"正在获取状态..." = "Obteniendo estado...";
"欢迎使用" = "Bienvenido";
"需要安装 GoAccess 才能使用日志分析功能" = "Es necesario instalar GoAccess para utilizar la función de análisis de registros";
"删除选中项 (%@)" = "Eliminar los elementos seleccionados (%@)";
"删除选中项 (%lld)" = "Eliminar los elementos seleccionados (%lld)";
"请输入要格式化的代码" = "Introduzca el código que desea formatear";
")) 匹配的站点目录。请手动创建 Apache 配置或删除孤立的 Nginx 代理配置。" = ")) Directorios coincidentes del sitio. Cree manualmente la configuración de Apache o elimine la configuración de proxy de Nginx huérfana.";
"无法转换解密后的数据" = "No se pueden convertir los datos descifrados";
"随机生成" = "generado aleatoriamente";
"无法连接到服务器，请检查域名是否解析到正确的IP" = "No se puede conectar al servidor; compruebe si el nombre de dominio se resuelve en la dirección IP correcta";
"Hosts文件" = "Archivo hosts";
"原始模式支持直接编辑 /etc/hosts 全部内容。" = "El modo sin formato permite editar directamente todo el contenido del archivo /etc/hosts.";
"日本語" = "Japonés";
"\n        # Pure-FTPd 配置文件\n        # 由及和PHP环境自动生成\n\n        # 基础配置\n        ChrootEveryone              yes\n        BrokenClientsCompatibility  no\n        MaxClientsNumber            50\n        Daemonize                   yes\n        MaxClientsPerIP             8\n        VerboseLog                  no\n        DisplayDotFiles             yes\n        AnonymousOnly               no\n        NoAnonymous                 yes\n        SyslogFacility              ftp\n        DontResolve                 yes\n        MaxIdleTime                 15\n\n        # TLS 配置\n        TLS                         1\n\n        # 被动模式端口范围\n        PassivePortRange            30000 50000\n\n        " = "# Archivo de configuración de Pure-FTPd\n        # Generado automáticamente por un entorno PHP\n\n        # Configuración básica\n        ChrootEveryone yes\n        BrokenClientsCompatibility no\n        MaxClientsNumber 50\n        Daemonize yes\n        MaxClientsPerIP 8\n        VerboseLog no\n        DisplayDotFiles yes\n        AnonymousOnly no\n        NoAnonymous yes\n        SyslogFacility ftp\n        DontResolve yes\n        MaxIdleTime 15\n\n        # Configuración TLS\n        TLS 1\n\n        # Rango de puertos para el modo pasivo\n        PassivePortRange 30000 50000";
"保存设置" = "Guardar ajustes";
"无法创建测试文件" = "No se puede crear el archivo de prueba";
"手动调整各项配置" = "Ajustar manualmente diversas configuraciones";
"重新加载" = "recargar";
"其他配置" = "Otras configuraciones";
"重启所有" = "Reiniciar todo";
"生成报告失败" = "Error al generar el informe";
"• 生成后浏览器将自动信任由该 CA 签发的证书" = "• Una vez generado, el navegador confiará automáticamente en el certificado emitido por esta autoridad de certificación (CA)";
"访客趋势" = "Tendencias de visitantes";
"锁等待超时" = "Tiempo de espera agotado para el bloqueo";
"严格模式" = "modo estricto";
"请选择要查看的日志文件" = "Seleccione un archivo de registro para verlo";
"未知进程" = "proceso desconocido";
"批量设置" = "Configuración por lotes";
"开始之前，请先设置服务工作空间" = "Antes de comenzar, configure su área de trabajo del servicio";
"文件类型检测扩展" = "Extensión de detección de tipos de archivo";
"待安装" = "Por instalar";
"所有版本已安装" = "Todas las versiones instaladas";
"扫描路径:" = "Ruta de exploración:";
"正在检查 acme.sh 安装状态..." = "Comprobando el estado de instalación de acme.sh...";
"管理" = "gestión";
"\n                # HTTP VirtualHost\n                # Apache 后端端口：%@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ?? " = "#HTTPVirtualHost\n                # Puerto de backend de Apache: %@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ??";
"\n                # HTTP VirtualHost\n                # Apache 后端端口：%lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ?? " = "#HTTPVirtualHost\n                # Puerto de backend de Apache: %lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ??";
"PHP 版本:" = "Versión de PHP:";
"会话名称" = "nombre de la sesión";
"上次修改: %@)" = "Última modificación: %@";
"上次修改: %lld)" = "Última modificación: %lld)";
"修复配置" = "Corregir configuración";
"列表模式" = "modo de lista";
"全部端口" = "Todos los puertos";
">\n                        Options Indexes FollowSymLinks\n                        AllowOverride All\n                        Require all granted\n                    </Directory>\n\n                    # PHP-FPM 配置\n                    <FilesMatch \\\\.php$>\n                        SetHandler " = ">\n                        Opciones Indexes FollowSymLinks\n                        AllowOverride All\n                        Require all granted\n                    </Directory>\n\n                    #Configuración de PHP-FPM\n                    <FilesMatch \\\\.php$>\n                        SetHandler";
"显示正常站点" = "Mostrar sitio normal";
"acme.sh 是申请 Let's Encrypt 证书的必需工具" = "acme.sh es una herramienta necesaria para solicitar un certificado de Let's Encrypt";
"欢迎使用 及和PHP环境" = "Bienvenido al entorno de y PHP";
"数据库名称" = "Nombre de la base de datos";
"已搜索以下路径:" = "Se han buscado las siguientes rutas:";
"⚠ 版本不匹配，期望: %@" = "⚠ Versión incompatible; versión esperada: %@";
"⚠ 版本不匹配，期望: %lld" = "⚠ Versión incompatible; versión esperada: %lld";
"同步配置" = "Sincronizar configuración";
"✅ 已恢复配置: %@" = "✅ Configuración restaurada: %@";
"✅ 已恢复配置: %lld" = "✅ Configuración restaurada: %lld";
"自定义日志分析" = "Análisis personalizado de registros";
"已安装版本" = "Versión instalada";
"IP 地址" = "Dirección IP";
"JSON 格式错误: %@" = "Error de formato JSON: %@";
"JSON 格式错误: %lld" = "Error de formato JSON: %lld";
"扫描时间: %@)" = "Tiempo de análisis: %@)";
"扫描时间: %lld)" = "Tiempo de análisis: %lld)";
"• 大文件导入可能需要较长时间，请耐心等待" = "• La importación de archivos grandes puede tardar mucho tiempo; por favor, tenga paciencia.";
"请输入数据库名" = "Introduzca el nombre de la base de datos";
"禁用函数" = "Desactivar función";
"配置 PHP-FPM 端口失败: %@" = "No se ha podido configurar el puerto de PHP-FPM: %@";
"配置 PHP-FPM 端口失败: %lld" = "No se ha podido configurar el puerto de PHP-FPM: %lld";
"运行 'lsof -i :%@' 查看端口占用详情" = "Ejecute «lsof -i :%@» para ver los detalles de la ocupación del puerto";
"运行 'lsof -i :%lld' 查看端口占用详情" = "Ejecute «lsof -i :%lld» para ver los detalles de la ocupación del puerto";
"缓存器" = "memoria intermedia";
"5 分钟" = "5 minutos";
"%@ 重启失败 (退出码: %@)" = "%@: reinicio fallido (código de salida: %@)";
"%lld 重启失败 (退出码: %lld)" = "%lld: reinicio fallido (código de salida: %lld)";
"独立文件" = "archivo independiente";
"证书类型" = "Tipo de certificado";
"✅ Nginx 配置文件已生成: %@" = "✅ El archivo de configuración de Nginx se ha generado correctamente: %@";
"✅ Nginx 配置文件已生成: %lld" = "✅ El archivo de configuración de Nginx se ha generado correctamente: %lld";
"最活跃时段" = "periodo de mayor actividad";
"伪静态" = "pseudoestático";
"配置 Pure-FTPd..." = "Configurando Pure-FTPd...";
"测试中..." = "Probando...";
"更改日志路径" = "Cambiar la ruta del registro";
"❌ 解析证书失败: %@" = "❌ Error al analizar el certificado: %@";
"❌ 解析证书失败: %lld" = "❌ Error al analizar el certificado: %lld";
"正在分析日志..." = "Analizando registros...";
"目录扫描和探测行为" = "Comportamiento de exploración y sondeo de directorios";
"点击\\\"签发证书\\\"按钮创建新的站点证书" = "Haga clic en el botón «Emitir certificado» para crear un nuevo certificado de sitio";
"未找到要删除的远程数据库连接" = "No se encontró la conexión remota a la base de datos que se iba a eliminar";
"生效日期" = "Fecha de vigencia";
"无效的 URL" = "URL no válida";
"打开可执行文件位置" = "Abrir la ubicación del archivo ejecutable";
"搜索软件名称..." = "Buscar nombre del software...";
"请求体缓冲区" = "Buffer del cuerpo de la solicitud";
"写入 Hosts 文件" = "Escribir en el archivo Hosts";
"检测到括号不匹配，已尽力格式化。请检查左侧波浪红线标注位置。" = "Se detectó una discrepancia en los corchetes; se realizó un formateo con el máximo esfuerzo posible. Revise la posición marcada con la línea roja ondulada a la izquierda.";
"最大空闲进程" = "Proceso inactivo máximo";
"显示 %@ 个结果" = "Mostrando %@ resultados";
"显示 %lld 个结果" = "Mostrando %lld resultados";
"导出配置" = "Exportar configuración";
"当前步骤:" = "Pasos actuales:";
"复制到 Navicat" = "Copiar a Navicat";
"有备份" = "Tiene una copia de seguridad";
"软件管理" = "Gestión de software";
"异常退出" = "Salida anómala";
"自签名" = "autofirmado";
"启动" = "iniciar";
"请输入密码" = "Introduzca la contraseña";
"设置后可避免系统目录权限问题，便于统一管理和备份。" = "Tras su configuración, se evitan los problemas de permisos en los directorios del sistema y se facilita la gestión y copia de seguridad unificadas.";
"• 开源的 ACME 协议客户端" = "• Cliente de protocolo ACME de código abierto";
"配置 Apache 失败" = "Error al configurar Apache";
"• 支持 .log 和 .txt 文件" = "• Admite archivos .log y .txt";
"数据库端口" = "Puerto de la base de datos";
"✅ 已备份旧配置: %@" = "✅ La configuración anterior se ha respaldado: %@";
"✅ 已备份旧配置: %lld" = "✅ La configuración anterior se ha respaldado: %lld";
"Nginx FastCGI 连接端口（localhost）" = "Puerto de conexión FastCGI de Nginx (localhost)";
"错误日志路径" = "Ruta del registro de errores";
"\n            # ============================================\n            # 🔄 Nginx 反向代理配置 (Apache 后端)\n            # 此配置将请求转发到 Apache 后端服务器\n            # 前端端口: 80 (Nginx)\n            # 后端端口: %@ (Apache)\n            # ============================================\n\n            server {\n                listen 80;\n                server_name %@;\n            %@\n                location / {\n                    proxy_pass http://127.0.0.1:%@;\n                    proxy_set_header Host $host;\n                    proxy_set_header X-Real-IP $remote_addr;\n                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                    proxy_set_header X-Forwarded-Proto $scheme;\n                    proxy_set_header X-Forwarded-Host $host;\n                    proxy_set_header X-Forwarded-Port $server_port;\n                }\n            }\n            " = "# ============================================\n            # 🔄 Configuración de proxy inverso de Nginx (servidor backend Apache)\n            # Esta configuración redirige las solicitudes al servidor backend Apache\n            # Puerto frontal: 80 (Nginx)\n            # Puerto de backend: %@ (Apache)\n            # ============================================\n\n            server {\n                listen 80;\n                server_name %@;\n            %@\n                location/{\n                    proxy_pass http://127.0.0.1:%@;\n                    proxy_set_header Host $host;\n                    proxy_set_header X-Real-IP $remote_addr;\n                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                    proxy_set_header X-Forwarded-Proto $scheme;\n                    proxy_set_header X-Forwarded-Host $host;\n                    proxy_set_header X-Forwarded-Port $server_port;\n                }\n            }";
"\n            # ============================================\n            # 🔄 Nginx 反向代理配置 (Apache 后端)\n            # 此配置将请求转发到 Apache 后端服务器\n            # 前端端口: 80 (Nginx)\n            # 后端端口: %lld (Apache)\n            # ============================================\n\n            server {\n                listen 80;\n                server_name %lld;\n            %lld\n                location / {\n                    proxy_pass http://127.0.0.1:%lld;\n                    proxy_set_header Host $host;\n                    proxy_set_header X-Real-IP $remote_addr;\n                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                    proxy_set_header X-Forwarded-Proto $scheme;\n                    proxy_set_header X-Forwarded-Host $host;\n                    proxy_set_header X-Forwarded-Port $server_port;\n                }\n            }\n            " = "# ============================================\n            # 🔄 Configuración de proxy inverso de Nginx (servidor backend Apache)\n            # Esta configuración redirige las solicitudes al servidor backend Apache\n            # Puerto frontal: 80 (Nginx)\n            # Puerto de backend: %lld (Apache)\n            # ============================================\n\n            server {\n                listen 80;\n                server_name %lld;\n            %lld\n                location/{\n                    proxy_pass http://127.0.0.1:%lld;\n                    proxy_set_header Host $host;\n                    proxy_set_header X-Real-IP $remote_addr;\n                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                    proxy_set_header X-Forwarded-Proto $scheme;\n                    proxy_set_header X-Forwarded-Host $host;\n                    proxy_set_header X-Forwarded-Port $server_port;\n                }\n            }";
"有(%@)" = "Sí (%@)";
"有(%lld)" = "Sí (%lld)";
"脚本缓存器" = "caché de scripts";
"使用 %@" = "Usar %@";
"使用 %lld" = "Usar %lld";
"错误 %@: %@" = "Error %@: %@";
"错误 %lld: %lld" = "Error %lld: %lld";
"自動" = "automático";
"路径缓存大小" = "Tamaño de la caché de rutas";
"请输入要计算哈希值的文本" = "Introduzca el texto para el que se calculará el valor hash";
"系统监控" = "Supervisión del sistema";
"单进程最大连接数" = "Número máximo de conexiones por proceso";
"HTTP 配置" = "Configuración HTTP";
"证书已过期" = "El certificado ha expirado";
"选择导出目录" = "Seleccionar directorio de exportación";
"历史备份" = "Copia de seguridad histórica";
"Cookie生命周期" = "Ciclo de vida de las cookies";
"端口详情已复制到剪贴板" = "Detalles del puerto copiados al portapapeles";
"Gzip 压缩" = "Compresión Gzip";
"fastcgi_pass 需要在 Apache 中配置 mod_proxy_fcgi" = "fastcgi_pass requiere que mod_proxy_fcgi esté configurado en Apache";
"确定要删除备份 %@ 吗？" = "¿Está seguro de que desea eliminar la copia de seguridad %@?";
"确定要删除备份 %lld 吗？" = "¿Está seguro de que desea eliminar la copia de seguridad %lld?";
"⚠️ 证书目录不存在: %@" = "⚠️ El directorio de certificados no existe: %@";
"⚠️ 证书目录不存在: %lld" = "⚠️ El directorio de certificados no existe: %lld";
"事件模型" = "modelo de evento";
"端口已更新为 %@，相关配置已同步" = "El puerto se ha actualizado a %@ y la configuración relacionada se ha sincronizado";
"端口已更新为 %lld，相关配置已同步" = "El puerto se ha actualizado a %lld y la configuración relacionada se ha sincronizado";
"• 修复所有站点：批量强力修复所有站点的配置文件" = "• Reparar todos los sitios: Repara de forma eficaz los archivos de configuración de todos los sitios en lote";
"证书尚未生效" = "El certificado aún no es válido";
"恢复配置失败" = "No se pudo restaurar la configuración";
"错误信息" = "mensaje de error";
"创建运行目录失败: %@\\n请检查路径权限或修改运行目录" = "No se pudo crear el directorio de ejecución: %@\nCompruebe los permisos de la ruta o modifique el directorio de ejecución";
"创建运行目录失败: %lld\\n请检查路径权限或修改运行目录" = "No se pudo crear el directorio de ejecución: %lld\nCompruebe los permisos de la ruta o modifique el directorio de ejecución";
"读取目录失败: %@" = "No se pudo leer el directorio: %@";
"读取目录失败: %lld" = "No se pudo leer el directorio: %lld";
"mysql/{版本}/" = "mysql/{version}/";
"配置文件未找到" = "No se encontró el archivo de configuración";
"站点配置已导出到: %@" = "Configuración del sitio exportada a: %@";
"站点配置已导出到: %lld" = "Configuración del sitio exportada a: %lld";
"IP限制" = "Restricciones de IP";
"信息" = "información";
"字节" = "bytes";
"读取日志文件失败: %@" = "Error al leer el archivo de registro: %@";
"读取日志文件失败: %lld" = "Error al leer el archivo de registro: %lld";
"安装 %@" = "Instalar %@";
"安装 %lld" = "Instalar %lld";
"点击备份" = "Haga clic en la copia de seguridad";
"选择站点" = "Seleccione el sitio";
"3. 确认网站服务正在运行" = "3. Confirme que el servicio del sitio web está en ejecución";
"YAML解析支持" = "Compatibilidad con el análisis de YAML";
"默认 PHP:" = "PHP predeterminado:";
"已检测到" = "Detectado";
"天" = "día";
"正在扫描目录..." = "Escaneando el directorio...";
"TLS设置" = "Configuración de TLS";
"需确认" = "Requiere confirmación";
"确认批量删除" = "Confirmar eliminación masiva";
"表数量" = "Número de tablas";
"根目录不能为空" = "El directorio raíz no puede estar vacío";
"数据库位置" = "Ubicación de la base de datos";
"这可能需要几秒钟，请耐心等待" = "Esto puede tardar unos segundos; por favor, tenga paciencia";
"统一管理" = "Gestión unificada";
"设置完成" = "Configuración completada";
"IP 列表" = "Lista de direcciones IP";
"进程名称" = "Nombre del proceso";
"设为默认" = "Establecer como predeterminado";
"开始导入" = "Iniciar importación";
"证书即将过期（%@天）" = "El certificado está a punto de expirar (%@ días)";
"证书即将过期（%lld天）" = "El certificado está a punto de expirar (%lld días)";
"正在加载 PHP %@ 扩展..." = "Cargando la extensión de PHP %@...";
"正在加载 PHP %lld 扩展..." = "Cargando la extensión de PHP %lld...";
"数据库已成功导入" = "La base de datos se ha importado correctamente";
"服务启动失败，请检查配置文件" = "Error al iniciar el servicio; revise el archivo de configuración";
"# 由及和PHP环境自动生成" = "# Generado automáticamente por el entorno PHP";
"连接信息" = "Información de conexión";
"数据库 %@ 已删除" = "Base de datos %@ eliminada";
"数据库 %lld 已删除" = "Base de datos %lld eliminada";
"SMTP端口" = "Puerto SMTP";
"已禁用" = "Deshabilitado";
"服务器备注" = "Notas del servidor";
"配置已保存" = "Configuración guardada";
"刷新方法" = "Método de actualización";
"• 证书到期前 30 天将自动提醒续期" = "• Recordatorio automático de renovación 30 días antes de la expiración del certificado";
"以下参数不在系统配置表中，可能存在风险" = "Los siguientes parámetros no figuran en la tabla de configuración del sistema y podrían suponer un riesgo";
"修改远程数据库密码" = "Cambiar la contraseña de la base de datos remota";
"• 支持MySQL5.5、MariaDB10.1及以上版本" = "• Compatible con MySQL 5.5, MariaDB 10.1 y versiones posteriores";
"停止中" = "Deteniéndose";
"分析内容包括：" = "El análisis incluye:";
"✓ 已复制默认首页到: %@" = "✓ Se ha copiado la página de inicio predeterminada a: %@";
"✓ 已复制默认首页到: %lld" = "✓ Se ha copiado la página de inicio predeterminada a: %lld";
"管理员密码" = "Contraseña de administrador";
"批量停止" = "Detención por lotes";
"Homebrew 未安装。请访问 https://brew.sh 安装 Homebrew" = "Homebrew no está instalado. Visite https://brew.sh para instalar Homebrew";
"\n            [mysqld]\n            # 基础配置\n            port = 3306\n            bind-address = 127.0.0.1\n            character-set-server = utf8mb4\n            collation-server = utf8mb4_general_ci\n\n            # 连接配置\n            max_connections = 151\n            wait_timeout = 28800\n\n            # InnoDB 配置\n            innodb_buffer_pool_size = 128M\n            innodb_log_file_size = 48M\n            innodb_flush_log_at_trx_commit = 1\n\n            [client]\n            default-character-set = utf8mb4\n            " = "[mysqld]\n            # Configuración básica\n            puerto=3306\n            bind-address = 127.0.0.1\n            character-set-server = utf8mb4\n            collation-server = utf8mb4_general_ci\n\n            # Configuración de conexión\n            max_connections = 151\n            wait_timeout = 28800\n\n            # Configuración de InnoDB\n            innodb_buffer_pool_size = 128M\n            innodb_log_file_size = 48M\n            innodb_flush_log_at_trx_commit = 1\n\n            [client]\n            default-character-set=utf8mb4";
"所有必要端口正常监听" = "Todos los puertos necesarios se están supervisando correctamente";
"应用到选中项" = "Aplicar a los elementos seleccionados";
"配置文件信息:\\n%@" = "Información del archivo de configuración:\n%@";
"配置文件信息:\\n%lld" = "Información del archivo de configuración:\n%lld";
"未找到 mysql 客户端" = "No se encontró el cliente mysql";
"自动滚动" = "desplazamiento automático";
"端口状态" = "estado del puerto";
"未检测到密码" = "No se ha detectado la contraseña";
"HTTPS 访问" = "Acceso HTTPS";
"导入失败" = "Error al importar";
"MySQL 重启成功" = "MySQL se ha reiniciado correctamente";
"MD5/SHA 哈希计算" = "Cálculo de hash MD5/SHA";
"浅色" = "color claro";
"应用内未找到 phpMyAdmin" = "phpMyAdmin no se encuentra en la aplicación";
"功能未实现" = "Función no implementada";
"加密算法" = "algoritmo de cifrado";
"接管模式" = "Modo de toma de control";
"更新 Web 服务器配置中的 PHP-FPM 连接端口" = "Actualizar el puerto de conexión de PHP-FPM en la configuración del servidor web";
"检测 Root 密码" = "Detectar la contraseña de administrador (root)";
"确定要删除数据库 %@ 吗？此操作不可恢复。" = "¿Está seguro de que desea eliminar la base de datos %@? Esta operación no se puede deshacer.";
"确定要删除数据库 %lld 吗？此操作不可恢复。" = "¿Está seguro de que desea eliminar la base de datos %lld? Esta operación no se puede deshacer.";
"请输入要加密的文本" = "Introduzca el texto que desea cifrar";
"SSL 架构模式:" = "Patrón de arquitectura SSL:";
"连接失败: %@" = "Error de conexión: %@";
"连接失败: %lld" = "Error de conexión: %lld";
"密码不能为空" = "La contraseña no puede estar vacía";
"更换PHP版本失败" = "No se pudo cambiar la versión de PHP";
"显示主窗口" = "Mostrar ventana principal";
"域名无法解析，请检查DNS设置" = "No se puede resolver el nombre de dominio; compruebe la configuración de DNS";
"路径不是目录" = "La ruta no es un directorio";
"✓ 端口 %@ 正在监听" = "✓ El puerto %@ está escuchando";
"✓ 端口 %lld 正在监听" = "✓ El puerto %lld está escuchando";
"导入数据库失败" = "Error al importar la base de datos";
"加载中..." = "Cargando...";
"加密失败: %@" = "Error de cifrado: %@";
"加密失败: %lld" = "Error de cifrado: %lld";
"管理员邮箱" = "Correo electrónico del administrador";
"允许本地导入" = "Permitir importación local";
"自定义文件..." = "Archivos personalizados...";
"工作进程数" = "Número de procesos de trabajo";
"附加功能" = "Características adicionales";
"欢迎使用及和PHP环境" = "Bienvenido al entorno PHP";
"使用原路径" = "Usar ruta original";
"Base64/URL/HTML 编码" = "Codificación Base64/URL/HTML";
"清空扫描日志" = "Borrar registro de análisis";
"独立表空间" = "Espacio de tablas independiente";
"备注" = "Observaciones";
"压缩" = "Compresión";
"最大输入时间" = "Tiempo máximo de entrada";
"主机 (% 表示任意主机)" = "Host (el carácter % significa cualquier host)";
"显示常用配置项" = "Mostrar elementos de configuración comúnmente utilizados";
"导出站点配置" = "Exportar la configuración del sitio";
"内存配置" = "Configuración de memoria";
"生效时间: %@)" = "Tiempo efectivo: %@";
"生效时间: %lld)" = "Tiempo efectivo: %lld";
"文本哈希计算" = "Cálculo del hash del texto";
"日志文件不存在: %@\\n\\n提示：请确认服务已安装并运行过。" = "El archivo de registro no existe: %@\\n\\nSugerencia: Confirme que el servicio se ha instalado y está en ejecución.";
"日志文件不存在: %lld\\n\\n提示：请确认服务已安装并运行过。" = "El archivo de registro no existe: %lld\\n\\nSugerencia: Confirme que el servicio se ha instalado y está en ejecución.";
"失败" = "error";
"\n        【基本信息】\n        服务名称: %@\n        端口号: %@\n        状态: %@\n\n        【进程信息】\n        进程 PID: %@\n        进程名称: %@\n        父进程 PID: %@\n        运行用户: %@\n        启动时间: %@\n\n        【路径信息】\n        可执行文件: %@\n        工作目录: %@\n\n        【命令行】\n        %@\n        " = "【Información básica】\n        Nombre del servicio: %@\n        Número de puerto: %@\n        Estado: %@\n\n        [Información del proceso]\n        PID del proceso: %@\n        Nombre del proceso: %@\n        PID del proceso padre: %@\n        Usuario en ejecución: %@\n        Hora de inicio: %@\n\n        【Información de rutas】\n        Archivo ejecutable: %@\n        Directorio de trabajo: %@\n\n        【Línea de comandos】\n        %@";
"\n        【基本信息】\n        服务名称: %lld\n        端口号: %lld\n        状态: %lld\n\n        【进程信息】\n        进程 PID: %lld\n        进程名称: %lld\n        父进程 PID: %lld\n        运行用户: %lld\n        启动时间: %lld\n\n        【路径信息】\n        可执行文件: %lld\n        工作目录: %lld\n\n        【命令行】\n        %lld\n        " = "【Información básica】\n        Nombre del servicio: %lld\n        Número de puerto: %lld\n        Estado: %lld\n\n        [Información del proceso]\n        PID del proceso: %lld\n        Nombre del proceso: %lld\n        PID del proceso padre: %lld\n        Usuario en ejecución: %lld\n        Hora de inicio: %lld\n\n        【Información de rutas】\n        Archivo ejecutable: %lld\n        Directorio de trabajo: %lld\n\n        【Línea de comandos】\n        %lld";
"MySQL默认Socket" = "Socket predeterminado de MySQL";
"系统临时目录" = "Directorio temporal del sistema";
"版本 %@" = "Versión %@";
"版本 %lld" = "Versión %lld";
"正在扫描端口..." = "Escaneando puertos...";
"一次接受多个连接" = "Aceptar múltiples conexiones simultáneamente";
"无法连接到远程数据库" = "No se puede conectar a la base de datos remota";
"强制GTID一致性" = "Aplicar la coherencia de GTID";
"输入用户名" = "Introduzca el nombre de usuario";
"404 Not Found (未找到)" = "404 No encontrado";
"脚本解密" = "Descifrado de scripts";
"申请SSL证书" = "Solicitar un certificado SSL";
"准备安装 Apache..." = "Preparando la instalación de Apache...";
"正在检测端口占用情况..." = "Comprobando la ocupación del puerto...";
"Nginx和Apache都使用SSL（用于特殊需求）" = "Tanto Nginx como Apache utilizan SSL (para necesidades especiales)";
"已修复 %@ 个站点的配置文件" = "Archivos de configuración corregidos para los sitios de %@";
"已修复 %lld 个站点的配置文件" = "Archivos de configuración corregidos para %lld sitios";
"本地 MySQL 数据库" = "Base de datos MySQL local";
") 使用相同端口" = ") utilizan el mismo puerto";
"站点不存在" = "El sitio no existe";
"超过保留天数的日志文件将被自动删除" = "Los archivos de registro que superen el número de días de retención se eliminarán automáticamente";
"扫描状态:" = "Estado del análisis:";
"2025-01-01 至 2035-01-01" = "Del 01-01-2025 al 01-01-2035";
"配置检测:\\n%@" = "Comprobación de la configuración:\\n%@";
"配置检测:\\n%lld" = "Comprobación de la configuración:\\n%lld";
"MySQL改进版扩展" = "Extensión de versión mejorada de MySQL";
"用户名不能为空" = "El nombre de usuario no puede estar vacío";
"添加至" = "Agregar a";
"\n        # 转换自 Nginx 规则\n        # 注意：部分规则可能需要手动调整\n\n        <IfModule mod_rewrite.c>\n            RewriteEngine On\n\n        " = "#Convertido desde las reglas de Nginx\n        #Nota: Es posible que algunas reglas deban ajustarse manualmente\n\n        <IfModule mod_rewrite.c>\n            RewriteEngine On";
"转换警告" = "Advertencia de conversión";
"启用" = "Habilitar";
"Redo日志容量" = "Capacidad del registro de rehacer (redo log)";
"停止 %@ 失败" = "Error al detener %@.";
"停止 %lld 失败" = "Error al detener %lld.";
"导出成功" = "Exportación correcta";
"日志查看" = "Vista de registros";
"请求路径" = "Ruta de la solicitud";
"签发成功" = "Emitido correctamente";
"未找到要更新的数据库连接" = "No se encontró la conexión a la base de datos para actualizar";
"排序缓冲大小" = "Tamaño del búfer de ordenación";
"连接成功，MySQL版本: %@" = "Conexión establecida correctamente. Versión de MySQL: %@";
"连接成功，MySQL版本: %lld" = "Conexión establecida correctamente. Versión de MySQL: %lld";
"即将过期" = "Caduca pronto";
"主配置: %@ -> %@ (软链接)" = "Configuración principal: %@ → %@ (enlace simbólico)";
"主配置: %lld -> %lld (软链接)" = "Configuración principal: %lld → %lld (enlace simbólico)";
"颁发者" = "emisor";
"加载日志失败: %@" = "Error al cargar el registro: %@";
"加载日志失败: %lld" = "Error al cargar el registro: %lld";
"日志设置" = "Configuración de registros";
"源规则" = "reglas de origen";
"手动停止" = "detención manual";
"加载数据库数据失败，且无法恢复备份" = "Error al cargar los datos de la base de datos y no se puede restaurar la copia de seguridad";
"PHP 脚本语言" = "Lenguaje de programación PHP";
"⚠️ 工作空间未初始化,跳过证书扫描" = "⚠️ El área de trabajo no está inicializada; se omite el análisis de certificados";
"申请 Let's Encrypt 证书" = "Solicitar un certificado de Let's Encrypt";
"无效响应" = "Respuesta no válida";
"修改密码失败，请检查 MySQL 连接" = "Error al cambiar la contraseña; compruebe la conexión con MySQL";
"访客数量和请求趋势分析" = "Análisis de tendencias del número de visitantes y de solicitudes";
"安装中" = "Instalando";
"   - 文档根目录: %@" = "- Directorio raíz del documento: %@";
"   - 文档根目录: %lld" = "- Directorio raíz del documento: %lld";
"更新于: %@)" = "Actualizado el: %@)";
"更新于: %lld)" = "Actualizado el: %lld)";
"正在验证..." = "Verificando...";
"访问权限" = "Permisos de acceso";
"备份跳过" = "Copia de seguridad omitida";
"选择连接" = "Seleccionar conexión";
"检查更新" = "Buscar actualizaciones";
"配置" = "Configuración";
"哈希计算" = "Cálculo del hash";
"检测到 %@ 配置文件位于原始路径：\\n%@\\n\\n是否将其迁移到工作空间？\\n目标路径：%@" = "Se ha detectado el archivo de configuración %@ en la ruta original:\\n%@\\n\\n¿Desea migrarlo al área de trabajo?\\nRuta de destino: %@";
"检测到 %lld 配置文件位于原始路径：\\n%lld\\n\\n是否将其迁移到工作空间？\\n目标路径：%lld" = "Se han detectado %lld archivos de configuración en la ruta original:\n%lld\n\n¿Desea migrarlos al área de trabajo?\nRuta de destino: %lld";
"最近一年" = "el año pasado";
"新密码" = "nueva contraseña";
"修复说明" = "Instrucciones de reparación";
"✗ 复制默认首页失败: %@" = "✗ No se ha podido copiar la página de inicio predeterminada: %@";
"✗ 复制默认首页失败: %lld" = "✗ No se ha podido copiar la página de inicio predeterminada: %lld";
"站点管理设置" = "Ajustes de administración de sitios";
"端口管理" = "Administración de puertos";
"创建 FTP 账号" = "Crear cuenta FTP";
"Zlib输出压缩" = "Compresión de salida Zlib";
"警告: _errorpage_ 目录不存在: %@，跳过复制错误页面" = "Advertencia: el directorio _errorpage_ no existe: %@; se omite la copia de la página de error";
"警告: _errorpage_ 目录不存在: %lld，跳过复制错误页面" = "Advertencia: el directorio _errorpage_ no existe: %lld; se omite la copia de la página de error";
"• Apache Combined 格式" = "• Formato combinado de Apache";
"证书信息" = "Información del certificado";
"默认站点主目录" = "Directorio principal predeterminado del sitio";
"保存失败" = "Error al guardar";
"未找到 mysqldump 工具" = "No se encontró la herramienta mysqldump";
"自定义路径" = "ruta personalizada";
"读取加密密码失败" = "Error al leer la contraseña cifrada";
"复制端口" = "Copiar puerto";
"批量禁用" = "Deshabilitar por lotes";
"路径" = "ruta";
"✗ PHP 测试失败" = "✗ La prueba de PHP ha fallado";
"brew 输出:\\n%@" = "Salida de brew:\n%@";
"brew 输出:\\n%lld" = "Salida de brew:\n%lld";
"未知错误" = "error desconocido";
"配置文件已迁移到工作空间" = "Los archivos de configuración se han migrado al área de trabajo";
"搜索数据库名或备注" = "Buscar nombre de base de datos o notas";
"网站管理" = "Gestión de sitios web";
"配置文件格式无效: %@" = "Formato de archivo de configuración no válido: %@";
"配置文件格式无效: %lld" = "Formato de archivo de configuración no válido: %lld";
"打开目录" = "Abrir directorio";
"检测到冲突：该域名在 %@ 中也有配置" = "Detección de conflicto: el nombre de dominio también está configurado en %@";
"检测到冲突：该域名在 %lld 中也有配置" = "Detección de conflicto: el nombre de dominio también está configurado en %lld";
"数据库权限已更新" = "Permisos de la base de datos actualizados";
"同时上传文件数" = "Número de archivos cargados simultáneamente";
"工作空间: 未初始化（使用 Homebrew 默认配置）" = "Área de trabajo: no inicializada (usando la configuración predeterminada de Homebrew)";
"强制保存" = "Forzar guardado";
"Let's Encrypt 证书" = "Certificado de Let's Encrypt";
"AES 加密解密工具" = "Herramienta de cifrado y descifrado AES";
"规则差异对比" = "Comparación de las diferencias entre reglas";
"# 请手动卸载" = "# Desinstale manualmente, por favor";
"未选择" = "No seleccionado";
"确认删除数据库" = "Confirmar eliminación de la base de datos";
"打开终端" = "Abrir terminal";
"MySQL 版本: %@" = "Versión de MySQL: %@";
"MySQL 版本: %lld" = "Versión de MySQL: %lld";
"启用默认站点" = "Habilitar sitio predeterminado";
"有效期（年）" = "Periodo de validez (años)";
"转换后规则" = "Reglas posteriores a la conversión";
"安装 Nginx 失败" = "Error al instalar Nginx";
"强力修复" = "Reparación potente";
"父目录没有写权限，请修改路径" = "El directorio padre no tiene permiso de escritura; modifique la ruta, por favor.";
"站点证书" = "Certificado del sitio";
"删除成功" = "Eliminación realizada correctamente";
"服务器地址不能为空" = "La dirección del servidor no puede estar vacía";
"生成" = "generar";
"二进制日志缓存大小" = "Tamaño de la caché del registro binario";
"确定要彻底删除站点 %@ 吗？此操作不可恢复。" = "¿Está seguro de que desea eliminar por completo el sitio %@? Esta operación no se puede deshacer.";
"确定要彻底删除站点 %lld 吗？此操作不可恢复。" = "¿Está seguro de que desea eliminar por completo el sitio %lld? Esta operación no se puede deshacer.";
"带宽使用" = "Uso del ancho de banda";
"最大缓存文件数" = "Número máximo de archivos en caché";
"容量未计算" = "Capacidad no calculada";
"总体统计" = "Estadísticas generales";
"• 注意1：请确保本服务器有访问数据库的权限" = "• Nota 1: Asegúrese de que este servidor tenga permiso para acceder a la base de datos";
"记录无索引查询" = "Consulta de registros sin índice";
"错误日志级别" = "Nivel de registro de errores";
"• 强力修复：根据站点信息重新生成配置文件（不依赖历史记录）" = "• Reparación potente: regenerar los archivos de configuración basándose en la información del sitio (sin depender del historial)";
"卸载 %@ 失败" = "Error al desinstalar %@";
"卸载 %lld 失败" = "Error al desinstalar %lld";
"卸载中" = "Desinstalando";
"错误" = "Error";
"点击应用建议值" = "Haga clic en «Aplicar valores sugeridos»";
"域名 %@ 无法访问：%@" = "No se puede acceder al nombre de dominio %@: %@";
"域名 %lld 无法访问：%lld" = "No se puede acceder al nombre de dominio %lld: %lld";
"高性能Excel读写" = "Lectura y escritura de Excel de alto rendimiento";
"正在切换 PHP 版本..." = "Cambiando la versión de PHP...";
"发送数据失败" = "Error al enviar los datos";
"SSL 证书签发与管理" = "Emisión y gestión de certificados SSL";
"最常访问的页面和资源" = "Páginas y recursos más visitados";
"修改数据库密码" = "Cambiar la contraseña de la base de datos";
"请求头 (Headers)" = "Encabezados de la solicitud";
"自启动设置" = "Configuración de inicio automático";
"您选择的PHP %@ 当前已停止运行。\\n\\n使用已停止的PHP版本可能导致网站无法正常运行PHP程序。\\n\\n建议先启动该PHP版本，或选择其他运行中的版本。" = "La versión de PHP seleccionada (%@) está actualmente detenida.\n\nEl uso de una versión obsoleta de PHP podría impedir que el sitio web ejecute correctamente los programas PHP.\n\nSe recomienda iniciar primero esta versión de PHP o seleccionar otra versión que esté en ejecución.";
"您选择的PHP %lld 当前已停止运行。\\n\\n使用已停止的PHP版本可能导致网站无法正常运行PHP程序。\\n\\n建议先启动该PHP版本，或选择其他运行中的版本。" = "La versión de PHP seleccionada (%lld) está actualmente detenida.\n\nEl uso de una versión obsoleta de PHP podría impedir que el sitio web ejecute correctamente los programas PHP.\n\nSe recomienda iniciar primero esta versión de PHP o seleccionar otra versión que esté en ejecución.";
"软件未安装" = "Software no instalado";
"请稍候，正在创建站点配置..." = "Espere, se está creando la configuración del sitio...";
"启动失败" = "Error al iniciar";
"已安装" = "Instalado";
"• 重置过程中数据库将暂时不可用" = "• La base de datos estará temporalmente indisponible durante el proceso de restablecimiento";
"设置成功" = "Configuración completada correctamente";
"选择 \\(serverType == .nginx ? " = "Seleccionar \\(serverType == .nginx ?";
"关于及和PHP环境" = "Acerca del entorno PHP Jihe";
"⚠ 证书尚未生效" = "⚠ El certificado aún no es válido";
"删除数据库失败" = "Error al eliminar la base de datos";
"重要配置" = "Configuración importante";
"资源限制" = "Límites de recursos";
"检查所有表" = "Comprobar todas las tablas";
"安装完成" = "Instalación completada";
"转换完成，建议测试后使用" = "Conversión completada; se recomienda su uso tras realizar pruebas";
"分析报告" = "informe de análisis";
"已信任" = "De confianza";
"✅ 扫描到 %@ 个证书文件" = "✅ Se han analizado %@ archivos de certificado";
"✅ 扫描到 %lld 个证书文件" = "✅ Se han analizado %lld archivos de certificado";
"配置端口失败" = "Error al configurar el puerto";
"共 %@ 条记录" = "Total: %@ registros";
"共 %lld 条记录" = "Total: %lld registros";
"• 密码长度建议至少8位，包含字母和数字" = "• Se recomienda que la contraseña tenga al menos 8 caracteres, incluyendo letras y números";
"Nginx处理SSL，转发HTTP到Apache（推荐）" = "Nginx gestiona SSL y redirige HTTP a Apache (recomendado)";
"按年" = "por año";
"生成反向代理配置前自动备份" = "Copia de seguridad automática antes de generar la configuración del proxy inverso";
"暂无可用站点，请先创建站点" = "Aún no hay sitios disponibles; cree primero un sitio";
"[DependencyManager] mkcert CA 安装成功" = "[DependencyManager] CA de mkcert instalada correctamente";
"无效的URL：%@" = "URL no válida: %@";
"无效的URL：%lld" = "URL no válido: %lld";
"所有主机" = "Todos los servidores";
"暂无错误日志" = "Aún no hay registros de errores";
"选择本地安装包" = "Seleccionar el paquete de instalación local";
"正在扫描配置文件..." = "Escaneando archivos de configuración...";
"未找到 Apache 配置文件" = "No se encontró el archivo de configuración de Apache";
"不切割日志文件" = "No dividir los archivos de registro";
"加密" = "Cifrado";
"没有运行的 Web 服务器，且默认目录不存在: %@" = "No hay ningún servidor web en ejecución y el directorio predeterminado no existe: %@";
"没有运行的 Web 服务器，且默认目录不存在: %lld" = "No hay ningún servidor web en ejecución y el directorio predeterminado no existe: %lld";
"PHP 扩展" = "Extensión de PHP";
"认证失败: %@" = "Error de autenticación: %@";
"认证失败: %lld" = "Error de autenticación: %lld";
"显示回收站" = "Mostrar la papelera de reciclaje";
"表缓存" = "Caché de tablas";
"修改默认页面功能正在开发中,敬请期待。\\n\\n此功能将允许您自定义 Nginx/Apache 的默认欢迎页面。" = "La función para modificar la página predeterminada está en desarrollo; ¡manténgase atento! \\n\\nEsta característica le permitirá personalizar la página de bienvenida predeterminada de Nginx/Apache.";
"端口号" = "Número de puerto";
"认证失败" = "Error de autenticación";
"本地 CA 已初始化" = "CA local inicializada";
"总请求数" = "Solicitudes totales";
"无法在根目录下创建目录，请使用用户目录" = "No se puede crear el directorio en el directorio raíz; utilice, por favor, el directorio de usuario";
"MacEnv 主题" = "Tema de MacEnv";
"无法读取 hosts 文件" = "No se puede leer el archivo hosts";
"监听中" = "Supervisión";
"修改 redis.conf 配置文件，将端口改为 6380" = "Modifique el archivo de configuración redis.conf y cambie el puerto a 6380";
"指定ip" = "Especificar dirección IP";
"无法添加 %@，请检查网络连接" = "No se puede agregar %@; compruebe, por favor, su conexión de red";
"无法添加 %lld，请检查网络连接" = "No se puede agregar %lld; compruebe, por favor, su conexión de red";
"缺少 Apache 后端配置" = "Falta la configuración del backend de Apache";
"复制错误页面目录失败: %@" = "Error al copiar el directorio de páginas de error: %@";
"复制错误页面目录失败: %lld" = "Error al copiar el directorio de páginas de error: %lld";
"解密" = "Descifrar";
"计算哈希值" = "Calcular el valor hash";
"站点状态" = "Estado del sitio";
"SSL 证书正常" = "Certificado SSL válido";
"已管理" = "Administrado";
"1. 确认域名已正确解析到服务器 IP" = "1. Confirme que el nombre de dominio se ha resuelto correctamente a la dirección IP del servidor";
"今天" = "hoy";
"内存" = "memoria";
"⚠️ 备份旧 Nginx 配置失败: %@" = "⚠️ Error al realizar una copia de seguridad de la configuración anterior de Nginx: %@";
"⚠️ 备份旧 Nginx 配置失败: %lld" = "⚠️ Error al hacer copia de seguridad de la antigua configuración de Nginx: %lld";
"重置路径" = "restablecer ruta";
"停止占用端口的服务，PHP %@ 将使用默认端口 %@" = "Detenga el servicio que está utilizando el puerto. PHP %@ utilizará el puerto predeterminado %@";
"停止占用端口的服务，PHP %lld 将使用默认端口 %lld" = "Detenga el servicio que está utilizando el puerto. PHP %lld utilizará el puerto predeterminado %lld";
"经典 Web 服务器" = "Servidor web clásico";
"提示：Ctrl+F 搜索关键字，Ctrl+S 保存，Ctrl+H 查找替换" = "Sugerencia: Pulse Ctrl+F para buscar palabras clave, Ctrl+S para guardar, Ctrl+H para buscar y reemplazar";
"导出数据库失败" = "Error al exportar la base de datos";
"无法打开目录：路径未知" = "No se puede abrir el directorio: ruta desconocida";
"正在安装 GoAccess..." = "Instalando GoAccess...";
"保持连接" = "mantener conectado";
"正在加载软件列表..." = "Cargando lista de software...";
"有 %@ 个端口未监听" = "Hay %@ puertos que no están escuchando";
"有 %lld 个端口未监听" = "Hay %lld puertos que no están escuchando";
"提示: 使用 MySQL 模式需要先启动 MySQL 服务" = "Sugerencia: Para usar el modo MySQL, primero debe iniciar el servicio MySQL";
"峰值访问量" = "visitas máximas";
"重要提示" = "Información importante";
"组织名称" = "Nombre de la organización";
"安装 Pure-FTPd 失败" = "Error al instalar Pure-FTPd";
"默认文档" = "documento predeterminado";
"搜索域名或IP" = "Buscar nombre de dominio o dirección IP";
"共 %@ 条  前往" = "Total de %@ elementos. Continuar";
"共 %lld 条  前往" = "Total de %lld elementos. Continuar";
"建议端口: %@" = "Puerto sugerido: %@";
"建议端口: %lld" = "Puerto sugerido: %lld";
"下载完成，准备安装..." = "Descarga completada. Listo para instalar...";
"无效的域名：%@" = "Nombre de dominio no válido: %@";
"无效的域名：%lld" = "Nombre de dominio no válido: %lld";
"修改成功" = "Modificación realizada correctamente";
"Redis 端口" = "Puerto de Redis";
"热门请求 (Top 10)" = "Solicitudes populares (10 principales)";
"系统默认" = "Valor predeterminado del sistema";
"仅使用Cookie" = "Utilizar únicamente cookies";
"请选择解决方案" = "Seleccione una solución";
"• 注意3：通过宝塔安装的数据库root默认不支持远程权限" = "• Nota 3: La base de datos raíz instalada mediante Pagoda no admite permisos remotos de forma predeterminada.";
"每行一个IP地址" = "Una dirección IP por línea";
"请先测试连接" = "Pruebe primero la conexión";
"开始使用" = "Comenzar";
"Sendmail路径" = "Ruta de Sendmail";
"创建时间" = "Hora de creación";
"GTID 模式" = "Modo GTID";
"查询失败: %@" = "Error en la consulta: %@";
"查询失败: %lld" = "Error en la consulta: %lld";
"多版本共存管理" = "Gestión de coexistencia de múltiples versiones";
"下载中" = "Descargando";
"请选择要分析的访问日志文件" = "Seleccione un archivo de registro de acceso para analizar";
"已从备份恢复数据" = "Datos restaurados desde la copia de seguridad";
"配置文件已保存" = "Archivo de configuración guardado";
"暂无备份记录" = "Aún no hay registros de copias de seguridad";
"加载数据库数据失败: %@" = "Error al cargar los datos de la base de datos: %@";
"加载数据库数据失败: %lld" = "Error al cargar los datos de la base de datos: %lld";
"未找到配置文件" = "No se encontró el archivo de configuración";
"添加第三方源 %@..." = "Agregar fuente de terceros %@...";
"添加第三方源 %lld..." = "Agregar fuente de terceros %lld...";
"正在验证 SSL 证书..." = "Verificando el certificado SSL...";
"最大浪费百分比" = "Porcentaje máximo de desperdicio";
"备份列表 - %@" = "Lista de copias de seguridad — %@";
"备份列表 - %lld" = "Lista de copias de seguridad — %lld";
"最大使用内存" = "Uso máximo de memoria";
"对象关系型数据库" = "Base de datos relacional orientada a objetos";
"单用户最大连接数" = "Número máximo de conexiones por usuario";
"• Redis扩展仅支持一个PHP版本安装使用，若在其它PHP版本已安装redis扩展，请勿再装" = "• La extensión Redis solo admite la instalación y el uso de una versión de PHP. Si la extensión redis ya está instalada en otras versiones de PHP, no la instale nuevamente.";
"批量重启完成" = "Reinicio por lotes completado";
"HTTP 端口:" = "Puerto HTTP:";
"重新选择" = "Volver a seleccionar";
"在 Finder 中打开" = "Abrir en Finder";
"攻击类型" = "Tipo de ataque";
"剩余天数: %@ 天" = "Días restantes: %@ días";
"剩余天数: %lld 天" = "Días restantes: %lld días";
"及和PHP环境" = "y entorno PHP";
"加载中" = "Cargando";
"性能配置" = "Configuración de rendimiento";
"该软件运行正常" = "El software funciona correctamente";
"证书申请失败：%@" = "Error al solicitar el certificado: %@";
"证书申请失败：%lld" = "Error al solicitar el certificado: %lld";
"配置中" = "Configurando";
"网站还没有访问记录，或日志文件不存在" = "Este sitio web no tiene registros de acceso o el archivo de registro no existe";
"JIT 编译模式" = "Modo de compilación JIT";
"保存时将条目纳入已管理区域。" = "Al guardar, la entrada se incluirá en el área gestionada.";
"JIT 缓冲区大小" = "Tamaño del búfer JIT";
"• 修改后需要重启 Nginx/Apache 才能生效" = "• Nginx/Apache debe reiniciarse tras realizar modificaciones para que surtan efecto";
"应用配置失败" = "Error al configurar la aplicación";
"高效文件传输" = "Transferencia eficiente de archivos";
"终端" = "terminal";
"端口占用与冲突管理" = "Gestión de ocupación y conflictos de puertos";
") 配置文件目录" = ") directorio del archivo de configuración";
"签发证书" = "Emitir certificado";
"选择目录" = "Seleccionar directorio";
"预估内存使用超过系统内存的 80%" = "El uso estimado de memoria supera el 80 % de la memoria del sistema";
"读取数据失败" = "Error al leer los datos";
"编辑站点" = "Editar sitio";
"压缩级别(-1到9)" = "Nivel de compresión (-1 a 9)";
"规则模板" = "Plantilla de regla";
"内存数据结构存储" = "Almacenamiento en estructura de datos en memoria";
"服务器标识" = "ID del servidor";
"创建配置文件" = "Crear archivo de configuración";
"安装列表" = "Lista de instalaciones";
"方法" = "método";
"确定要删除 %@ %@ 吗？此操作不可恢复。" = "¿Está seguro de que desea eliminar %@ %@? Esta operación no se puede deshacer.";
"确定要删除 %lld %lld 吗？此操作不可恢复。" = "¿Está seguro de que desea eliminar %lld %lld? Esta operación no se puede deshacer.";
"生成新配置前自动备份" = "Copia de seguridad automática antes de generar una nueva configuración";
"Zlib压缩支持" = "Compatibilidad con compresión Zlib";
"Web服务器" = "Servidor web";
"运行目录子路径:" = "Ejecutar subruta del directorio:";
"MySQL 系统数据库" = "Base de datos del sistema MySQL";
"最大执行时间" = "Tiempo máximo de ejecución";
"创建数据库失败，请检查 MySQL 连接" = "Error al crear la base de datos; compruebe la conexión con MySQL";
"新建备份" = "Crear una copia de seguridad nueva";
"MySQL 状态" = "Estado de MySQL";
"可用" = "Disponible";
"备份" = "copia de seguridad";
"\n            Listen 8080\n\n            # 加载模块\n            LoadModule mpm_prefork_module lib/httpd/modules/mod_mpm_prefork.so\n            LoadModule authn_core_module lib/httpd/modules/mod_authn_core.so\n            LoadModule authz_core_module lib/httpd/modules/mod_authz_core.so\n            LoadModule dir_module lib/httpd/modules/mod_dir.so\n            LoadModule mime_module lib/httpd/modules/mod_mime.so\n            LoadModule log_config_module lib/httpd/modules/mod_log_config.so\n            LoadModule unixd_module lib/httpd/modules/mod_unixd.so\n\n            <IfModule unixd_module>\n                User _www\n                Group _www\n            </IfModule>\n\n            ServerAdmin admin@localhost\n            ServerName localhost:8080\n\n            DocumentRoot " = "Escuchar en el puerto 8080\n\n            # Cargar módulo\n            LoadModule mpm_prefork_module lib/httpd/modules/mod_mpm_prefork.so\n            LoadModule authn_core_module lib/httpd/modules/mod_authn_core.so\n            LoadModule authz_core_module lib/httpd/modules/mod_authz_core.so\n            LoadModule dir_module lib/httpd/modules/mod_dir.so\n            LoadModule mime_module lib/httpd/modules/mod_mime.so\n            LoadModule log_config_module lib/httpd/modules/mod_log_config.so\n            LoadModule unixd_module lib/httpd/modules/mod_unixd.so\n\n            <IfModule unixd_module>\n                User_www\n                Group_www\n            </IfModule>\n\n            ServerAdmin admin@localhost\n            ServerName localhost:8080\n\n            DocumentRoot";
"Nginx 反向代理到 Apache 的 HTTPS 端口" = "Proxy inverso Nginx al puerto HTTPS de Apache";
"仅 1024+" = "Solo valores mayores o iguales a 1024";
"父目录不存在，将自动创建" = "El directorio padre no existe y se creará automáticamente.";
"[SiteManager] 恢复 hosts 条目失败: %@ - %@" = "[SiteManager] Error al restaurar la entrada en el archivo hosts: %@ - %@";
"[SiteManager] 恢复 hosts 条目失败: %lld - %lld" = "[SiteManager] Error al restaurar la entrada en el archivo hosts: %lld - %lld";
"停止 %@ 释放端口 %@" = "Detener %@ y liberar el puerto %@";
"停止 %lld 释放端口 %lld" = "Detener %lld y liberar el puerto %lld";
"✅ mkcert 生成证书成功: %@" = "✅ Certificado generado correctamente mediante mkcert: %@";
"✅ mkcert 生成证书成功: %lld" = "✅ Certificado generado correctamente mediante mkcert: %lld";
"按大小" = "por tamaño";
"暂无数据库" = "Aún no hay ninguna base de datos";
"检查 Homebrew..." = "Eche un vistazo a Homebrew...";
"PDO PostgreSQL驱动" = "Controlador PDO para PostgreSQL";
"添加用户" = "Agregar usuario";
"添加站点" = "Agregar sitio web";
"选择SQL文件" = "Seleccionar archivo SQL";
"有效期至" = "Válido hasta";
"每年切割一次" = "Cortar una vez al año";
"批量彻底删除" = "Eliminación por lotes completa";
"我们将帮助您完成初始设置" = "Le ayudaremos con la configuración inicial";
"操作失败: %@" = "La operación ha fallado: %@";
"操作失败: %lld" = "La operación ha fallado: %lld";
"%@ 重启失败,请检查日志" = "%@ No se ha podido reiniciar; compruebe el registro";
"%lld 重启失败,请检查日志" = "%lld No se ha podido reiniciar; compruebe el registro";
"• 支持多个域名，用空格分隔" = "• Admite varios nombres de dominio, separados por espacios";
"无法读取响应" = "No se puede leer la respuesta";
"按月" = "por mes";
"请先创建网站后再查看日志" = "Primero debe crear el sitio web y luego podrá ver los registros";
"端口号已复制到剪贴板" = "Número de puerto copiado al portapapeles";
"表名大小写敏感" = "Los nombres de las tablas distinguen mayúsculas y minúsculas";
"剩余63天" = "Quedan 63 días";
"连接超时" = "Tiempo de espera agotado para la conexión";
"未找到 %@ 的配置文件。\\n\\n可能的原因:\\n1. 服务未安装\\n2. 配置文件位置不在预期路径\\n\\n请确认服务已正确安装。" = "No se encontró el archivo de configuración para %@.\n\nPosibles causas:\n1. El servicio no está instalado.\n2. La ubicación del archivo de configuración no coincide con la ruta esperada.\n\nVerifique que el servicio se haya instalado correctamente.";
"未找到 %lld 的配置文件。\\n\\n可能的原因:\\n1. 服务未安装\\n2. 配置文件位置不在预期路径\\n\\n请确认服务已正确安装。" = "No se encontró el archivo de configuración para %lld.\n\nPosibles causas:\n1. El servicio no está instalado.\n2. La ubicación del archivo de configuración no coincide con la ruta esperada.\n\nVerifique que el servicio se haya instalado correctamente.";
"最大内存(0不限制)" = "Memoria máxima (0 = sin límite)";
"本地连接" = "Conexión de área local";
"待检测" = "Por probar";
"IO 最大容量" = "Capacidad máxima de E/S";
"配置冲突：同时存在完整的 Nginx 和 Apache 配置。请手动选择要使用的配置，并删除另一个。" = "Conflicto de configuración: existen tanto la configuración completa de Nginx como la de Apache. Seleccione manualmente la configuración que desea utilizar y elimine la otra.";
"检测" = "Detección";
"正在下载 GoAccess..." = "Descargando GoAccess...";
"default.conf listen 指令:" = "Directiva 'listen' en default.conf:";
"选择父目录并开始扫描" = "Seleccione el directorio padre e inicie el escaneo";
"跨站脚本攻击检测" = "Detección de ataques de secuencias de comandos entre sitios (XSS)";
"缓存时间 (Seconds)" = "Tiempo de caché (segundos)";
"复制默认首页失败: %@" = "Error al copiar la página de inicio predeterminada: %@";
"复制默认首页失败: %lld" = "Error al copiar la página de inicio predeterminada: %lld";
"创建数据库" = "Crear base de datos";
"关于 acme.sh：" = "Acerca de acme.sh:";
"MySQL 数据库" = "Base de datos MySQL";
"通用扩展" = "Extensión universal";
"即将打开 %@ 的配置文件" = "Se va a abrir el archivo de configuración de %@.";
"即将打开 %lld 的配置文件" = "Se va a abrir el archivo de configuración de %lld.";
"未检测到已安装的服务" = "No se ha detectado ningún servicio instalado";
"没有正在运行的Web服务" = "No hay ningún servicio web en ejecución";
"剩余天数" = "Días restantes";
"生成私钥失败" = "Error al generar la clave privada";
"# 请手动安装" = "# Instale manualmente";
"请先加密文本或输入要解密的密文" = "Primero cifre el texto o introduzca el texto cifrado que desea descifrar";
"线程池大小" = "Tamaño del grupo de hilos";
"系统和浏览器" = "Sistema y navegador";
"线程并发数" = "Número de hilos simultáneos";
"有効 %@ / %@" = "Válido %@ / %@";
"有効 %lld / %lld" = "Válido %lld / %lld";
"清除搜索" = "Borrar búsqueda";
"• 系统将自动创建数据库和用户，并授予相应权限" = "• El sistema creará automáticamente bases de datos y usuarios, y otorgará los permisos correspondientes";
"生成 CA 证书" = "Generar certificado CA";
"MPM 配置" = "Configuración de MPM";
"无法创建验证文件：%@" = "No se puede crear el archivo de verificación: %@";
"无法创建验证文件：%lld" = "No se pudo crear el archivo de verificación: %lld";
"📁 扫描到 %@ 个证书文件" = "📁 Se han escaneado %@ archivos de certificado";
"📁 扫描到 %lld 个证书文件" = "📁 Se han escaneado %lld archivos de certificado";
"复制完整连接信息到剪贴板，方便在 Navicat 中使用" = "Copiar toda la información de conexión al portapapeles para su uso fácil en Navicat";
"准备安装 Nginx..." = "Preparando la instalación de Nginx...";
"搜索日志..." = "Buscando registros...";
"繁" = "Tradicional";
"系统" = "sistema";
"• 申请前会自动检测域名是否可达" = "• Comprobará automáticamente si el nombre de dominio es accesible antes de aplicarlo";
"强烈建议迁移 %@ 配置文件" = "Se recomienda encarecidamente migrar el archivo de configuración de %@";
"强烈建议迁移 %lld 配置文件" = "Se recomienda encarecidamente migrar el archivo de configuración de %lld";
"最大工作进程" = "Número máximo de procesos de trabajo";
"记录所有HTTP请求（包括状态码、IP、请求路径等）" = "Registrar todas las solicitudes HTTP (incluyendo el código de estado, la dirección IP, la ruta de la solicitud, etc.)";
"使用其他端口" = "Usar otro puerto";
"攻击次数" = "Número de ataques";
"导入配置失败: %@" = "Error al importar la configuración: %@";
"导入配置失败: %lld" = "Error al importar la configuración: %lld";
"\n            # Apache 配置文件\n            # 由及和PHP环境自动生成\n\n            ServerRoot " = "#Archivo de configuración de Apache\n            # Generado automáticamente por el entorno PHP\n\n            ServerRoot";
"基于内存亦可持久化的Key-Value数据库" = "Base de datos persistente de pares clave-valor basada en memoria";
"作者" = "Autor";
"显示最近 %@ 条日志" = "Mostrar los registros más recientes de %@";
"显示最近 %lld 条日志" = "Mostrar los registros más recientes de %lld";
"便于备份" = "Fácil de hacer copias de seguridad";
"修复配置失败: %@" = "Error al reparar la configuración: %@";
"修复配置失败: %lld" = "Error al reparar la configuración: %lld";
"⚠️ 工作空间未初始化" = "⚠️ El área de trabajo no está inicializada";
"PDO MySQL驱动" = "Controlador PDO MySQL";
"连接名称不能为空" = "El nombre de la conexión no puede estar vacío";
"本地域名（如 localhost、.local）不支持 Let's Encrypt" = "Los nombres de dominio locales (como localhost o .local) no son compatibles con Let's Encrypt";
">\n    <title>恭喜，站点创建成功！</title>\n    <style>\n        .container {\n            width: 60%;\n            margin: 10% auto 0;\n            background-color: #f0f0f0;\n            padding: 2% 5%;\n            border-radius: 10px\n        }\n\n        ul {\n            padding-left: 20px;\n        }\n\n        ul li {\n            line-height: 2.3\n        }\n\n        a {\n            color: #20a53a\n        }\n    </style>\n</head>\n<body>\n    <div class=" = ">\n    <title>¡Felicitaciones! El sitio se ha creado correctamente.</title>\n    <style>\n        .container {\n            width: 60%;\n            margin: 10% auto 0;\n            background-color: #f0f0f0;\n            padding: 2% 5%;\n            border-radius: 10px\n        }\n\n        ul {\n            padding-left: 20px;\n        }\n\n        ul li {\n            line-height: 2.3\n        }\n\n        a {\n            color: #20a53a\n        }\n    </style>\n</head>\n<body>\n    <div class=";
"暂无备份" = "Aún no hay copias de seguridad";
"表定义缓存" = "Caché de definiciones de tablas";
"格式化" = "Formato";
"复制连接信息" = "Copiar la información de la conexión";
"✗ 配置文件不存在: %@" = "✗ El archivo de configuración no existe: %@";
"✗ 配置文件不存在: %lld" = "✗ El archivo de configuración no existe: %lld";
"连接缓冲区" = "búfer de conexión";
"读取目录失败 %@: %@" = "Error al leer el directorio %@: %@";
"读取目录失败 %lld: %lld" = "Error al leer el directorio %lld: %lld";
"操作系统和浏览器分布" = "Distribución del sistema operativo y del navegador";
"缺少 Nginx 反向代理配置" = "Falta la configuración del proxy inverso de Nginx";
"自动选择" = "selección automática";
"证书类型: 自签名证书" = "Tipo de certificado: certificado autofirmado";
"服务器签名" = "Firma del servidor";
"删除配置文件失败: %@, 错误: %@" = "No se pudo eliminar el archivo de configuración: %@, error: %@";
"删除配置文件失败: %lld, 错误: %lld" = "No se pudo eliminar el archivo de configuración: %lld, error: %lld";
"正在扫描日志..." = "Escaneando registros...";
"TCP 保活时间" = "Tiempo de mantenimiento de la conexión TCP";
"服务器字符集" = "Conjunto de caracteres del servidor";
"• 数据库名称和用户名只能包含字母、数字、下划线和连字符" = "• Los nombres de bases de datos y de usuarios solo pueden contener letras, números, guiones bajos y guiones";
"输入代码" = "Introduzca el código";
"快捷操作" = "Operación rápida";
"终止" = "finalizar";
"正在重启 %@..." = "Reiniciando %@...";
"正在重启 %lld..." = "Reiniciando %lld...";
"最少2位字符" = "Mínimo 2 caracteres";
"安装新版本" = "Instalar nueva versión";
"允许文件上传" = "Permitir carga de archivos";
"无法查看密码" = "No se puede ver la contraseña";
"JSON 解析失败: %@" = "Error al analizar el JSON: %@";
"JSON 解析失败: %lld" = "Error al analizar el JSON: %lld";
"输出缓冲" = "buffer de salida";
"永久" = "permanente";
"暂无日志" = "Aún no hay registros";
"模板" = "Plantilla";
"导出失败" = "Error al exportar";
"已部署" = "Implementado";
"证书文件" = "archivo de certificado";
"创建FTP账户: %@, 密码: %@" = "Crear cuenta FTP: %@, contraseña: %@";
"创建FTP账户: %lld, 密码: %lld" = "Crear cuenta FTP: %lld, contraseña: %lld";
"言語" = "voz";
"保存前自动备份" = "Copia de seguridad automática antes de guardar";
"连接超时，请检查网络连接和防火墙设置" = "Se agotó el tiempo de conexión; compruebe la conexión de red y la configuración del cortafuegos";
"下载地址" = "Dirección de descarga";
"\n                # HTTP 服务器块 - 重定向到 HTTPS\n                server {\n                    listen 80;\n                    server_name %@;\n\n                    # 强制跳转到 HTTPS\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # HTTPS 服务器块\n                server {\n                    listen 443 ssl;\n                    server_name %@;\n                    root %@;\n                    index index.php index.html index.htm;\n\n                    # SSL 证书配置\n                    ssl_certificate %@;\n                    ssl_certificate_key %@;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # 访问日志\n                    access_log %@;\n                    error_log %@;\n\n                    location / {\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\\\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:9000;\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # 禁止访问隐藏文件\n                    location ~ /\\\\. {\n                        deny all;\n                    }\n                }\n                " = "# Bloque del servidor HTTP: redirección a HTTPS\n                server {\n                    listen 80;\n                    server_name %@;\n\n                    # Redirección forzada a HTTPS\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # Bloque del servidor HTTPS\n                server {\n                    listen 443 ssl;\n                    server_name %@;\n                    root %@;\n                    index index.php index.html index.htm;\n\n                    # Configuración del certificado SSL\n                    ssl_certificate %@;\n                    ssl_certificate_key %@;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # Registro de accesos\n                    access_log %@;\n                    error_log %@;\n\n                    location/{\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:9000;\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # Deshabilitar el acceso a archivos ocultos\n                    location ~ /\\. {\n                        deny all;\n                    }\n                }";
"\n                # HTTP 服务器块 - 重定向到 HTTPS\n                server {\n                    listen 80;\n                    server_name %lld;\n\n                    # 强制跳转到 HTTPS\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # HTTPS 服务器块\n                server {\n                    listen 443 ssl;\n                    server_name %lld;\n                    root %lld;\n                    index index.php index.html index.htm;\n\n                    # SSL 证书配置\n                    ssl_certificate %lld;\n                    ssl_certificate_key %lld;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # 访问日志\n                    access_log %lld;\n                    error_log %lld;\n\n                    location / {\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\\\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:9000;\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # 禁止访问隐藏文件\n                    location ~ /\\\\. {\n                        deny all;\n                    }\n                }\n                " = "# Bloque del servidor HTTP: redirección a HTTPS\n                server {\n                    listen 80;\n                    server_name %lld;\n\n                    # Redirección forzada a HTTPS\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # Bloque del servidor HTTPS\n                server {\n                    listen 443 ssl;\n                    server_name %lld;\n                    root %lld;\n                    index index.php index.html index.htm;\n\n                    # Configuración del certificado SSL\n                    ssl_certificate %lld;\n                    ssl_certificate_key %lld;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # Registro de accesos\n                    access_log %lld;\n                    error_log %lld;\n\n                    location/{\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:9000;\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # Deshabilitar el acceso a archivos ocultos\n                    location ~ /\\. {\n                        deny all;\n                    }\n                }";
"重新加载失败" = "Error al recargar";
"请使用工具菜单中的证书管理功能申请证书" = "Utilice la función de administración de certificados del menú «Herramientas» para solicitar un certificado";
"%@ (已停止)" = "%@ (detenido)";
"%lld (已停止)" = "%lld (detenido)";
"时间" = "hora";
"已重启 %@ 个Web服务" = "Se han reiniciado los servicios web de %@.";
"已重启 %lld 个Web服务" = "Se han reiniciado %lld servicios web.";
"解码" = "Descodificar";
"未知主机，请检查服务器地址" = "Nombre de host desconocido; compruebe la dirección del servidor.";
"路径: %@" = "Ruta: %@";
"路径: %lld" = "Ruta: %lld";
"授权失败: \\(grantResult.error ?? " = "Error de autorización: \\(grantResult.error ??";
"进程控制扩展" = "extensión de control de procesos";
"开始分析" = "Iniciar análisis";
"目录没有写权限" = "El directorio no tiene permiso de escritura.";
"\n                # HTTP VirtualHost - 重定向到 HTTPS\n                # Apache 后端端口：%@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ?? " = "# VirtualHost HTTP: redirección a HTTPS\n                # Puerto de backend de Apache: %@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ??";
"\n                # HTTP VirtualHost - 重定向到 HTTPS\n                # Apache 后端端口：%lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ?? " = "# VirtualHost HTTP: redirección a HTTPS\n                # Puerto de backend de Apache: %lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ??";
"批量删除失败" = "Error al eliminar elementos en lote.";
"管理本地域名解析" = "Administrar la resolución de nombres de dominio locales.";
"忘记密码" = "¿Olvidó su contraseña?";
"服务器ID" = "ID del servidor";
"编码解码工具" = "Herramientas de codificación y decodificación";
"导入选中项 (%@)" = "Importar elementos seleccionados (%@)";
"导入选中项 (%lld)" = "Importar elementos seleccionados (%lld)";
"用于解密ionCube Encoder加密脚本" = "Script cifrado para descifrar ionCube Encoder";
"确认密码" = "Confirmar contraseña";
"隐藏密码" = "Ocultar contraseña";
"系统内存: \\(String(format: " = "Memoria del sistema: \\(String(format:";
"端口 %@ 已被占用，请选择其他端口" = "El puerto %@ ya está ocupado; elija otro puerto.";
"端口 %lld 已被占用，请选择其他端口" = "El puerto %lld ya está ocupado; elija otro puerto.";
"已导出 %@ 个站点配置到: %@" = "Se exportaron %@ configuraciones de sitios a: %@";
"已导出 %lld 个站点配置到: %lld" = "Se exportaron %lld configuraciones de sitios a: %lld";
"+%@ 个域名" = "+%@ nombres de dominio";
"+%lld 个域名" = "+%lld nombres de dominio";
"常规" = "normal";
"❌ 扫描证书目录失败: %@" = "❌ Error al escanear el directorio de certificados: %@";
"❌ 扫描证书目录失败: %lld" = "❌ Error al escanear el directorio de certificados: %lld";
"rewrite 规则已转换，请检查正则表达式语法" = "Regla de reescritura convertida; compruebe la sintaxis de expresiones regulares.";
"只能为 Apache 配置生成 Nginx 代理" = "El proxy de Nginx solo se puede generar para configuraciones de Apache.";
"成功删除 %@ 个配置文件，失败 %@ 个" = "Se eliminaron correctamente %@ archivos de configuración; errores: %@";
"成功删除 %lld 个配置文件，失败 %lld 个" = "Se eliminaron correctamente %lld archivos de configuración; errores: %lld";
"确定要删除数据库 %@ 吗？此操作不可恢复！" = "¿Está seguro de que desea eliminar la base de datos «%@»? Esta operación no se puede deshacer.";
"确定要删除数据库 %lld 吗？此操作不可恢复！" = "¿Está seguro de que desea eliminar la base de datos %lld? Esta operación no se puede deshacer.";
"编辑 Hosts 条目" = "Editar entrada de Hosts";
"正在检查域名解析..." = "Comprobando la resolución del nombre de dominio...";
"显示高级配置项" = "Mostrar elementos de configuración avanzada";
"性能优化" = "Optimización del rendimiento";
"运行中" = "En ejecución";
"创建" = "Crear";
"只能为 Nginx 代理配置生成 Apache 后端" = "La configuración de backend de Apache solo se puede generar para la configuración de proxy de Nginx";
"正在加载..." = "Cargando...";
"重新检查" = "Volver a comprobar";
"端口监听" = "Escucha en el puerto";
"未检测到" = "no detectado";
"未检测" = "No detectado";
"完成" = "Completado";
"删除配置失败: %@" = "Error al eliminar la configuración: %@";
"删除配置失败: %lld" = "Error al eliminar la configuración: %lld";
"状态" = "Estado";
"• 证书将自动写入站点配置并启用 HTTPS" = "• El certificado se escribirá automáticamente en la configuración del sitio y se habilitará HTTPS";
"申请成功" = "Aplicación correcta";
"读取 hosts 文件失败: %@" = "Error al leer el archivo hosts: %@";
"读取 hosts 文件失败: %lld" = "Error al leer el archivo hosts: %lld";
"修改配置文件，将端口改为 %@" = "Modifique el archivo de configuración y cambie el puerto a %@";
"修改配置文件，将端口改为 %lld" = "Modifique el archivo de configuración y cambie el puerto a %lld";
"文档根目录" = "raíz del documento";
"%@ 启动失败,请检查日志" = "%@ no se pudo iniciar; compruebe el registro";
"%lld 启动失败,请检查日志" = "%lld no se pudo iniciar; compruebe el registro";
"启用 OPcache" = "Habilitar OPcache";
"修改权限" = "Modificar permisos";
"请选择MySQL版本" = "Seleccione la versión de MySQL";
"验证成功" = "Verificación correcta";
"正在修改 %@ 端口..." = "Modificando el puerto de %@...";
"正在修改 %lld 端口..." = "Modificando el puerto %lld...";
"✅ 已强力修复站点配置: %@" = "✅ La configuración del sitio se ha reparado correctamente: %@";
"✅ 已强力修复站点配置: %lld" = "✅ La configuración del sitio se ha reparado correctamente: %lld";
"修改路径" = "Modificar ruta";
"• 修改 hosts 文件需要管理员权限" = "• Modificar el archivo hosts requiere privilegios de administrador";
"扫描" = "escanear";
"端口" = "puerto";
"检查 %@ 个站点" = "Comprobar sitios %@";
"检查 %lld 个站点" = "Comprobar %lld sitios";
"选择日志文件" = "Seleccionar archivo de registro";
"修改密码失败，请检查当前密码是否正确" = "No se pudo modificar la contraseña; compruebe si la contraseña actual es correcta";
"请输入当前密码" = "Introduzca la contraseña actual";
"未找到要编辑的 hosts 条目" = "No se encontró la entrada de hosts que se iba a editar";
"Let's Encrypt 证书已成功申请并配置。\\n\\n域名: %@\\n有效期: 90 天" = "Se ha solicitado y configurado correctamente el certificado de Let's Encrypt.\n\nNombre de dominio: %@\nPeriodo de validez: 90 días";
"Let's Encrypt 证书已成功申请并配置。\\n\\n域名: %lld\\n有效期: 90 天" = "Se ha solicitado y configurado correctamente el certificado de Let's Encrypt.\n\nNombre de dominio: %lld\nPeriodo de validez: 90 días";
"短标签" = "etiqueta corta";
"域名列表不能为空" = "La lista de nombres de dominio no puede estar vacía";
"15 分钟" = "15 minutos";
"========== 扫描结束 ==========\\n" = "========== Finaliza el análisis ==========\n";
"准备开始检查" = "Listo para comenzar la comprobación";
"安装独立的 %@ 实例，使用其他可用端口" = "Instalar una instancia independiente de %@, utilizando otros puertos disponibles";
"安装独立的 %lld 实例，使用其他可用端口" = "Instalar una instancia independiente de %lld, utilizando otros puertos disponibles";
"设置服务工作空间" = "Configurar un espacio de trabajo de servicio";
"OPcache 配置" = "Configuración de OPcache";
"支持的日志格式" = "Formatos de registro compatibles";
"未找到 mysqldump" = "No se encontró mysqldump";
"验证失败" = "Error de autenticación";
"Web 服务器" = "Servidor web";
"支持虚拟用户，无需创建系统账户" = "Admite usuarios virtuales sin necesidad de crear cuentas del sistema";
"有效请求" = "solicitud válida";
"验证文件内容不匹配" = "El contenido del archivo no coincide con la verificación";
"404错误页" = "Página de error 404";
"最小压缩长度" = "Longitud mínima de compresión";
"\n        # PureDB 文件认证\n        PureDB                      %@/pureftpd.pdb\n\n        " = "#Autenticación mediante archivo PureDB\n        PureDB %@/pureftpd.pdb";
"\n        # PureDB 文件认证\n        PureDB                      %lld/pureftpd.pdb\n\n        " = "#Autenticación mediante archivo PureDB\n        PureDB %lld/pureftpd.pdb";
"配置项" = "Elementos de configuración";
"点击刷新按钮加载日志" = "Haga clic en el botón de actualización para cargar el registro";
"服务名称" = "Nombre del servicio";
" combined\n                </VirtualHost>\n\n                # HTTPS VirtualHost\n                # Apache 后端端口：%@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ?? " = "combinado\n                </VirtualHost>\n\n                # VirtualHost HTTPS\n                # Puerto de backend de Apache: %@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ??";
" combined\n                </VirtualHost>\n\n                # HTTPS VirtualHost\n                # Apache 后端端口：%lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ?? " = "combinado\n                </VirtualHost>\n\n                # VirtualHost HTTPS\n                # Puerto de backend de Apache: %lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ??";
"路径缓存TTL" = "TTL de la caché de rutas";
"批量删除" = "Eliminación por lotes";
"保存日志配置失败: %@" = "No se pudo guardar la configuración del registro: %@";
"保存日志配置失败: %lld" = "No se pudo guardar la configuración del registro: %lld";
"确定要删除站点 %@ 吗？站点将被移到回收站，可以恢复。" = "¿Está seguro de que desea eliminar el sitio %@? El sitio se moverá a la Papelera de reciclaje y podrá restaurarse.";
"确定要删除站点 %lld 吗？站点将被移到回收站，可以恢复。" = "¿Está seguro de que desea eliminar el sitio %lld? El sitio se moverá a la Papelera de reciclaje y podrá restaurarse.";
"⚠ %@ 已禁用" = "⚠ %@ desactivado";
"⚠ %lld 已禁用" = "⚠ %lld desactivado";
"请选择SQL文件" = "Seleccione un archivo SQL";
"来源 (Origins)" = "Orígenes";
"✅ 已为 Apache 配置生成 Nginx 代理: %@" = "✅ Se ha generado un proxy de Nginx para la configuración de Apache: %@";
"✅ 已为 Apache 配置生成 Nginx 代理: %lld" = "✅ Se ha generado el proxy de Nginx para la configuración de Apache: %lld";
"正在准备安装 %@..." = "Preparando la instalación de %@...";
"正在准备安装 %lld..." = "Preparando la instalación de %lld...";
"交互超时" = "Tiempo de espera agotado para la interacción";
"恢复建议值" = "Restaurar valores recomendados";
"历史记录" = "Historial";
"确定要彻底删除选中的 %@ 个站点吗？此操作不可恢复。" = "¿Está seguro de que desea eliminar por completo los sitios seleccionados %@? Esta operación no se puede deshacer.";
"确定要彻底删除选中的 %lld 个站点吗？此操作不可恢复。" = "¿Está seguro de que desea eliminar por completo los %lld sitios seleccionados? Esta operación no se puede deshacer.";
"持久化配置" = "Configuración de persistencia";
"浮点数精度" = "Precisión de punto flotante";
"添加mysql数据库" = "Agregar base de datos MySQL";
"重新验证频率" = "Frecuencia de revalidación";
"配置已保存，但重载 Nginx 失败" = "La configuración se ha guardado, pero se produjo un error al recargar Nginx";
"按天" = "por día";
"上一步" = "Paso anterior";
"邮件配置" = "Configuración de correo electrónico";
"允许跨域请求的域名，例如: http://localhost:8080，允许所有请填 *" = "Nombres de dominio que permiten solicitudes entre dominios, por ejemplo: http://localhost:8080; introduzca * si se permiten todos";
"序列化精度" = "Precisión de serialización";
"日志文件不存在" = "El archivo de registro no existe";
"自动" = "automático";
"PHP-FPM 端口:" = "Puerto de PHP-FPM:";
"最新版" = "Versión más reciente";
"配置文件已备份" = "El archivo de configuración se ha respaldado";
"网站日志 - %@" = "Registro del sitio web: %@";
"网站日志 - %lld" = "Registro del sitio web: %lld";
"以下服务当前未运行：\\n\\n%@\\n\\n使用已停止的服务可能导致网站无法正常访问。\\n\\n建议先启动这些服务，或选择其他运行中的服务。" = "Los siguientes servicios no están ejecutándose actualmente:\n\n%@.\n\nUtilizar servicios detenidos podría provocar que el sitio web no sea accesible.\n\nSe recomienda iniciar primero estos servicios o seleccionar otros servicios que sí estén en ejecución.";
"以下服务当前未运行：\\n\\n%lld\\n\\n使用已停止的服务可能导致网站无法正常访问。\\n\\n建议先启动这些服务，或选择其他运行中的服务。" = "Los siguientes servicios no están ejecutándose actualmente:\n\n%lld.\n\nUtilizar servicios detenidos podría provocar que el sitio web no sea accesible.\n\nSe recomienda iniciar primero estos servicios o seleccionar otros servicios que sí estén en ejecución.";
"基于 GoAccess 生成" = "Generado con GoAccess";
"安装 %@ 失败" = "La instalación de %@ ha fallado";
"安装 %lld 失败" = "La instalación de %lld ha fallado";
"高性能异步网络通信引擎" = "Motor de comunicación en red asíncrono de alto rendimiento";
"繁體中文" = "Chino tradicional";
"下次重启服务后生效" = "Entrará en vigor tras el siguiente reinicio del servicio.";
"未找到 MySQL 配置文件" = "No se encontró el archivo de configuración de MySQL";
"开机自启动" = "Iniciar automáticamente al arrancar";
"✓ PHP 运行正常" = "✓ PHP funciona correctamente";
"root密码" = "Contraseña de root";
"PHP攻击" = "Ataque a PHP";
"复制配置" = "Copiar configuración";
"访问日志" = "registro de accesos";
"MySQL默认主机" = "Host predeterminado de MySQL";
"连接成功" = "Conexión establecida correctamente";
"正在生成分析报告..." = "Generando informe de análisis...";
"安装 acme.sh 失败：%@" = "La instalación de acme.sh ha fallado: %@";
"安装 acme.sh 失败：%lld" = "La instalación de acme.sh ha fallado: %lld";
"未部署" = "No implementado";
"显示IP归属地信息" = "Mostrar información de la dirección IP";
"(端口 %@)" = "(puerto %@)";
"(端口 %lld)" = "(puerto %lld)";
"编译中" = "Compilando";
"当日志文件达到指定大小时自动切割" = "Cortar automáticamente el archivo de registro cuando alcance el tamaño especificado";
"通用名称 (CN)" = "Nombre común (CN)";
"GNU多精度算术库" = "Biblioteca de aritmética de precisión múltiple de GNU";
"安装失败: %@" = "La instalación ha fallado: %@";
"安装失败: %lld" = "La instalación ha fallado: %lld";
"终端为系统应用，点击下方按钮将在外部打开。" = "El Terminal es una aplicación del sistema. Al hacer clic en el botón inferior, se abrirá externamente.";
"规则内容" = "Contenido de la regla";
"正在检测 MySQL 状态..." = "Comprobando el estado de MySQL...";
"父目录:" = "Directorio principal:";
"• 证书有效期 90 天" = "• El certificado es válido durante 90 días";
"服务工作空间" = "Área de trabajo del servicio";
"正在停止 %@..." = "Deteniendo %@...";
"正在停止 %lld..." = "Deteniendo %lld...";
"确认彻底删除" = "Confirmar eliminación completa";
"证书路径" = "Ruta del certificado";
"批量修复完成" = "Reparación por lotes completada";
"文件不存在：%@" = "El archivo no existe: %@";
"文件不存在：%lld" = "El archivo no existe: %lld";
"服务器名称" = "Nombre del servidor";
"本地域名不支持 Let's Encrypt：%@" = "Let's Encrypt no es compatible con nombres de dominio locales: %@";
"本地域名不支持 Let's Encrypt：%lld" = "Let's Encrypt no es compatible con nombres de dominio locales: %lld";
"2. 确认防火墙已开放 80 和 443 端口" = "2. Confirme que el firewall tiene los puertos 80 y 443 abiertos";
"安装" = "Instalación";
"正在获取数据库列表..." = "Obteniendo la lista de bases de datos...";
"⚠️ 备份旧配置失败: %@" = "⚠️ Error al hacer una copia de seguridad de la configuración anterior: %@";
"⚠️ 备份旧配置失败: %lld" = "⚠️ Error al hacer una copia de seguridad de la configuración anterior: %lld";
"default.conf: %@ (普通文件)" = "default.conf: %@ (archivo normal)";
"default.conf: %lld (普通文件)" = "default.conf: %lld (archivo normal)";
"操作失败" = "La operación ha fallado";
"请输入管理员密码" = "Introduzca la contraseña de administrador";
"已安装并信任" = "Instalado y de confianza";
"已连通远程数据库，但本机客户端不支持该认证插件。您仍然可以保存此连接配置。" = "Se ha establecido la conexión con la base de datos remota, pero el cliente local no admite el complemento de autenticación. Aun así, puede guardar esta configuración de conexión.";
"LRU采样数" = "Número de muestras LRU";
"端口配置" = "Configuración del puerto";
"未知的 caching_sha2_password 状态: %@" = "Estado desconocido de caching_sha2_password: %@";
"未知的 caching_sha2_password 状态: %lld" = "Estado desconocido de caching_sha2_password: %lld";
"读入缓冲区大小" = "Tamaño del búfer de lectura";
"%@ 天" = "%@ días";
"%lld 天" = "%lld días";
"关联数据库" = "base de datos relacional";
"默认: %@" = "Valor predeterminado: %@";
"默认: %lld" = "Valor predeterminado: %lld";
"========== 开始扫描配置文件 ==========" = "========== Iniciando el análisis de los archivos de configuración ==========";
"读缓冲区" = "búfer de lectura";
"（无密码）" = "(sin contraseña)";
"伪静态规则转换" = "Conversión de reglas pseudoestáticas";
"/（根目录）" = "/(directorio raíz)";
"刷新" = "Actualizar";
"日志分析" = "Análisis de registros";
"仅当前用户" = "Solo el usuario actual";
"⚠️ 证书目录不存在" = "⚠️ El directorio de certificados no existe";
"服务未运行" = "El servicio no está en ejecución";
"配置 PHP-FPM 端口..." = "Configurar el puerto de PHP-FPM...";
"知道了" = "Entendido";
"❌ 解析失败: %@" = "❌ Error al analizar: %@";
"❌ 解析失败: %lld" = "❌ Error al analizar: %lld";
"提示信息" = "Mensaje de aviso";
"自定义目录" = "Directorio personalizado";
"内存表最大大小" = "Tamaño máximo de la tabla en memoria";
"请求方法 (Methods)" = "Métodos de solicitud (métodos)";
"二进制日志格式" = "Formato del registro binario";
"会话配置" = "Configuración de sesión";
"GoAccess 执行失败" = "Error al ejecutar GoAccess";
"执行失败" = "Error al ejecutar";
"GC触发概率" = "Probabilidad de activación de la recolección de basura (GC)";
"保存成功" = "Guardado correctamente";
"错误处理" = "Control de errores";
"数据库名称已存在" = "Ya existe una base de datos con ese nombre";
"PHP运行" = "Ejecución de PHP";
"未找到 Homebrew" = "No se encontró Homebrew";
"批量启用" = "Habilitar por lotes";
"状态码" = "código de estado";
"加载错误日志失败: %@" = "Error al cargar el registro de errores: %@";
"加载错误日志失败: %lld" = "Error al cargar el registro de errores: %lld";
"每月切割一次" = "Cortar una vez al mes";
"未找到可编辑的未管理条目，请改用“转为已管理后编辑”" = "No se encontraron entradas no administradas editables; en su lugar, utilice «Convertir a administrada y luego editar»";
"网站名" = "nombre del sitio web";
"生成成功" = "Generado correctamente";
"MySQL 性能优化" = "Optimización del rendimiento de MySQL";
"编辑连接信息" = "Editar información de conexión";
"客户端超时(0不超时)" = "Tiempo de espera del cliente (0 significa sin tiempo de espera)";
"✗ 端口 %@ 未监听" = "✗ El puerto %@ no está escuchando";
"✗ 端口 %lld 未监听" = "✗ El puerto %lld no está escuchando";
"SQL文件" = "Archivo SQL";
"打开工作目录" = "Abrir el directorio de trabajo";
"导出完成（部分失败）" = "Exportación completada (con errores parciales)";
"申请证书" = "Solicitar un certificado";
"本地服务器" = "servidor local";
"查看更多历史记录" = "Ver más historial";
"最大连接数" = "Número máximo de conexiones";
"保存配置时会自动创建备份" = "Se crea automáticamente una copia de seguridad al guardar la configuración";
"✅ 成功更新 %@ 个配置文件:" = "✅ Se han actualizado correctamente %@ archivos de configuración:";
"✅ 成功更新 %lld 个配置文件:" = "✅ Se han actualizado correctamente %lld archivos de configuración:";
"证书即将过期，请及时续期" = "El certificado está a punto de expirar; renuévelo a tiempo";
"✅ 扫描到证书: %@" = "✅ Escaneo al certificado: %@";
"✅ 扫描到证书: %lld" = "✅ Escaneo al certificado: %lld";
"代码格式化" = "Formato de código";
"请选择批量操作" = "Seleccione una operación por lotes";
"MySQL 未安装" = "MySQL no está instalado";
"Root密码" = "Contraseña de root";
"加载站点数据失败，且无法恢复备份" = "No se pudieron cargar los datos del sitio y no es posible restaurar la copia de seguridad";
"服务管理" = "Administración de servicios";
"%@个" = "%@";
"%lld个" = "%lld";
"无法执行配置检测: %@" = "No se pudo realizar la detección de la configuración: %@";
"无法执行配置检测: %lld" = "No se pudo realizar la detección de la configuración: %lld";
"cURL配置" = "Configuración de cURL";
"创建目录失败: %@" = "No se pudo crear el directorio: %@";
"创建目录失败: %lld" = "No se pudo crear el directorio: %lld";
"隐藏系统数据库" = "Ocultar base de datos del sistema";
"安装 CA 证书失败" = "La instalación del certificado de CA ha fallado";
"操作" = "Operación";
"📊 扫描完成，共找到 %@ 个配置" = "📊 Escaneo completado; se encontraron un total de %@ configuraciones";
"📊 扫描完成，共找到 %lld 个配置" = "📊 Escaneo completado; se encontraron un total de %lld configuraciones";
"保存服务缓存失败: %@" = "No se pudo guardar la caché del servicio: %@";
"保存服务缓存失败: %lld" = "No se pudo guardar la caché del servicio: %lld";
"日志级别" = "Nivel de registro";
"最大保持请求数" = "Número máximo de solicitudes en espera";
"退出时保留服务" = "Mantener el servicio al salir";
"大小" = "tamaño";
"正在修改 Nginx 端口..." = "Modificando el puerto de Nginx...";
"分析失败" = "El análisis ha fallado";
"无备份" = "Sin copia de seguridad";
"共 %@ 条" = "Total: %@ elementos";
"共 %lld 条" = "Total: %lld elementos";
"访问密码" = "contraseña de acceso";
"安装成功！" = "¡Instalación correcta!";
"本地 CA 证书" = "Certificado de CA local";
"请输入加密/解密密码" = "Introduzca la contraseña de cifrado/descifrado";
"建议值" = "Valor recomendado";
"设置路径" = "Establecer ruta";
"使用 PureDB 文件存储用户信息，简单易用，适合小型站点" = "Utilice archivos PureDB para almacenar la información de los usuarios: sencillos y fáciles de usar, adecuados para sitios pequeños";
"总请求" = "solicitudes totales";
"刷新数据库列表" = "Actualizar lista de bases de datos";
"PHP代码注入检测" = "Detección de inyección de código PHP";
"版本数据来源于 Homebrew" = "Los datos de versión provienen de Homebrew";
"密码长度至少为8位" = "La contraseña debe tener al menos 8 caracteres";
"本地" = "local";
"黑名单模式" = "modo de lista negra";
"Nginx 反向代理到 Apache 的 HTTP 端口" = "Proxy inverso de Nginx al puerto HTTP de Apache";
"• 建议在导入前备份现有数据" = "• Se recomienda realizar una copia de seguridad de los datos existentes antes de importarlos";
"保持连接超时" = "Tiempo de espera para la conexión persistente";
"未找到 MySQL 客户端" = "No se encontró el cliente de MySQL";
"认证策略" = "Política de autenticación";
"开始扫描" = "Iniciar análisis";
"• 本地域名（如 localhost、.local、.test）无法使用" = "• No se pueden utilizar nombres de dominio locales (por ejemplo, localhost, .local, .test)";
"暂不迁移" = "Aún no se está migrando";
"网络连接中断" = "Conexión de red interrumpida";
"nginx -t 输出:\\n%@" = "Salida de nginx -t:\n%@";
"nginx -t 输出:\\n%lld" = "Salida de nginx -t:\n%lld";
"搜索数据库..." = "Buscar en la base de datos...";
"\n        # Pure-FTPd MySQL 配置\n        # 请根据实际情况修改以下配置\n\n        MYSQLServer     localhost\n        MYSQLPort       3306\n        MYSQLSocket     /tmp/mysql.sock\n        MYSQLUser       pureftpd\n        MYSQLPassword   your_password_here\n        MYSQLDatabase   pureftpd\n        MYSQLCrypt      md5\n\n        # SQL 查询语句\n        MYSQLGetPW      SELECT Password FROM users WHERE User=" = "# Configuración de MySQL para Pure-FTPd\n        # Modifique la siguiente configuración según su entorno real\n\n        MYSQLServer localhost\n        MYSQLPort 3306\n        MYSQLSocket /tmp/mysql.sock\n        MYSQLUser pureftpd\n        MYSQLPassword su_contraseña_aquí\n        MYSQLDatabase pureftpd\n        MYSQLCrypt md5\n\n        # Sentencia SQL de consulta\n        MYSQLGetPW SELECT Password FROM users WHERE User=";
"占用" = "ocupar";
"HTML格式错误" = "Error de formato HTML";
"%@ 停止失败,请检查日志" = "%@ No se pudo detener. Compruebe el registro.";
"%lld 停止失败,请检查日志" = "%lld No se pudo detener. Compruebe el registro.";
"• 修改密码后，使用该用户的应用需要更新配置" = "• Tras cambiar la contraseña, las aplicaciones que utilicen este usuario deberán actualizar sus configuraciones.";
"网站高级设置 - %@" = "Configuración avanzada del sitio web — %@";
"网站高级设置 - %lld" = "Configuración avanzada del sitio web — %lld";
"正在配置 Apache..." = "Configurando Apache...";
"启用 AOF 持久化" = "Habilitar la persistencia AOF";
"配置文件迁移" = "Migración del archivo de configuración";
"正在检测域名是否可以访问到当前站点" = "Comprobando si el nombre de dominio puede acceder al sitio actual";
"📁 扫描目录: %@" = "📁 Escaneando directorio: %@";
"📁 扫描目录: %lld" = "📁 Escaneando directorio: %lld";
"无法自动修复：未在工作空间中找到与域名 \\(config.parsed.domains.joined(separator: " = "No se puede corregir automáticamente: no se encontró el separador en el espacio de trabajo con el nombre de dominio \\(config.parsed.domains.joined((separator:";
"删除用户" = "Eliminar usuario";
"请输入服务器地址" = "Introduzca la dirección del servidor";
"未找到证书配置" = "No se encontró la configuración del certificado";
">\n                    Options Indexes FollowSymLinks\n                    AllowOverride All\n                    Require all granted\n                </Directory>\n\n                # PHP-FPM 配置\n                <FilesMatch \\\\.php$>\n                    SetHandler " = ">\n                    Options Indexes FollowSymLinks\n                    AllowOverride All\n                    Require all granted\n                </Directory>\n\n                # Configuración de PHP-FPM\n                <FilesMatch \\.php$>\n                    SetHandler";
"正在验证服务状态..." = "Verificando el estado del servicio...";
"工作空间未初始化" = "El área de trabajo no está inicializada";
"点击下方按钮开始验证域名可达性" = "Haga clic en el botón de abajo para comenzar a verificar la accesibilidad del dominio";
"• CA 证书将用于签发所有站点 SSL 证书" = "• El certificado de autoridad de certificación (CA) se utilizará para emitir todos los certificados SSL del sitio";
"最大输入变量数" = "Número máximo de variables de entrada";
"\n            # Redis 配置\n            port 6379\n            bind 127.0.0.1\n            protected-mode yes\n            maxmemory 0\n            " = "# Configuración de Redis\n            puerto 6379\n            enlazar 127.0.0.1\n            modo protegido sí\n            memoria máxima 0";
"配置解析错误: %@" = "Error al analizar la configuración: %@";
"配置解析错误: %lld" = "Error al analizar la configuración: %lld";
"正在修改 Apache 端口..." = "Modificando el puerto de Apache...";
"选择目录路径" = "Seleccione la ruta del directorio";
"    # 原始条件: %@\\n" = "# Condición original: %@\\n";
"    # 原始条件: %lld\\n" = "# Condición original: %lld\\n";
"大写输出" = "Salida en mayúsculas";
"刷新 IP 列表" = "Actualizar lista de direcciones IP";
"系统工具" = "Herramientas del sistema";
"立即迁移" = "Migrar ahora";
"生成中..." = "Generando...";
"批量导入: %@" = "Importación por lotes: %@";
"批量导入: %lld" = "Importación por lotes: %lld";
"用于加速PHP脚本" = "Se utiliza para acelerar los scripts de PHP";
"• 支持自动申请和续期证书" = "• Admite la aplicación y renovación automáticas de certificados";
"仍然继续" = "seguirá ejecutándose";
"# 内置扩展，请在 php.ini 中禁用" = "# Extensión integrada; desactívela en el archivo php.ini";
"生成随机密码" = "Generar contraseña aleatoria";
"未检测到相关软件" = "No se ha detectado ningún software relacionado";
"关闭" = "Cerrar";
"重新生成功能开发中" = "La función de regeneración está en desarrollo";
"配置 %@" = "Configuración %@";
"配置 %lld" = "Configuración %lld";
"休眠超时" = "Tiempo de espera de inactividad";
"软件: %@" = "Software: %@";
"软件: %lld" = "Software: %lld";
"仅CLI模式可用" = "Solo disponible en modo CLI";
"✅ 已删除配置文件: %@" = "✅ Archivo de configuración eliminado: %@";
"✅ 已删除配置文件: %lld" = "✅ Archivo de configuración eliminado: %lld";
"清空" = "Borrar";
"开始验证" = "Iniciar verificación";
"设置PHP命令行版本" = "Establecer la versión de PHP para línea de comandos";
"错误日志 (最近10行):\\n%@" = "Registro de errores (últimas 10 líneas):\n%@";
"错误日志 (最近10行):\\n%lld" = "Registro de errores (últimas 10 líneas):\n%lld";
"添加连接" = "Agregar conexión";
"打开站点" = "abrir sitio";
"外部编辑器" = "editor externo";
"建议使用 8000-8999 范围内的端口" = "Se recomienda utilizar puertos en el rango 8000-8999";
"扫描网站日志中的安全威胁" = "Escanear los registros del sitio web en busca de amenazas de seguridad";
"CLI启用OPcache" = "CLI para habilitar OPcache";
"\n            # 禁止访问隐藏文件\n            location ~ /\\\\. {\n                deny all;\n            }\n        }\n        " = "# Deshabilitar el acceso a archivos ocultos\n            location ~ /\\\\. {\n                deny all;\n            }\n        }";
"正在配置..." = "Configurando...";
"深色" = "Oscuro";
"支持分析任意 Apache/Nginx 访问日志" = "Admite el análisis de cualquier registro de accesos de Apache o Nginx";
")/\n                </VirtualHost>\n\n                # HTTPS VirtualHost\n                # Apache 后端端口：%@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ?? " = ")/\n                </VirtualHost>\n\n                # VirtualHost HTTPS\n                # Puerto de backend de Apache: %@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ??";
")/\n                </VirtualHost>\n\n                # HTTPS VirtualHost\n                # Apache 后端端口：%lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ?? " = ")/\n                </VirtualHost>\n\n                # VirtualHost HTTPS\n                # Puerto de backend de Apache: %lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ??";
"MySQL 端口" = "Puerto de MySQL";
"端口已更新，但部分配置同步失败" = "El puerto se ha actualizado, pero se produjo un error al sincronizar parte de la configuración";
"%@ (本机)" = "%@ (nativo)";
"%lld (本机)" = "%lld (nativo)";
"导入前将删除数据库中的所有现有数据" = "Todos los datos existentes en la base de datos se eliminarán antes de importar";
"SSL 证书正常（剩余 %@ 天）" = "Certificado SSL válido (%@ días restantes)";
"SSL 证书正常（剩余 %lld 天）" = "Certificado SSL válido (%lld días restantes)";
"安装 Apache 失败" = "Error al instalar Apache";
"成功导入 %@ 个站点，失败 %@ 个" = "Se han importado correctamente %@ sitios; fallo en %@";
"成功导入 %lld 个站点，失败 %lld 个" = "Se han importado correctamente %lld sitios; fallo en %lld";
"等待转换" = "Esperando la conversión";
"常用配置" = "Configuración común";
"热门请求" = "Solicitudes populares";
"反选" = "Contravotación";
"• 修改远程数据库密码需要提供正确的当前密码" = "• Para cambiar la contraseña de la base de datos remota, debe proporcionar la contraseña actual correcta";
"适用于 %@ 内存的服务器" = "Servidor para %@ de memoria";
"适用于 %lld 内存的服务器" = "Servidor para %lld de memoria";
"免费的 SSL/TLS 证书，适用于生产环境" = "Certificado SSL/TLS gratuito para entornos de producción";
"数据库数量" = "Número de bases de datos";
"批量恢复" = "Recuperación por lotes";
"正在安装 Apache..." = "Instalando Apache...";
"%@ 错误日志" = "Registro de errores de %@";
"%lld 错误日志" = "Registro de errores de %lld";
"全选" = "Seleccionar todo";
"扫描目录" = "Examinar directorio";
"邮箱地址" = "Dirección de correo electrónico";
"POST 最大大小" = "Tamaño máximo de las solicitudes POST";
"正在加载错误日志..." = "Cargando el registro de errores...";
"例如 /admin/，根目录请填 /" = "Por ejemplo, /admin/. Rellene / para el directorio raíz.";
"页面浏览量" = "vistas de página";
"带宽" = "Ancho de banda";
"SSL 证书" = "Certificado SSL";
"\n            # Apache 后端端口：%@\n            <VirtualHost *:%@>\n                ServerName \\(domains.first ?? " = "# Puerto de backend de Apache: %@\n            <VirtualHost *:%@>\n                ServerName \\(domains.first ??";
"\n            # Apache 后端端口：%lld\n            <VirtualHost *:%lld>\n                ServerName \\(domains.first ?? " = "# Puerto de backend de Apache: %lld\n            <VirtualHost *:%lld>\n                ServerName \\(domains.first ??";
"将以下设置应用到选中的 %@ 个站点" = "Aplicar la siguiente configuración a los %@ sitios seleccionados";
"将以下设置应用到选中的 %lld 个站点" = "Aplicar la siguiente configuración a los %lld sitios seleccionados";
"下一步" = "Siguiente paso";
"[BatchImport] 扫描目录失败: %@" = "[Importación masiva] Error al escanear el directorio: %@";
"[BatchImport] 扫描目录失败: %lld" = "[Importación masiva] Error al escanear el directorio: %lld";
"通用查询日志" = "Registro de consultas general";
"暂无端口信息" = "Aún no hay información sobre el puerto";
"語言" = "Idioma";
"运行目录:" = "Directorio de ejecución:";
"容量" = "Capacidad";
"进程 PID" = "PID del proceso";
"无法读取文件" = "No se puede leer el archivo";
"颁发机构: JihePHPEnv Local CA" = "Autoridad emisora: CA local de JihePHPEnv";
"配置已恢复" = "Configuración restaurada";
"暂无网站" = "Aún no hay sitio web";
"当前密码" = "contraseña actual";
"✗ 复制错误页面目录失败: %@" = "✗ Error al copiar el directorio de páginas de error: %@";
"✗ 复制错误页面目录失败: %lld" = "✗ Error al copiar el directorio de páginas de error: %lld";
"复制失败" = "Error al copiar";
"高精度数学运算" = "Operaciones matemáticas de alta precisión";
"Web 服务器:" = "Servidor web:";
"ImageMagick高性能图形库" = "Biblioteca gráfica de alto rendimiento ImageMagick";
"全部" = "Todos";
"编码解码" = "Codificación y decodificación";
"MySQL 未运行" = "MySQL no está en ejecución";
"CA 证书已成功生成并安装到 Keychain。" = "El certificado de autoridad (CA) se ha generado e instalado correctamente en la cadena de claves.";
"剩余%@天" = "Quedan%@ días";
"剩余%lld天" = "Quedan %lld días";
"最近30天" = "Últimos 30 días";
"根目录" = "directorio raíz";
"正在加载 PHP %@ 扩展列表..." = "Cargando lista de extensiones PHP %@...";
"正在加载 PHP %lld 扩展列表..." = "Cargando lista de extensiones PHP %lld...";
"迁移到工作空间" = "Migrar al espacio de trabajo";
"\n        # 默认站点配置\n        # 由 JihePHPEnv 自动生成\n\n        server {\n            listen       %@;\n            server_name  localhost 127.0.0.1;\n\n            root   %@/var/www;\n            index  index.php index.html index.htm;\n\n            location / {\n                try_files $uri $uri/ /index.php?$query_string;\n            }\n\n            location ~ \\\\.php$ {\n                fastcgi_pass   127.0.0.1:9000;\n                fastcgi_index  index.php;\n                fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;\n                include        fastcgi_params;\n            }\n\n            location /phpmyadmin {\n                alias %@/share/phpmyadmin;\n                index jihe_signon.php;\n\n                location ~ \\\\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_param SCRIPT_FILENAME $request_filename;\n                }\n            }\n\n            location ~ /\\\\. {\n                deny all;\n            }\n        }\n        " = "#Configuración predeterminada del sitio\n        # Generado automáticamente por JihePHPEnv\n\n        server {\n            listen %@;\n            server_name localhost 127.0.0.1;\n\n            root %@/var/www;\n            index index.php index.html index.htm;\n\n            location/{\n                try_files $uri $uri/ /index.php?$query_string;\n            }\n\n            location ~ \\.php$ {\n                fastcgi_pass 127.0.0.1:9000;\n                fastcgi_index index.php;\n                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                include fastcgi_params;\n            }\n\n            location /phpmyadmin {\n                alias %@/share/phpmyadmin;\n                index jihe_signon.php;\n\n                location ~ \\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_param SCRIPT_FILENAME $request_filename;\n                }\n            }\n\n            location ~ /\\. {\n                deny all;\n            }\n        }";
"\n        # 默认站点配置\n        # 由 JihePHPEnv 自动生成\n\n        server {\n            listen       %lld;\n            server_name  localhost 127.0.0.1;\n\n            root   %lld/var/www;\n            index  index.php index.html index.htm;\n\n            location / {\n                try_files $uri $uri/ /index.php?$query_string;\n            }\n\n            location ~ \\\\.php$ {\n                fastcgi_pass   127.0.0.1:9000;\n                fastcgi_index  index.php;\n                fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;\n                include        fastcgi_params;\n            }\n\n            location /phpmyadmin {\n                alias %lld/share/phpmyadmin;\n                index jihe_signon.php;\n\n                location ~ \\\\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_param SCRIPT_FILENAME $request_filename;\n                }\n            }\n\n            location ~ /\\\\. {\n                deny all;\n            }\n        }\n        " = "#Configuración predeterminada del sitio\n        # Generado automáticamente por JihePHPEnv\n\n        server {\n            listen %lld;\n            server_name localhost 127.0.0.1;\n\n            root %lld/var/www;\n            index index.php index.html index.htm;\n\n            location / {\n                try_files $uri $uri/ /index.php?$query_string;\n            }\n\n            location ~ \\.php$ {\n                fastcgi_pass 127.0.0.1:9000;\n                fastcgi_index index.php;\n                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                include fastcgi_params;\n            }\n\n            location /phpmyadmin {\n                alias %lld/share/phpmyadmin;\n                index jihe_signon.php;\n\n                location ~ \\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_param SCRIPT_FILENAME $request_filename;\n                }\n            }\n\n            location ~ /\\. {\n                deny all;\n            }\n        }";
"使用用户目录，无需 sudo 权限" = "Usar el directorio de usuario sin permisos sudo";
"MySQL 重启失败 (退出码: %@)" = "Error al reiniciar MySQL (código de salida: %@)";
"MySQL 重启失败 (退出码: %lld)" = "Error al reiniciar MySQL (código de salida: %lld)";
"php/{版本}/" = "php/{version}/";
"暂无错误记录" = "Aún no hay registros de errores";
"终止进程失败，可能需要管理员权限" = "Error al finalizar el proceso; es posible que se requieran privilegios de administrador";
"基础配置" = "Configuración básica";
"成功: %@ 个，失败: %@ 个\\n导出目录: %@" = "Correcto: %@, fallido: %@\\nDirectorio de exportación: %@";
"成功: %lld 个，失败: %lld 个\\n导出目录: %lld" = "Correcto: %lld, fallido: %lld\\nDirectorio de exportación: %lld";
"\n\n                # 密码保护: %@\n                location %@ {\n                    auth_basic " = "# Protección con contraseña: %@\n                location %@ {\n                    auth_basic";
"\n\n                # 密码保护: %lld\n                location %lld {\n                    auth_basic " = "# Protección con contraseña: %lld\n                location %lld {\n                    auth_basic";
"SQL 文件" = "Archivo SQL";
"暂无访问日志" = "Aún no hay registros de acceso";
"Web服务器类型: %@" = "Tipo de servidor web: %@";
"Web服务器类型: %lld" = "Tipo de servidor web: %lld";
"服务状态" = "Estado del servicio";
"跨域访问 CORS 配置" = "Configuración CORS para acceso entre dominios";
"线程堆栈大小" = "Tamaño de la pila de hilos";
"表缓存数量" = "Número de cachés de tablas";
"重启" = "Reiniciar";
"服务器地址" = "Dirección del servidor";
"重启数据库" = "Reiniciar la base de datos";
"缓冲池实例数" = "Número de instancias del grupo de búferes";
"\n            # phpMyAdmin 配置\n            location /phpmyadmin {\n                alias %@;\n                index index.php;\n\n                location ~ \\\\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_index index.php;\n                    fastcgi_param SCRIPT_FILENAME $request_filename;\n                }\n            }\n\n        " = "# Configuración de phpMyAdmin\n            location /phpmyadmin {\n                alias %@;\n                index index.php;\n\n                location ~ \\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_index index.php;\n                    fastcgi_param SCRIPT_FILENAME $request_filename;\n                }\n            }";
"\n            # phpMyAdmin 配置\n            location /phpmyadmin {\n                alias %lld;\n                index index.php;\n\n                location ~ \\\\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_index index.php;\n                    fastcgi_param SCRIPT_FILENAME $request_filename;\n                }\n            }\n\n        " = "# Configuración de phpMyAdmin\n            location /phpmyadmin {\n                alias %lld;\n                index index.php;\n\n                location ~ \\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_index index.php;\n                    fastcgi_param SCRIPT_FILENAME $request_filename;\n                }\n            }";
"打开站点目录" = "Abrir el directorio del sitio";
"\n                # HTTP 服务器块\n                server {\n                    listen 80;\n                    server_name %@;\n                    root %@;\n                    index index.php index.html index.htm;\n\n                    # 访问日志\n                    access_log %@;\n                    error_log %@;\n\n                    location / {\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\\\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:9000;\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # 禁止访问隐藏文件\n                    location ~ /\\\\. {\n                        deny all;\n                    }\n                }\n\n                # HTTPS 服务器块\n                server {\n                    listen 443 ssl;\n                    server_name %@;\n                    root %@;\n                    index index.php index.html index.htm;\n\n                    # SSL 证书配置\n                    ssl_certificate %@;\n                    ssl_certificate_key %@;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # 访问日志\n                    access_log %@;\n                    error_log %@;\n\n                    location / {\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\\\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:9000;\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # 禁止访问隐藏文件\n                    location ~ /\\\\. {\n                        deny all;\n                    }\n                }\n                " = "# Bloque del servidor HTTP\n                server {\n                    listen 80;\n                    server_name %@;\n                    root %@;\n                    index index.php index.html index.htm;\n\n                    # Registro de accesos\n                    access_log %@;\n                    error_log %@;\n\n                    location/{\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:9000;\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # Deshabilitar el acceso a archivos ocultos\n                    location ~ /\\. {\n                        deny all;\n                    }\n                }\n\n                # Bloque del servidor HTTPS\n                server {\n                    listen 443 ssl;\n                    server_name %@;\n                    root %@;\n                    index index.php index.html index.htm;\n\n                    # Configuración del certificado SSL\n                    ssl_certificate %@;\n                    ssl_certificate_key %@;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # Registro de accesos\n                    access_log %@;\n                    error_log %@;\n\n                    location/{\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:9000;\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # Deshabilitar el acceso a archivos ocultos\n                    location ~ /\\. {\n                        deny all;\n                    }\n                }";
"\n                # HTTP 服务器块\n                server {\n                    listen 80;\n                    server_name %lld;\n                    root %lld;\n                    index index.php index.html index.htm;\n\n                    # 访问日志\n                    access_log %lld;\n                    error_log %lld;\n\n                    location / {\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\\\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:9000;\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # 禁止访问隐藏文件\n                    location ~ /\\\\. {\n                        deny all;\n                    }\n                }\n\n                # HTTPS 服务器块\n                server {\n                    listen 443 ssl;\n                    server_name %lld;\n                    root %lld;\n                    index index.php index.html index.htm;\n\n                    # SSL 证书配置\n                    ssl_certificate %lld;\n                    ssl_certificate_key %lld;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # 访问日志\n                    access_log %lld;\n                    error_log %lld;\n\n                    location / {\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\\\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:9000;\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # 禁止访问隐藏文件\n                    location ~ /\\\\. {\n                        deny all;\n                    }\n                }\n                " = "# Bloque del servidor HTTP\n                server {\n                    listen 80;\n                    server_name %lld;\n                    root %lld;\n                    index index.php index.html index.htm;\n\n                    # Registro de accesos\n                    access_log %lld;\n                    error_log %lld;\n\n                    location/{\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:9000;\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # Deshabilitar el acceso a archivos ocultos\n                    location ~ /\\. {\n                        deny all;\n                    }\n                }\n\n                # Bloque del servidor HTTPS\n                server {\n                    listen 443 ssl;\n                    server_name %lld;\n                    root %lld;\n                    index index.php index.html index.htm;\n\n                    # Configuración del certificado SSL\n                    ssl_certificate %lld;\n                    ssl_certificate_key %lld;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # Registro de accesos\n                    access_log %lld;\n                    error_log %lld;\n\n                    location/{\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:9000;\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # Deshabilitar el acceso a archivos ocultos\n                    location ~ /\\. {\n                        deny all;\n                    }\n                }";
"\n            server {\n                listen 80;\n                server_name %@;\n                root %@;\n                index index.php index.html index.htm;\n\n                # 访问日志\n                access_log %@;\n                error_log %@;\n\n                location / {\n                    try_files $uri $uri/ /index.php?$args;\n                }\n\n                location ~ \\\\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_index index.php;\n                    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                }\n\n                # 禁止访问隐藏文件\n                location ~ /\\\\. {\n                    deny all;\n                }\n            }\n            " = "server {\n                listen 80;\n                server_name %@;\n                root %@;\n                index index.php index.html index.htm;\n\n                # Registro de accesos\n                access_log %@;\n                error_log %@;\n\n                location/{\n                    try_files $uri $uri/ /index.php?$args;\n                }\n\n                location ~ \\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_index index.php;\n                    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                }\n\n                # Deshabilitar el acceso a archivos ocultos\n                location ~ /\\. {\n                    deny all;\n                }\n            }";
"\n            server {\n                listen 80;\n                server_name %lld;\n                root %lld;\n                index index.php index.html index.htm;\n\n                # 访问日志\n                access_log %lld;\n                error_log %lld;\n\n                location / {\n                    try_files $uri $uri/ /index.php?$args;\n                }\n\n                location ~ \\\\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_index index.php;\n                    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                }\n\n                # 禁止访问隐藏文件\n                location ~ /\\\\. {\n                    deny all;\n                }\n            }\n            " = "server {\n                listen 80;\n                server_name %lld;\n                root %lld;\n                index index.php index.html index.htm;\n\n                # Registro de accesos\n                access_log %lld;\n                error_log %lld;\n\n                location/{\n                    try_files $uri $uri/ /index.php?$args;\n                }\n\n                location ~ \\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_index index.php;\n                    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                }\n\n                # Deshabilitar el acceso a archivos ocultos\n                location ~ /\\. {\n                    deny all;\n                }\n            }";
"批量启动" = "Inicio por lotes";
"代码格式化工具" = "Herramientas de formato de código";
"CA签发" = "Certificado emitido por la CA";
"返回站点列表" = "Volver a la lista de sitios";
"phpMyAdmin 安装成功" = "phpMyAdmin instalado correctamente";
"选择运行目录..." = "Seleccionar el directorio de ejecución...";
"无法解析 RewriteRule: %@" = "No se puede analizar la regla de reescritura: %@";
"无法解析 RewriteRule: %lld" = "No se puede analizar la regla de reescritura: %lld";
"无法执行 nginx -t: %@" = "No se puede ejecutar 'nginx -t': %@";
"无法执行 nginx -t: %lld" = "No se puede ejecutar nginx -t: %lld";
"    # 请参考 Apache mod_proxy_fcgi 配置\\n" = "# Consulte la configuración de Apache mod_proxy_fcgi\\n";
"[SiteManager] 添加 hosts 条目失败: %@ - %@" = "[SiteManager] Error al agregar la entrada en el archivo hosts: %@ - %@";
"[SiteManager] 添加 hosts 条目失败: %lld - %lld" = "[SiteManager] Error al agregar la entrada en el archivo hosts: %lld - %lld";
"\n        # MySQL 数据库认证\n        MySQLConfigFile             %@/pureftpd-mysql.conf\n\n        " = "# Autenticación de base de datos MySQL\n        MySQLConfigFile %@/pureftpd-mysql.conf";
"\n        # MySQL 数据库认证\n        MySQLConfigFile             %lld/pureftpd-mysql.conf\n\n        " = "# Autenticación de base de datos MySQL\n        MySQLConfigFile %lld/pureftpd-mysql.conf";
"日志记录" = "registro";
"连接失败" = "Conexión fallida";
"开源的PHP程序调试器" = "Depurador de programas PHP de código abierto";
"30 分钟" = "30 minutos";
"正在重启 Apache..." = "Reiniciando Apache...";
"未找到匹配的日志文件: %@" = "No se encontró ningún archivo de registro coincidente: %@";
"未找到匹配的日志文件: %lld" = "No se encontró ningún archivo de registro coincidente: %lld";
"正在安装 acme.sh..." = "Instalando acme.sh...";
"首次启动设置向导" = "Iniciando el asistente de configuración por primera vez";
"正在应用..." = "Aplicando...";
"请输入要压缩的代码" = "Introduzca el código que desea comprimir";
"))\n\n                    # 强制跳转到 HTTPS\n                    Redirect permanent / https://\\(domains.first ?? " = "))\n\n                    # Redirección forzada a HTTPS\n                    Redirect permanent / https://\\(domains.first ??";
"留空则使用域名" = "Déjelo en blanco para usar el nombre de dominio";
"单位: %@" = "Unidad: %@";
"单位: %lld" = "Unidad: %lld";
"运行目录" = "Directorio de ejecución";
"mcrypt加密/解密" = "Cifrado/descifrado con mcrypt";
"独立访客" = "Visitante único";
"高级" = "Avanzado";
"没有写入权限" = "Sin permiso de escritura";
"数据库配置" = "Configuración de la base de datos";
"搜索服务或端口..." = "Buscar un servicio o puerto...";
"使用 GoAccess 生成详细的日志分析报告" = "Usar GoAccess para generar informes detallados de análisis de registros";
"随机读取缓冲区大小" = "Tamaño del búfer para lecturas aleatorias";
"缓冲池大小" = "Tamaño del grupo de búferes";
"添加条目" = "Agregar entrada";
"Nginx SSL listen 指令" = "Directiva de escucha SSL de Nginx";
"数据库已备份" = "La base de datos ha sido respaldada";
"%@ 重启成功" = "%@ Reinicio exitoso";
"%lld 重启成功" = "%lld Reinicio exitoso";
"FTP 账号" = "Cuenta FTP";
"\n\n                    # SSL 配置\n                    SSLEngine on\n                    SSLCertificateFile " = "# Configuración SSL\n                    SSLEngine on\n                    SSLCertificateFile";
"当前 (0)" = "Actual (0)";
"远程数据库连接已保存，请使用以下工具进行管理" = "La conexión remota a la base de datos se ha guardado; utilice las siguientes herramientas para administrarla";
"站点 %@ 的配置文件已重新生成" = "El archivo de configuración para el sitio %@ se ha regenerado";
"站点 %lld 的配置文件已重新生成" = "El archivo de configuración para el sitio %lld se ha regenerado";
"• 证书将自动配置到站点" = "• El certificado se configurará automáticamente en el sitio";
"配置、数据、日志将存储在此目录" = "La configuración, los datos y los registros se almacenarán en este directorio";
"服务列表" = "Lista de servicios";
"已复制" = "Copiado";
"共 %@ 个端口" = "Puertos totales: %@";
"共 %lld 个端口" = "Puertos totales: %lld";
"准备安装 Pure-FTPd..." = "Preparando la instalación de Pure-FTPd...";
"请输入连接名称" = "Introduzca un nombre de conexión";
"会话存储方式" = "Método de almacenamiento de sesión";
"备份失败: %@" = "Error al realizar la copia de seguridad: %@";
"备份失败: %lld" = "Error al realizar la copia de seguridad: %lld";
"✓ 检测到版本: %@" = "✓ Versión detectada: %@";
"✓ 检测到版本: %lld" = "✓ Versión detectada: %lld";
"开始检查" = "Iniciando comprobación";
"未管理" = "No gestionado";
"执行 brew 命令失败: %@" = "Error al ejecutar el comando brew: %@";
"执行 brew 命令失败: %lld" = "Error al ejecutar el comando brew: %lld";
"域名:" = "Nombre de dominio:";
"部分 RewriteCond 需要手动转换" = "Algunas directivas RewriteCond requieren conversión manual";
"HTTP 自动跳转到 HTTPS" = "HTTP salta automáticamente a HTTPS";
"SourceGuardian加密脚本解密" = "Desencriptación de scripts cifrados con SourceGuardian";
"完全退出" = "Salir completamente";
"查看" = "Ver";
"暂无限制规则" = "Aún no hay reglas restrictivas";
"过期时间: 已过期" = "Fecha de caducidad: Caducada";
"淺色" = "color claro";
"已被" = "ha sido";
"已启用 %@ / %@" = "Activado %@ / %@";
"已启用 %lld / %lld" = "Activado %lld / %lld";
"版本: %@" = "Versión: %@";
"版本: %lld" = "Versión: %lld";
"站点已创建成功，但 %@ Web 服务器当前未运行。\\n\\n请启动 %@ 后，网站才能正常访问。\\n\\n您可以在「服务管理」中启动 %@。" = "El sitio se creó correctamente, pero actualmente el servidor web %@ no está en ejecución.\n\nPor favor, active %@ antes de que el sitio web pueda accederse normalmente.\n\nPuede activar %@ en la Gestión de servicios.";
"站点已创建成功，但 %lld Web 服务器当前未运行。\\n\\n请启动 %lld 后，网站才能正常访问。\\n\\n您可以在「服务管理」中启动 %lld。" = "El sitio se creó correctamente, pero actualmente el servidor web %lld no está en ejecución.\n\nPor favor, active %lld antes de que el sitio web pueda accederse normalmente.\n\nPuede activar %lld en la Gestión de servicios.";
"默认" = "Predeterminado";
"• 默认文档，每行一个，优先级由上至下" = "• Documento predeterminado, uno por línea, prioridad de arriba abajo";
"✗ 证书已过期" = "✗ El certificado ha caducado";
"改密" = "Cambiar contraseña";
"acme.sh 未安装" = "acme.sh no está instalado";
"多字节字符串处理" = "Procesamiento de cadenas multibyte";
"执行终止命令失败: %@" = "Error al ejecutar el comando de terminación: %@";
"执行终止命令失败: %lld" = "Error al ejecutar el comando de terminación: %lld";
"保存失败: %@" = "Error al guardar: %@";
"保存失败: %lld" = "Error al guardar: %lld";
"本地解析" = "análisis local";
"读取失败: %@" = "Error al leer: %@";
"读取失败: %lld" = "Error al leer: %lld";
"无法解析输入的文本" = "No se puede analizar el texto de entrada";
"设置后可避免权限问题，便于备份管理" = "Tras su configuración, se pueden evitar problemas de permisos y facilitar la gestión de copias de seguridad.";
"CGI路径修复" = "Reparación de la ruta CGI";
"一个目录包含所有数据，轻松备份迁移" = "Un solo directorio contiene todos los datos, lo que simplifica las copias de seguridad y la migración";
"数据库备注信息" = "Información de notas de la base de datos";
"请先停止服务再卸载" = "Detenga primero el servicio y luego desinstálelo.";
"端口不能为空" = "El puerto no puede estar vacío";
"   - 端口: %@ }.joined(separator: " = "- Puertos: %@ }.joined(separator:";
"   - 端口: %lld }.joined(separator: " = "- Puertos: %lld }.joined(separator:";
"• 证书已自动安装到 Keychain 并设为信任" = "• El certificado se instala automáticamente en el llavero y se configura como de confianza";
"已选择 %@ 个站点" = "%@ sitios seleccionados";
"已选择 %lld 个站点" = "%lld sitios seleccionados";
"Nginx SSL终止" = "Terminación SSL de Nginx";
"default.conf: %@ -> %@ (软链接)" = "default.conf: %@ -> %@ (enlace simbólico)";
"default.conf: %lld -> %lld (软链接)" = "default.conf: %lld -> %lld (enlace simbólico)";
"缓存配置功能正在开发中" = "La función de configuración de caché está en desarrollo";
"读取 hosts 原始内容失败：%@" = "Error al leer el contenido original del archivo hosts: %@";
"读取 hosts 原始内容失败：%lld" = "Error al leer el contenido original del archivo hosts: %lld";
"仅显示用户 %@) 的进程" = "Mostrar solo los procesos del usuario %@";
"仅显示用户 %lld) 的进程" = "Mostrar solo los procesos del usuario %lld)";
"系统数据库" = "Base de datos del sistema";
"私钥文件缺失" = "Falta el archivo de clave privada";
"说明" = "Descripción";
"正在解压安装包..." = "Descomprimiendo el paquete de instalación...";
"客户端最大请求体" = "Cuerpo máximo de la solicitud del cliente";
"安装完成！" = "¡La instalación ha finalizado!";
"OPcache 内存" = "Memoria de OPcache";
"无法启动站点：%@ Web 服务器未运行。请先在「服务管理」中启动 %@ 后再启动站点。" = "No se puede iniciar el sitio: %@ El servidor web no se está ejecutando. Inicie %@ en \"Administración de servicios\" antes de iniciar el sitio.";
"无法启动站点：%lld Web 服务器未运行。请先在「服务管理」中启动 %lld 后再启动站点。" = "No se puede iniciar el sitio: %lld El servidor web no se está ejecutando. Inicie %lld en \"Administración de servicios\" antes de iniciar el sitio.";
"批量操作" = "Operaciones por lotes";
"随机读缓冲区" = "Buffer de lectura aleatoria";
"同步配置文件" = "Sincronizar archivos de configuración";
"GoAccess 未安装" = "GoAccess no está instalado";
"创建配置文件失败: %@" = "Error al crear el archivo de configuración: %@";
"创建配置文件失败: %lld" = "Error al crear el archivo de configuración: %lld";
"删除失败" = "Error al eliminar";
"写入 hosts 原始内容失败：%@" = "Error al escribir el contenido original del archivo hosts: %@";
"写入 hosts 原始内容失败：%lld" = "Error al escribir el contenido original del archivo hosts: %lld";
"# %@ 配置文件\\n" = "# Archivo de configuración de %@\\n";
"# %lld 配置文件\\n" = "# Archivo de configuración de %lld\\n";
"• IP 地址通常为 127.0.0.1（本地）" = "• La dirección IP suele ser 127.0.0.1 (local)";
"与 %@ 配置冲突" = "Conflicto con la configuración de %@";
"与 %lld 配置冲突" = "Conflicto con la configuración de %lld";
"总访问量" = "visitas totales";
"加载更多" = "cargar más";
"• 修改root密码后，所有使用该密码的应用程序都需要更新配置" = "• Tras cambiar la contraseña de root, todas las aplicaciones que usan esta contraseña deben actualizar sus configuraciones";
"%@ 年" = "%@ años";
"%lld 年" = "%lld años";
"✅ 已为 Nginx 代理生成 Apache 后端配置: %@" = "✅ Configuración del backend de Apache generada para el proxy de Nginx: %@";
"✅ 已为 Nginx 代理生成 Apache 后端配置: %lld" = "✅ Configuración del backend de Apache generada para el proxy de Nginx: %lld";
"恢复成功" = "Recuperación exitosa";
") 配置正常" = ") la configuración es normal";
"(系统)" = "(sistema)";
"部分运行" = "En ejecución parcial";
"预览" = "Vista previa";
"请输入数据库名称" = "Introduzca el nombre de la base de datos";
"确定要终止进程 %@ (PID: %@) 吗？\\n\\n这将停止占用端口 %@ 的服务。" = "¿Está seguro de que desea terminar el proceso %@ (PID: %@)?\n\nEsto detendrá el servicio que utiliza el puerto %@.";
"确定要终止进程 %lld (PID: %lld) 吗？\\n\\n这将停止占用端口 %lld 的服务。" = "¿Está seguro de que desea terminar el proceso %lld (PID: %lld)?\n\nEsto detendrá el servicio que utiliza el puerto %lld.";
"\n                </FilesMatch>\n\n                # 访问日志\n                ErrorLog " = "</FilesMatch>\n\n                #Registro de accesos\n                ErrorLog";
"字符集" = "conjunto de caracteres";
"Base64 解码失败" = "Error al descodificar Base64";
"有效期（天）" = "Periodo de validez (días)";
"本地数据库" = "base de datos local";
"配置键" = "clave de configuración";
"设置默认站点" = "Establecer sitio predeterminado";
"运行环境" = "Entorno operativo";
"正在修改 MySQL 端口..." = "Modificando el puerto de MySQL...";
"目标数据库" = "base de datos de destino";
"数据盘" = "disco de datos";
"扫描 Nginx 和 Apache 配置目录，导入已有站点配置" = "Escanear los directorios de configuración de Nginx y Apache e importar las configuraciones de sitios existentes";
"未找到 PHP-FPM 配置文件" = "No se encontró el archivo de configuración de PHP-FPM";
"从配置文件导入: %@" = "Importar desde el archivo de configuración: %@";
"从配置文件导入: %lld" = "Importar desde el archivo de configuración: %lld";
"需要从官网下载" = "Es necesario descargarlo desde el sitio web oficial";
"(已停止)" = "(detenido)";
"加载失败" = "Error al cargar";
"显示 %@ 个用户端口" = "Mostrar %@ puertos de usuario";
"显示 %lld 个用户端口" = "Mostrar %lld puertos de usuario";
"查看差异" = "Ver diferencias";
"导入进度" = "Progreso de la importación";
"未找到版本信息" = "No se encontró información de la versión";
"网络不通？手动安装" = "¿Sin conexión a Internet? Instalación manual";
"未知" = "desconocido";
"网站健康检查" = "Comprobación del estado del sitio web";
"已停止" = "Detenido";
"选择自定义扫描目录" = "Seleccionar un directorio personalizado para el escaneo";
"常规设置" = "Configuración general";
"HTTPS防篡站" = "Sitio web protegido contra modificaciones HTTPS";
"[VhostScan] 日志文件已创建: %@" = "[VhostScan] Archivo de registro creado: %@";
"[VhostScan] 日志文件已创建: %lld" = "[VhostScan] Archivo de registro creado: %lld";
"保存数据失败: %@" = "Error al guardar los datos: %@";
"保存数据失败: %lld" = "Error al guardar los datos: %lld";
"强制重置模式" = "Forzar modo de reinicio";
"端口详情 - %@" = "Detalles del puerto - %@";
"端口详情 - %lld" = "Detalles del puerto - %lld";
"重新验证" = "volver a validar";
"ZIP压缩文件处理" = "Procesamiento de archivos comprimidos en ZIP";
"• Let's Encrypt 仅适用于可公网访问的域名" = "• Let's Encrypt solo funciona en dominios accesibles públicamente";
"服务端口" = "puerto del servicio";
"默认存储引擎" = "Motor de almacenamiento predeterminado";
"正在启动中..." = "Iniciando...";
"按文件大小切割" = "Recortar al tamaño del archivo";
"正在检测配置文件语法..." = "Detectando la sintaxis del archivo de configuración...";
"mkcert 生成证书失败" = "mkcert no pudo generar el certificado";
"请确保已安装 Homebrew 且路径配置正确" = "Asegúrese de que Homebrew esté instalado y de que la ruta esté configurada correctamente";
"打开 phpMyAdmin" = "Abrir phpMyAdmin";
"))\\n有效期: %@ 天" = "))\\nPeriodo de validez: %@ días";
"))\\n有效期: %lld 天" = "))\\nPeriodo de validez: %lld días";
"网站目录" = "directorio del sitio web";
"配置文件缺失" = "Falta el archivo de configuración";
"无法检测 Root 密码" = "No se puede detectar la contraseña de root";
"• 证书将自动安装到 Keychain 并设为信任" = "• El certificado se instalará automáticamente en Keychain y se marcará como de confianza";
"未找到 Homebrew，请先安装 Homebrew" = "Homebrew no encontrado, instale Homebrew primero";
"(运行中)" = "(en ejecución)";
"源配置文件不存在" = "El archivo de configuración de origen no existe";
"临时表大小" = "Tamaño de la tabla temporal";
"Hosts 编辑器" = "Editor de hosts";
"哈希算法" = "Algoritmo hash";
"• Nginx Combined 格式" = "• Formato combinado de Nginx";
"彻底删除" = "Eliminar completamente";
"设置访问密码" = "Establecer contraseña de acceso";
"Nginx FastCGI 连接端口" = "Puerto de conexión FastCGI de Nginx";
"mysqldump 执行失败" = "La ejecución de mysqldump ha fallado";
"正在启动 %@..." = "Iniciando %@...";
"正在启动 %lld..." = "Iniciando %lld...";
"无法读取文件: %@" = "No se puede leer el archivo: %@";
"无法读取文件: %lld" = "No se puede leer el archivo: %lld";
"域名可以正常访问，可以继续申请证书" = "El nombre de dominio se puede acceder normalmente y puede continuar solicitando un certificado.";
"配置文件错误" = "Error en el archivo de configuración";
"目录结构预览" = "Vista previa de la estructura de directorios";
"点击应用推荐值" = "Haga clic para aplicar recomendaciones";
"站点证书已成功签发。\\n\\n域名: \\(domainList.joined(separator: " = "El certificado del sitio se ha emitido correctamente. \\n\\nNombre de dominio: \\(domainList.joined(separator:";
"简" = "Jane";
"添加限制规则" = "Agregar reglas de restricción";
"刷新日志" = "Actualizar el registro";
"恢复默认" = "Restaurar valores predeterminados";
"✅ 已从 JSON 加载 %@ 个证书" = "✅ %@ certificados cargados desde JSON";
"✅ 已从 JSON 加载 %lld 个证书" = "✅ %lld certificados cargados desde JSON";
"慢查询日志文件" = "Archivo de registro de consultas lentas";
"无法打开文件位置：路径未知" = "No se puede abrir la ubicación del archivo: ruta desconocida";
"启动 %@ 失败，请展开查看详细信息" = "Error al iniciar %@; amplíe para ver los detalles";
"启动 %lld 失败，请展开查看详细信息" = "Error al iniciar %lld; amplíe para ver los detalles";
"PHP版本已停止" = "La versión de PHP ha quedado obsoleta";
"\n                # ============================================\n                # 🔄 Nginx 反向代理配置 (Apache 后端)\n                # 此配置将请求转发到 Apache 后端服务器\n                # 前端端口: 80/443 (Nginx)\n                # 后端端口: %@/%@ (Apache)\n                # ============================================\n\n                # HTTP 服务器块 - 反向代理到 Apache\n                server {\n                    listen 80;\n                    server_name %@;\n                %@\n                    location / {\n                        proxy_pass http://127.0.0.1:%@;\n                        proxy_set_header Host $host;\n                        proxy_set_header X-Real-IP $remote_addr;\n                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                        proxy_set_header X-Forwarded-Proto $scheme;\n                        proxy_set_header X-Forwarded-Host $host;\n                        proxy_set_header X-Forwarded-Port $server_port;\n                    }\n                }\n\n                # HTTPS 服务器块 - 反向代理到 Apache\n                server {\n                    listen 443 ssl;\n                    server_name %@;\n\n                    # SSL 证书配置\n                    ssl_certificate %@;\n                    ssl_certificate_key %@;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                %@\n                    location / {\n                        proxy_pass \\(site.sslMode == .nginxTermination ? " = "# ============================================\n                # 🔄 Configuración del proxy inverso de Nginx (backend Apache)\n                # Esta configuración redirige las solicitudes al servidor backend Apache\n                # Puerto frontal: 80/443 (Nginx)\n                # Puerto de backend: %@/%@ (Apache)\n                # ============================================\n\n                # Bloque de servidor HTTP: proxy inverso a Apache\n                server {\n                    listen 80;\n                    server_name %@;\n                %@\n                    location/{\n                        proxy_pass http://127.0.0.1:%@;\n                        proxy_set_header Host $host;\n                        proxy_set_header X-Real-IP $remote_addr;\n                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                        proxy_set_header X-Forwarded-Proto $scheme;\n                        proxy_set_header X-Forwarded-Host $host;\n                        proxy_set_header X-Forwarded-Port $server_port;\n                    }\n                }\n\n                # Bloque de servidor HTTPS: proxy inverso a Apache\n                server {\n                    listen 443 ssl;\n                    server_name %@;\n\n                    # Configuración del certificado SSL\n                    ssl_certificate %@;\n                    ssl_certificate_key %@;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                %@\n                    location/{\n                        proxy_pass \\(site.sslMode == .nginxTermination ?";
"\n                # ============================================\n                # 🔄 Nginx 反向代理配置 (Apache 后端)\n                # 此配置将请求转发到 Apache 后端服务器\n                # 前端端口: 80/443 (Nginx)\n                # 后端端口: %lld/%lld (Apache)\n                # ============================================\n\n                # HTTP 服务器块 - 反向代理到 Apache\n                server {\n                    listen 80;\n                    server_name %lld;\n                %lld\n                    location / {\n                        proxy_pass http://127.0.0.1:%lld;\n                        proxy_set_header Host $host;\n                        proxy_set_header X-Real-IP $remote_addr;\n                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                        proxy_set_header X-Forwarded-Proto $scheme;\n                        proxy_set_header X-Forwarded-Host $host;\n                        proxy_set_header X-Forwarded-Port $server_port;\n                    }\n                }\n\n                # HTTPS 服务器块 - 反向代理到 Apache\n                server {\n                    listen 443 ssl;\n                    server_name %lld;\n\n                    # SSL 证书配置\n                    ssl_certificate %lld;\n                    ssl_certificate_key %lld;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                %lld\n                    location / {\n                        proxy_pass \\(site.sslMode == .nginxTermination ? " = "# ============================================\n                # 🔄 Configuración del proxy inverso de Nginx (backend Apache)\n                # Esta configuración redirige las solicitudes al servidor backend Apache\n                # Puerto frontal: 80/443 (Nginx)\n                # Puerto de backend: %lld/%lld (Apache)\n                # ============================================\n\n                # Bloque de servidor HTTP: proxy inverso a Apache\n                server {\n                    listen 80;\n                    server_name %lld;\n                %lld\n                    location/{\n                        proxy_pass http://127.0.0.1:%lld;\n                        proxy_set_header Host $host;\n                        proxy_set_header X-Real-IP $remote_addr;\n                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                        proxy_set_header X-Forwarded-Proto $scheme;\n                        proxy_set_header X-Forwarded-Host $host;\n                        proxy_set_header X-Forwarded-Port $server_port;\n                    }\n                }\n\n                # Bloque de servidor HTTPS: proxy inverso a Apache\n                server {\n                    listen 443 ssl;\n                    server_name %lld;\n\n                    # Configuración del certificado SSL\n                    ssl_certificate %lld;\n                    ssl_certificate_key %lld;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                %lld\n                    location/{\n                        proxy_pass \\(site.sslMode == .nginxTermination ?";
"选择任意日志文件进行分析" = "Seleccione cualquier archivo de registro para analizarlo";
"• 完成后 MySQL 服务将自动恢复正常运行" = "• El servicio MySQL reanudará automáticamente su funcionamiento normal tras finalizar";
"安装成功" = "Instalación correcta";
"密码已更新" = "La contraseña se ha actualizado";
"显示版本号" = "Mostrar número de versión";
"PHP 7.2+ 已废弃，建议使用 openssl" = "PHP 7.2+ está en desuso; se recomienda usar OpenSSL";
"导入数据库" = "Importar base de datos";
"批量导出" = "Exportación por lotes";
"允许动态加载" = "Permitir la carga dinámica";
"mkcert 未安装" = "mkcert no está instalado";
"端到端SSL" = "SSL de extremo a extremo";
"获取数据库列表失败" = "Error al obtener la lista de bases de datos";
"扫描结果" = "Resultados del escaneo";
"文件缓存目录" = "Directorio de caché de archivos";
"默认经度" = "Longitud predeterminada";
"端口号无效" = "Número de puerto no válido";
"配置站点在遇到错误时显示的页面。默认使用 MacEnv 内置精美主题。" = "Configure la página que muestra el sitio web cuando encuentra un error. De forma predeterminada, se utilizan los temas integrados y elegantes de MacEnv.";
"解密失败: %@" = "Error al descifrar: %@";
"解密失败: %lld" = "Error al descifrar: %lld";
"正在保存配置文件..." = "Guardando el archivo de configuración...";
"正在检查端口监听..." = "Verificando la escucha del puerto...";
"JSON 格式有误: %@。已尝试强制格式化，请检查红线标注处。" = "JSON mal formado: %@. Se ha intentado forzar el formato; revise la marca de línea roja.";
"JSON 格式有误: %lld。已尝试强制格式化，请检查红线标注处。" = "JSON mal formado: %lld. Se ha intentado forzar el formato; revise la marca de línea roja.";
"未找到" = "no encontrado";
"默认纬度" = "Latitud predeterminada";
"其他程序" = "Otros programas";
"用户数据存储方式" = "Método de almacenamiento de datos del usuario";
"PHP配置" = "Configuración de PHP";
"✅ 已备份旧 Nginx 配置: %@" = "✅ La antigua configuración de Nginx se ha respaldado correctamente: %@";
"✅ 已备份旧 Nginx 配置: %lld" = "✅ La antigua configuración de Nginx se ha respaldado correctamente: %lld";
"名称" = "Nombre";
"建议使用 6379 或 6380-6399 范围内的端口" = "Se recomienda utilizar puertos en el rango 6379 o 6380-6399";
"加载服务缓存失败: %@" = "Error al cargar la caché del servicio: %@";
"加载服务缓存失败: %lld" = "Error al cargar la caché del servicio: %lld";
"无法更新数据库权限，请检查 MySQL 连接。" = "No se pueden actualizar los permisos de la base de datos; por favor, verifique su conexión a MySQL.";
"加密解密" = "Cifrado y descifrado";
"签发站点证书" = "Emitir certificado del sitio";
"会话存储路径" = "Ruta de almacenamiento de sesiones";
"未知原因" = "razón desconocida";
"高级分析" = "Análisis avanzado";
"PID 文件路径" = "Ruta del archivo PID";
"%@ 字符" = "%@ caracteres";
"%lld 字符" = "%lld caracteres";
"请再次输入新密码" = "Por favor, ingrese nuevamente la nueva contraseña";
"目录不存在：%@" = "El directorio no existe: %@";
"目录不存在：%lld" = "El directorio no existe: %lld";
"备份文件不存在" = "El archivo de respaldo no existe";
"网站数量" = "Número de sitios web";
"伪静态转换" = "Conversión pseudoestática";
"SSL证书详情" = "Detalles del certificado SSL";
">\n        <h1>恭喜，站点创建成功！</h1>\n        <h3>这是默认index.html，本文件位于站点根目录</h3>\n    </div>\n</body>\n</html>\n" = ">\n        <h1>¡Felicitaciones, el sitio se ha creado correctamente!</h1>\n        <h3>Este es el archivo index.html predeterminado, ubicado en el directorio raíz del sitio</h3>\n    </div>\n</body>\n</html>";
"长连接超时" = "Tiempo de espera para conexiones prolongadas";
"日志过期天数" = "Días de expiración de los registros";
"健康检查" = "verificación de estado";
"⚠️ mkcert 不可用，使用 openssl 生成自签名证书" = "⚠️ mkcert no está disponible; se usará openssl para generar un certificado autofirmado";
"路径已存在但不是目录" = "La ruta existe, pero no es un directorio";
"系统软件" = "Software del sistema";
"浏览" = "Examinar";
"请输入要解码的文本" = "Introduzca el texto que desea descodificar";
"确定要删除证书 %@ 吗？\\n\\n此操作不可恢复。" = "¿Está seguro de que desea eliminar el certificado %@?\n\nEsta operación no se puede deshacer.";
"确定要删除证书 %lld 吗？\\n\\n此操作不可恢复。" = "¿Está seguro de que desea eliminar el certificado %lld?\n\nEsta operación no se puede deshacer.";
"错误页面设置" = "Configuración de la página de error";
"数据库已导出到: %@" = "La base de datos se ha exportado a: %@";
"数据库已导出到: %lld" = "La base de datos se ha exportado a: %lld";
"数据库已从备份恢复" = "Base de datos restaurada desde la copia de seguridad";
"站点名称" = "Nombre del sitio";
"保存备注失败: %@" = "No se pudieron guardar las notas: %@";
"保存备注失败: %lld" = "Error al guardar las notas: %lld";
"配置文件" = "Archivo de configuración";
"添加第三方源失败" = "Error al agregar la fuente de terceros";
"设置PHP命令行版本功能正在开发中,敬请期待。\\n\\n此功能将允许您选择终端中使用的 PHP 版本。" = "La función para establecer la versión de línea de comandos de PHP está en desarrollo, así que ¡manténgase atento! \\n\\nEsta característica le permitirá seleccionar la versión de PHP utilizada en la terminal.";
"站点已启用 SSL 但未关联证书" = "El sitio tiene SSL habilitado pero no tiene ningún certificado asociado";
"终端工具" = "Herramientas de terminal";
"修改默认页面" = "Modificar página predeterminada";
"仪表盘" = "Panel de control";
"SSL证书申请功能正在开发中，敬请期待。" = "La función para solicitar certificados SSL está en desarrollo, así que ¡manténgase atento!";
"Gzip 配置" = "Configuración de Gzip";
"测试连接" = "probar conexión";
"警告: index.html 文件不存在: %@，跳过复制默认首页" = "Advertencia: el archivo index.html no existe: %@, se omite la copia de la página de inicio predeterminada";
"警告: index.html 文件不存在: %lld，跳过复制默认首页" = "Advertencia: el archivo index.html no existe: %lld, se omite la copia de la página de inicio predeterminada";
"未检测到可用 IP" = "No se detectó ninguna IP disponible";
"• 不同 PHP 版本的扩展需要分别管理，切换版本后扩展状态会同步更新" = "• Las extensiones de distintas versiones de PHP deben gestionarse por separado. Tras cambiar de versión, el estado de las extensiones se actualizará simultáneamente.";
"目标路径不是目录: %@" = "La ruta de destino no es un directorio: %@";
"目标路径不是目录: %lld" = "La ruta de destino no es un directorio: %lld";
"未找到 mysqldump 客户端" = "No se encontró el cliente mysqldump";
"目录不存在" = "El directorio no existe";
"数据库不存在" = "La base de datos no existe";
"应用配置" = "Configuración de la aplicación";
"写入失败: %@" = "Error al escribir: %@";
"写入失败: %lld" = "Error al escribir: %lld";
"⚠️ 修复站点 %@ 失败: %@" = "⚠️ Falló la reparación del sitio %@: %@";
"⚠️ 修复站点 %lld 失败: %lld" = "⚠️ Falló la reparación del sitio %lld: %lld";
"用于接收证书到期提醒" = "Utilizado para recibir recordatorios de expiración de certificados";
"编码" = "codificación";
"安装 PHP %@ 时发现端口冲突" = "Se detectó un conflicto de puertos al instalar PHP %@";
"安装 PHP %lld 时发现端口冲突" = "Se detectó un conflicto de puertos al instalar PHP %lld";
"颁发机构" = "autoridad emisora";
"暂无远程连接" = "Aún no hay conexión remota";
"403 Forbidden (禁止访问)" = "403 Prohibido (acceso denegado)";
"✓ 端口 %@ 可用" = "✓ El puerto %@ está disponible";
"✓ 端口 %lld 可用" = "✓ El puerto %lld está disponible";
"提示：可使用历史备份回滚配置" = "Sugerencia: Puede utilizar copias de seguridad anteriores para restaurar configuraciones";
"刷新服务列表" = "Actualizar lista de servicios";
"SSL 架构模式" = "Patrones de arquitectura SSL";
"端口修改后重启" = "Reiniciar tras modificar el puerto";
"\n        连接信息已复制到剪贴板\n\n        您可以在 Navicat 中使用以下信息：\n        主机: %@\n        端口: %@\n        用户名: root\n        密码: \\(password.isEmpty ? " = "Información de conexión copiada al portapapeles\n\n        Puede usar la siguiente información en Navicat:\n        Host: %@\n        Puerto: %@\n        Nombre de usuario: root\n        Contraseña: \\(password.isEmpty ?";
"\n        连接信息已复制到剪贴板\n\n        您可以在 Navicat 中使用以下信息：\n        主机: %lld\n        端口: %lld\n        用户名: root\n        密码: \\(password.isEmpty ? " = "Información de conexión copiada al portapapeles\n\n        Puede usar la siguiente información en Navicat:\n        Host: %lld\n        Puerto: %lld\n        Nombre de usuario: root\n        Contraseña: \\(password.isEmpty ?";
"级别" = "nivel";
"独立账号访问" = "Acceso independiente a la cuenta";
"连接已断开" = "La conexión se ha perdido";
"转换" = "Convertir";
"Homebrew 未安装，请先安装 Homebrew" = "Homebrew no está instalado; instálelo primero";
"不支持的协议版本: %@" = "Versión del protocolo no compatible: %@";
"不支持的协议版本: %lld" = "Versión del protocolo no compatible: %lld";
"遇到错误时继续执行，而不是停止导入" = "Continuar la ejecución al encontrar un error, en lugar de detener la importación";
"确定" = "Aceptar";
"# 内置扩展，请在 php.ini 中启用" = "# Extensión integrada; actívela en php.ini";
"未选择文件" = "Ningún archivo seleccionado";
"备份数据库失败" = "Error al hacer copia de seguridad de la base de datos";
"重启所有Web相关服务" = "Reiniciar todos los servicios web relacionados";
"工作空间用于存储网站文件、配置、数据库和日志。\\n设置后可避免权限问题，便于统一管理和备份。" = "El espacio de trabajo se utiliza para almacenar archivos de sitios web, configuraciones, bases de datos y registros. \\nTras su configuración, se pueden evitar problemas de permisos y facilitar la gestión unificada y las copias de seguridad.";
"端口冲突：\\(version.conflictingService ?? " = "Conflicto de puertos: \\(version.conflictingService ??";
"服务器要求完整认证，请确保密码正确或启用 SSL" = "El servidor requiere autenticación completa; asegúrese de que la contraseña sea correcta o habilite SSL";
"数据库关联功能正在开发中" = "La función de correlación de bases de datos está en desarrollo";
"切换 PHP 版本失败" = "Error al cambiar la versión de PHP";
"• 支持阿里云、腾讯云等云厂商的云数据库" = "• Admite bases de datos en la nube de proveedores como Alibaba Cloud y Tencent Cloud";
"类型" = "Tipo";
"已成功导出 %@ 个数据库到: %@" = "Se exportaron correctamente %@ bases de datos a: %@";
"已成功导出 %lld 个数据库到: %lld" = "Se exportaron correctamente %lld bases de datos a: %lld";
"📄 找到 %@ 个文件" = "📄 Se encontraron %@ archivos";
"📄 找到 %lld 个文件" = "📄 Se encontraron %lld archivos";
"安装更多版本..." = "Instalar más versiones...";
"自动休眠空闲服务" = "Poner en modo de suspensión automáticamente los servicios inactivos";
"字符数: %@" = "Número de caracteres: %@";
"字符数: %lld" = "Número de caracteres: %lld";
"查看连接配置" = "Ver la configuración de conexión";
"站点管理设置功能正在开发中,敬请期待。" = "La función de configuración de gestión de sitios se encuentra actualmente en desarrollo; ¡manténgase atento!";
"正在卸载 %@..." = "Desinstalando %@...";
"正在卸载 %lld..." = "Desinstalando %lld...";
"有备份(%@)" = "Existe una copia de seguridad (%@)";
"有备份(%lld)" = "Existe una copia de seguridad (%lld)";
"[BatchImport] 导入站点失败: %@ - %@" = "[Importación por lotes] Error al importar el sitio: %@ - %@";
"[BatchImport] 导入站点失败: %lld - %lld" = "[Importación por lotes] Error al importar el sitio: %lld - %lld";
"数据文件" = "archivo de datos";
"使用默认路径" = "Usar la ruta predeterminada";
"输出结果" = "Mostrar resultados";
"创建网站目录失败: %@\\n请检查路径权限或修改网站目录" = "Error al crear el directorio del sitio web: %@\\nVerifique los permisos de la ruta o modifique el directorio del sitio web";
"创建网站目录失败: %lld\\n请检查路径权限或修改网站目录" = "Error al crear el directorio del sitio web: %lld\\nVerifique los permisos de la ruta o modifique el directorio del sitio web";
"确定要删除远程数据库连接「%@」吗？此操作不会删除远程服务器上的数据。" = "¿Está seguro de que desea eliminar la conexión remota a la base de datos \"%@\"? Esta operación no elimina los datos en el servidor remoto.";
"确定要删除远程数据库连接「%lld」吗？此操作不会删除远程服务器上的数据。" = "¿Está seguro de que desea eliminar la conexión remota a la base de datos \"%lld\"? Esta operación no elimina los datos en el servidor remoto.";
"自动创建同名库" = "Crear automáticamente una biblioteca con el mismo nombre";
"父路径不是目录" = "La ruta padre no es un directorio";
"PHP 运行异常" = "PHP se ejecuta de forma anormal";
"⏭️  跳过非配置文件: %@" = "⏭️ Omitir archivos que no son de configuración: %@";
"⏭️  跳过非配置文件: %lld" = "⏭️ Omitir archivos que no son de configuración: %lld";
"加密失败" = "Error al cifrar";
"支持 TLS/SSL 加密传输" = "Soporta transmisión cifrada TLS/SSL";
"mysql 执行失败" = "Error al ejecutar mysql";
"安装目录" = "Directorio de instalación";
"伪静态规则" = "reglas de pseudo-estático";
"配置修改" = "Modificación de la configuración";
"自动识别" = "reconocimiento automático";
"持久化数据" = "Datos persistentes";
"正在配置环境..." = "Configurando el entorno...";
"工具箱" = "caja de herramientas";
"停止" = "detener";
"不切割" = "Sin recorte";
"应用设置" = "Aplicar configuración";
"在添加远程数据库前，请先测试连接以确保配置正确。" = "Antes de agregar una base de datos remota, pruebe la conexión para asegurarse de que esté configurada correctamente.";
"保存错误日志失败: %@" = "Error al guardar el registro de errores: %@";
"保存错误日志失败: %lld" = "Error al guardar el registro de errores: %lld";
"请输入用户名" = "Ingrese el nombre de usuario";
"远程连接" = "conexión remota";
"错误: %@" = "Error: %@";
"错误: %lld" = "Error: %lld";
"请先安装 MySQL" = "Instale primero MySQL";
"配置文件仍在 Homebrew 默认路径，建议迁移到工作空间以便统一管理" = "El archivo de configuración sigue estando en la ruta predeterminada de Homebrew. Se recomienda migrarlo al espacio de trabajo para una gestión unificada.";
"响应日志" = "Registro de respuestas";
"• 支持 .sql 和 .sql.gz 格式的文件" = "• Admite archivos en formatos .sql y .sql.gz";
"全局设置" = "Configuración global";
"站点创建成功" = "Sitio creado correctamente";
"日志安全分析" = "Análisis de seguridad del registro";
"创建父目录失败: %@\\n请检查路径权限或修改网站目录" = "Error al crear el directorio padre: %@\\nVerifique los permisos de la ruta o modifique el directorio del sitio web";
"创建父目录失败: %lld\\n请检查路径权限或修改网站目录" = "Error al crear el directorio padre: %lld\\nVerifique los permisos de la ruta o modifique el directorio del sitio web";
"跳过DNS解析" = "Omitir resolución DNS";
"✓ 版本匹配: %@" = "✓ Coincidencia de versión: %@";
"✓ 版本匹配: %lld" = "✓ Coincidencia de versión: %lld";
"读IO线程数" = "Número de hilos de E/S de lectura";
"临时表缓存大小" = "Tamaño de la caché de tablas temporales";
"显示启动错误" = "Mostrar error de inicio";
"缓存" = "caché";
"打开日志目录" = "Abrir directorio de registros";
"快速打开终端" = "Abrir rápidamente una terminal";
"选择" = "Seleccionar";
"• 请确保您有权限修改该数据库的密码" = "• Asegúrese de tener permiso para cambiar la contraseña de la base de datos";
"可选备注信息" = "Observaciones opcionales";
"\n        # 转换自 Apache 规则\n        # 注意：部分规则可能需要手动调整\n\n        " = "# Convertir desde reglas de Apache\n        # Nota: Algunas reglas pueden requerir ajustes manuales";
"添加 Hosts 条目" = "Agregar entrada en Hosts";
"PHP %@ 运行正常" = "PHP %@ funciona correctamente";
"PHP %lld 运行正常" = "PHP %lld funciona correctamente";
"正在下载..." = "Descargando...";
"端口 %@ 已被 %@ 占用，请先停止该服务" = "El puerto %@ está ocupado por %@; detenga primero el servicio";
"端口 %lld 已被 %lld 占用，请先停止该服务" = "El puerto %lld está ocupado por %lld; detenga primero el servicio";
"Apache 端口" = "Puerto de Apache";
"GoAccess 输出无效" = "La salida de GoAccess no es válida";
"安装 phpMyAdmin 失败: %@" = "La instalación de phpMyAdmin ha fallado: %@";
"安装 phpMyAdmin 失败: %lld" = "La instalación de phpMyAdmin ha fallado: %lld";
"Root密码已成功修改。" = "La contraseña de root se ha modificado correctamente.";
"无密码" = "Sin contraseña";
"多个域名使用逗号隔开" = "Utilice comas para separar varios nombres de dominio";
"二进制日志" = "Registro binario";
"MySQL默认端口" = "Puerto predeterminado de MySQL";
"如: public_html（留空则使用子目录根目录）" = "Por ejemplo: public_html (déjelo en blanco para usar el directorio raíz del subdirectorio)";
"网络配置" = "Configuración de red";
"未开始验证" = "La verificación no ha comenzado";
"正在安装 Pure-FTPd..." = "Instalando Pure-FTPd...";
"导入选项" = "Opciones de importación";
"[VhostScan] 写入日志失败: %@" = "[VhostScan] Error al escribir el registro: %@";
"[VhostScan] 写入日志失败: %lld" = "[VhostScan] Error al escribir el registro: %lld";
"默认时区" = "Zona horaria predeterminada";
"请输入新密码" = "Introduzca la nueva contraseña";
"计算中..." = "Calculando...";
"工作空间未配置" = "El espacio de trabajo no está configurado";
"无法创建目录" = "No se puede crear el directorio";
"调试器" = "depurador";
"设置" = "ajustes";
"续期" = "Renovar";
"⚠ HTTPS 连接失败" = "⚠ La conexión HTTPS ha fallado";
"SSL证书" = "Certificado SSL";
"✓ 配置文件存在: %@" = "✓ El archivo de configuración existe: %@";
"✓ 配置文件存在: %lld" = "✓ El archivo de configuración existe: %lld";
"网站根目录" = "Directorio raíz del sitio web";
"高级日志分析" = "Análisis avanzado de registros";
"关联表缓存大小" = "Tamaño de la caché de la tabla de asociación";
"运行用户" = "Usuario de ejecución";
"确认恢复" = "Confirmar recuperación";
"配置同步结果" = "Configurar resultados de sincronización";
"已安装扩展" = "Extensión instalada";
"Nginx listen 指令" = "Directiva 'listen' de Nginx";
"伪静态规则管理功能正在开发中" = "La función de gestión de reglas pseudo-estáticas está en desarrollo";
"基本信息" = "Información básica";
"文件上传" = "Carga de archivos";
"最大输入嵌套层级" = "Nivel máximo de anidamiento de entrada";
"端口 %@ 已被 %@ 占用\\n\\n建议：%@" = "El puerto %@ ya está ocupado por %@\\n\\nSugerencia: %@";
"端口 %lld 已被 %lld 占用\\n\\n建议：%lld" = "El puerto %lld ya está ocupado por %lld\\n\\nSugerencia: %lld";
"X Protocol 端口" = "Puerto del protocolo X";
"正在修改 Redis 端口..." = "Modificando el puerto de Redis...";
"Pure-FTPd 是一个安全、高效的 FTP 服务器" = "Pure-FTPd es un servidor FTP seguro y eficiente";
"语言" = "idioma";
"XSS攻击" = "Ataque XSS";
"最小空闲进程" = "Proceso inactivo mínimo";
"安全配置" = "Configuración de seguridad";
"用户自定义参数" = "Parámetros definidos por el usuario";
"正在加载日志..." = "Cargando registros...";
"修改连接" = "Modificar conexión";
"最多保留 10 个备份" = "Conservar hasta 10 copias de seguridad";
"🔍 处理配置文件: %@" = "🔍 Procesando archivos de configuración: %@";
"🔍 处理配置文件: %lld" = "🔍 Procesando archivos de configuración: %lld";
"重置密码" = "restablecer contraseña";
"[SiteManager] 删除 hosts 条目失败: %@ - %@" = "[SiteManager] Error al eliminar la entrada de hosts: %@ - %@";
"[SiteManager] 删除 hosts 条目失败: %lld - %lld" = "[SiteManager] Error al eliminar la entrada de hosts: %lld - %lld";
"\n        # 默认站点配置\n        # 由 JihePHPEnv 自动生成\n        # 生成时间: %@.string(from: Date()))\n\n        server {\n            listen       %@;\n            server_name  localhost 127.0.0.1;\n\n            root   %@;\n            index  index.php index.html index.htm;\n\n            location / {\n                try_files $uri $uri/ /index.php?$query_string;\n            }\n\n            # PHP-FPM 配置\n            location ~ \\\\.php$ {\n                fastcgi_pass   127.0.0.1:9000;\n                fastcgi_index  index.php;\n                fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;\n                include        fastcgi_params;\n            }\n\n        " = "#Configuración predeterminada del sitio\n        #Generado automáticamente por JihePHPEnv\n        #Hora de generación: %@.string(from: Date()))\n\n        server {\n            listen %@;\n            server_name localhost 127.0.0.1;\n\n            root %@;\n            index index.php index.html index.htm;\n\n            location/{\n                try_files $uri $uri/ /index.php?$query_string;\n            }\n\n            #Configuración de PHP-FPM\n            location ~ \\.php$ {\n                fastcgi_pass 127.0.0.1:9000;\n                fastcgi_index index.php;\n                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                include fastcgi_params;\n            }";
"\n        # 默认站点配置\n        # 由 JihePHPEnv 自动生成\n        # 生成时间: %lld.string(from: Date()))\n\n        server {\n            listen       %lld;\n            server_name  localhost 127.0.0.1;\n\n            root   %lld;\n            index  index.php index.html index.htm;\n\n            location / {\n                try_files $uri $uri/ /index.php?$query_string;\n            }\n\n            # PHP-FPM 配置\n            location ~ \\\\.php$ {\n                fastcgi_pass   127.0.0.1:9000;\n                fastcgi_index  index.php;\n                fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;\n                include        fastcgi_params;\n            }\n\n        " = "#Configuración predeterminada del sitio\n        #Generado automáticamente por JihePHPEnv\n        #Hora de generación: %lld.string(from: Date()))\n\n        server {\n            listen %lld;\n            server_name localhost 127.0.0.1;\n\n            root %lld;\n            index index.php index.html index.htm;\n\n            location/{\n                try_files $uri $uri/ /index.php?$query_string;\n            }\n\n            #Configuración de PHP-FPM\n            location ~ \\.php$ {\n                fastcgi_pass 127.0.0.1:9000;\n                fastcgi_index index.php;\n                fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                include fastcgi_params;\n            }";
"准备安装" = "Listo para instalar";
"导入" = "importar";
"无法解析 PHP 版本" = "No se puede analizar la versión de PHP";
"在 Nginx 和 Apache 配置目录中未找到可导入的站点配置" = "No se encontró ninguna configuración de sitio importable en los directorios de configuración de Nginx y Apache";
"简体中文" = "Chino simplificado";
"哈希值" = "valor hash";
"国际化扩展，支持多语言" = "Expansión internacional, con soporte para múltiples idiomas";
"卸载" = "Desinstalar";
"查看错误日志" = "Ver el registro de errores";
"\n                # ============================================\n                # 🔄 Nginx 反向代理配置 (Apache 后端)\n                # 此配置将请求转发到 Apache 后端服务器\n                # 前端端口: 80/443 (Nginx)\n                # 后端端口: %@/%@ (Apache)\n                # ============================================\n\n                # HTTP 服务器块 - 重定向到 HTTPS\n                server {\n                    listen 80;\n                    server_name %@;\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # HTTPS 服务器块 - 反向代理到 Apache\n                server {\n                    listen 443 ssl;\n                    server_name %@;\n\n                    # SSL 证书配置\n                    ssl_certificate %@;\n                    ssl_certificate_key %@;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                %@\n                    # 反向代理配置\n                    location / {\n                        proxy_pass \\(site.sslMode == .nginxTermination ? " = "# ============================================\n                # 🔄 Configuración del proxy inverso de Nginx (backend Apache)\n                # Esta configuración redirige las solicitudes al servidor backend Apache\n                # Puerto frontal: 80/443 (Nginx)\n                # Puerto de backend: %@/%@ (Apache)\n                # ============================================\n\n                # Bloque del servidor HTTP: redirección a HTTPS\n                server {\n                    listen 80;\n                    server_name %@;\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # Bloque del servidor HTTPS: proxy inverso a Apache\n                server {\n                    listen 443 ssl;\n                    server_name %@;\n\n                    # Configuración del certificado SSL\n                    ssl_certificate %@;\n                    ssl_certificate_key %@;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                %@\n                    # Configuración del proxy inverso\n                    location/{\n                        proxy_pass \\(site.sslMode == .nginxTermination ?";
"\n                # ============================================\n                # 🔄 Nginx 反向代理配置 (Apache 后端)\n                # 此配置将请求转发到 Apache 后端服务器\n                # 前端端口: 80/443 (Nginx)\n                # 后端端口: %lld/%lld (Apache)\n                # ============================================\n\n                # HTTP 服务器块 - 重定向到 HTTPS\n                server {\n                    listen 80;\n                    server_name %lld;\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # HTTPS 服务器块 - 反向代理到 Apache\n                server {\n                    listen 443 ssl;\n                    server_name %lld;\n\n                    # SSL 证书配置\n                    ssl_certificate %lld;\n                    ssl_certificate_key %lld;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                %lld\n                    # 反向代理配置\n                    location / {\n                        proxy_pass \\(site.sslMode == .nginxTermination ? " = "# ============================================\n                # 🔄 Configuración del proxy inverso de Nginx (backend Apache)\n                # Esta configuración redirige las solicitudes al servidor backend Apache\n                # Puerto frontal: 80/443 (Nginx)\n                # Puerto de backend: %lld/%lld (Apache)\n                # ============================================\n\n                # Bloque del servidor HTTP: redirección a HTTPS\n                server {\n                    listen 80;\n                    server_name %lld;\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # Bloque del servidor HTTPS: proxy inverso a Apache\n                server {\n                    listen 443 ssl;\n                    server_name %lld;\n\n                    # Configuración del certificado SSL\n                    ssl_certificate %lld;\n                    ssl_certificate_key %lld;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                %lld\n                    # Configuración del proxy inverso\n                    location/{\n                        proxy_pass \\(site.sslMode == .nginxTermination ?";
"InnoDB日志缓冲区大小" = "Tamaño del búfer de registro de InnoDB";
"无效的HTTP响应" = "Respuesta HTTP no válida";
"显示错误" = "Mostrar error";
"配置迁移失败: %@" = "Error en la migración de la configuración: %@";
"配置迁移失败: %lld" = "Error en la migración de la configuración: %lld";
"图像处理" = "procesamiento de imágenes";
"证书状态" = "Estado del certificado";
"系统默认页面" = "Página predeterminada del sistema";
"日志切割" = "segmentación de registros";
"选择的 Web 服务器（%@）未安装。请先安装 %@ 或选择其他已安装的 Web 服务器。" = "El servidor web seleccionado (%@) no está instalado. Instale primero %@ o seleccione otro servidor web instalado.";
"选择的 Web 服务器（%lld）未安装。请先安装 %lld 或选择其他已安装的 Web 服务器。" = "El servidor web seleccionado (%lld) no está instalado. Instale primero %lld o seleccione otro servidor web instalado.";
"目录不存在且无法创建: %@\\n\\n请先在设置中配置默认站点主目录。" = "El directorio no existe y no se puede crear: %@\\n\\nConfigure primero el directorio raíz predeterminado del sitio en la configuración.";
"目录不存在且无法创建: %lld\\n\\n请先在设置中配置默认站点主目录。" = "El directorio no existe y no se puede crear: %lld\\n\\nConfigure primero el directorio raíz predeterminado del sitio en la configuración.";
"保存规则" = "guardar regla";
"• 证书有效期 90 天，需定期续期" = "• El certificado es válido durante 90 días y debe renovarse periódicamente";
"限制目录" = "directorio restringido";
"无法连接到服务器，请检查地址和端口" = "No se puede conectar al servidor; por favor, verifique la dirección y el puerto";
"复制" = "Copiar";
"共扫描 %@ 条日志，发现 %@ 个威胁" = "Se escanearon un total de %@ registros y se encontraron %@ amenazas";
"共扫描 %lld 条日志，发现 %lld 个威胁" = "Se escanearon un total de %lld registros y se encontraron %lld amenazas";
"PDO SQLite驱动" = "Controlador SQLite PDO";
"优化方案" = "Plan de optimización";
"正在检测已安装的服务..." = "Detectando servicios instalados...";
"PHP命令行版本" = "Versión de línea de comandos de PHP";
"搜索..." = "Buscar...";
"✅ openssl 生成自签名证书成功: %@" = "✅ openssl generó correctamente un certificado autofirmado: %@";
"✅ openssl 生成自签名证书成功: %lld" = "✅ openssl generó correctamente un certificado autofirmado: %lld";
"复制输出" = "Copiar salida";
"查询缓存类型" = "Tipo de caché de consultas";
"签发" = "problema";
"将保持当前管理状态（未管理条目仍为未管理）。" = "El estado actual de administración se mantendrá (las entradas no administradas seguirán sin administrar).";
"找到 %@ 个配置文件" = "Se encontraron %@ archivos de configuración";
"找到 %lld 个配置文件" = "Se encontraron %lld archivos de configuración";
"搜索日志内容..." = "Buscar en el contenido del registro...";
"父目录没有写权限" = "El directorio padre no tiene permisos de escritura";
"配置、数据、日志、网站文件集中存储" = "Almacenamiento centralizado de configuración, datos, registros y archivos del sitio web";
"请输入站点名称" = "Ingrese el nombre del sitio";
"全部监听端口" = "Todos los puertos en escucha";
"数据库 %@ 已成功备份" = "La base de datos %@ se ha respaldado correctamente";
"数据库 %lld 已成功备份" = "La base de datos %lld se ha respaldado correctamente";
"（需要输入实际密码）" = "(requiere la contraseña real)";
"配置文件 (%@)" = "Archivo de configuración (%@)";
"配置文件 (%lld)" = "Archivo de configuración (%lld)";
"白名单模式" = "modo de lista blanca";
"远程数据库密码已成功修改。" = "La contraseña de la base de datos remota se ha modificado correctamente.";
"启动进程数" = "Número de procesos iniciados";
"新端口" = "nuevo puerto";
"启用 SSL" = "Habilitar SSL";
"隐式刷新" = "actualización implícita";
"无效的密文格式" = "Formato de texto cifrado no válido";
"确认终止进程" = "Confirmar para terminar el proceso";
"✗ 证书文件不存在: %@" = "✗ El archivo de certificado no existe: %@";
"✗ 证书文件不存在: %lld" = "✗ El archivo de certificado no existe: %lld";
"检查第三方源..." = "Comprobando fuentes de terceros...";
"迁移失败: %@" = "La migración falló: %@";
"迁移失败: %lld" = "La migración falló: %lld";
"确定要删除选中的 %@ 个数据库吗？此操作不可恢复。" = "¿Está seguro de que desea eliminar las %@ bases de datos seleccionadas? Esta operación es irreversible.";
"确定要删除选中的 %lld 个数据库吗？此操作不可恢复。" = "¿Está seguro de que desea eliminar las %lld bases de datos seleccionadas? Esta operación es irreversible.";
"创建用户失败: \\(userResult.error ?? " = "Error al crear el usuario: \\(userResult.error ??";
"初始化失败" = "Error al inicializar";
"及和团队" = "Equipo Jihe";
"黑名单 (仅拒绝)" = "Lista negra (solo denegación)";
"优化所有表" = "Optimizar todas las tablas";
"所有域名解析正常" = "Todos los nombres de dominio se resuelven correctamente";
"未配置" = "No configurado";
"站点名称已存在" = "El nombre del sitio ya existe";
"远程数据库" = "Base de datos remota";
"AES-GCM (推荐)" = "AES-GCM (recomendado)";
"重新生成" = "Regenerar";
"仅文件缓存" = "Solo caché de archivos";
"文档根目录不存在" = "La raíz del documento no existe";
"%@ 重新加载配置失败，请检查日志" = "%@ Error al recargar la configuración; por favor, revise los registros";
"%lld 重新加载配置失败，请检查日志" = "%lld errores al recargar la configuración; por favor, revise los registros";
"更换Web服务器失败" = "No se pudo reemplazar el servidor web";
"\\n    # PHP-FPM 配置需要在 Apache 中单独设置\\n" = "\\n # La configuración de PHP-FPM debe establecerse por separado en Apache\\n";
"• 从历史记录恢复：从最近5次配置备份中选择一个恢复" = "• Restaurar desde el historial: seleccione una de las últimas 5 copias de seguridad de la configuración para restaurarla";
"点击查看SSL证书详情" = "Haga clic para ver los detalles del certificado SSL";
"\n        # 默认站点配置\n        # 由 JihePHPEnv 自动生成\n        # 生成时间: %@.string(from: Date()))\n\n        <VirtualHost *:%@>\n            ServerName localhost\n            DocumentRoot " = "#Configuración del sitio predeterminado\n        # Generado automáticamente por JihePHPEnv\n        #Hora de generación: %@.string(from: Date()))\n\n        <VirtualHost *:%@>\n            ServerName localhost\n            DocumentRoot";
"\n        # 默认站点配置\n        # 由 JihePHPEnv 自动生成\n        # 生成时间: %lld.string(from: Date()))\n\n        <VirtualHost *:%lld>\n            ServerName localhost\n            DocumentRoot " = "#Configuración del sitio predeterminado\n        # Generado automáticamente por JihePHPEnv\n        #Hora de generación: %lld.string(from: Date()))\n\n        <VirtualHost *:%lld>\n            ServerName localhost\n            DocumentRoot";
"连接名称" = "nombre de la conexión";
"配置已应用成功" = "La configuración se ha aplicado correctamente";
"数据库已成功导入。" = "La base de datos se ha importado correctamente.";
"已选择文件：" = "Archivo seleccionado:";
"未配置SSL证书" = "Certificado SSL no configurado";
"❌ 目录不存在: %@" = "❌ El directorio no existe: %@";
"❌ 目录不存在: %lld" = "❌ El directorio no existe: %lld";
"根据系统内存自动选择最佳方案" = "Seleccionar automáticamente la mejor solución según la memoria del sistema";
" 天" = "día";
"默认配置" = "Configuración predeterminada";
"索引缓冲区大小" = "Tamaño del búfer de índices";
"慢查询阈值" = "Umbral de consultas lentas";
"无法自动检测 MySQL Root 密码，可能原因：" = "No es posible detectar automáticamente la contraseña de root de MySQL; posibles causas:";
"限制模式" = "modo restringido";
"输入端口号" = "Introduzca el número de puerto";
"选择包含站点子目录的父目录" = "Seleccione el directorio principal que contiene el subdirectorio del sitio";
"默认字符集" = "Conjunto de caracteres predeterminado";
"导出完成" = "Exportación completada";
"⚠️ 端口 %@ 已被 %@ 占用" = "⚠️El puerto %@ ya está ocupado por %@";
"⚠️ 端口 %lld 已被 %lld 占用" = "⚠️El puerto %lld ya está ocupado por %lld";
"续期功能开发中" = "La función de renovación está en desarrollo";
"其他" = "Otros";
"未找到 Nginx 配置文件" = "No se encontró el archivo de configuración de Nginx";
"• CA 证书用于签发站点 SSL 证书" = "• Certificado CA utilizado para emitir el certificado SSL del sitio";
"系统会自动扫描一层子目录，并根据目录名推导域名" = "El sistema escaneará automáticamente una capa de subdirectorios y derivará el nombre de dominio basándose en el nombre del directorio.";
"修改密码失败" = "Error al cambiar la contraseña";
"配置 Nginx 失败" = "Error al configurar Nginx";
"准备中" = "En preparación";
"事务提交刷新策略" = "Estrategia de actualización para la confirmación de transacciones";
"网站停用后的提示页" = "Página de aviso tras la desactivación del sitio web";
"证书详情" = "Detalles del certificado";
"系统未安装 mkcert 工具，无法自动安装信任 CA。请先通过 Homebrew 安装：brew install mkcert" = "La herramienta mkcert no está instalada en el sistema y no se puede instalar automáticamente la autoridad certificadora (CA) de confianza. Instálela primero mediante Homebrew: brew install mkcert";
"更新 Nginx 站点配置中的监听端口" = "Actualizar el puerto de escucha en la configuración del sitio de Nginx";
"错误页面" = "página de error";
"主配置 listen 指令:" = "Directiva principal de escucha de la configuración:";
"请求超时" = "Tiempo de espera de la solicitud";
"需要从官网下载对应版本" = "Necesita descargar la versión correspondiente desde el sitio web oficial";
"%@ 行" = "%@ líneas";
"%lld 行" = "%lld líneas";
"端口号必须在 1-65535 之间" = "El número de puerto debe estar entre 1 y 65535";
"配置失败: %@" = "Error en la configuración: %@";
"配置失败: %lld" = "Error en la configuración: %lld";
"Redis扩展仅支持一个PHP版本安装使用" = "La extensión Redis solo admite una versión de PHP para su instalación y uso.";
"强大的内容缓存器" = "Caché de contenido potente";
"建议使用 3306 或 3307-3399 范围内的端口" = "Se recomienda utilizar puertos en el rango 3306 o 3307-3399";
"安装 Pure-FTPd" = "Instalar Pure-FTPd";
"页" = "página";
"搜索域名..." = "Buscar nombre de dominio...";
"主机" = "Host";
"已占用" = "Ya ocupado";
"取消全选" = "Deseleccionar todo";
"❌ 扫描失败: %@" = "❌ Error al escanear: %@";
"❌ 扫描失败: %lld" = "❌ Error al escanear: %lld";
"HTTP状态码：%@" = "Código de estado HTTP: %@";
"HTTP状态码：%lld" = "Código de estado HTTP: %lld";
"• opcache/apcu等脚本缓存扩展，请只安装其中1个，否则可能导致站点程序异常" = "• Extensiones de caché de scripts como opcache/apcu; instale solo una de ellas, de lo contrario podría provocar excepciones en el programa del sitio";
"写IO线程数" = "Número de hilos de E/S de escritura";
"保存并重启前自动备份" = "Guardar y hacer copia de seguridad automáticamente antes de reiniciar";
"停止 %@" = "Detener %@";
"停止 %lld" = "Detener %lld";
"CA 证书" = "Certificado CA";
"上传临时目录" = "Directorio temporal de carga";
"安装扩展" = "Instalar extensión";
"暂无已安装版本" = "Aún no se ha instalado ninguna versión";
"复制自: %@" = "Copiado de: %@";
"复制自: %lld" = "Copiado de: %lld";
"保存" = "Guardar";
"快速关闭" = "Cierre rápido";
"另存为模板" = "Guardar como plantilla";
"✓ 配置文件语法正确" = "✓ La sintaxis del archivo de configuración es correcta";
"停止失败" = "Error al detener";
"当前值" = "valor actual";
"重写时不同步" = "Desincronizado al reescribir";
"[VhostScan] 创建日志目录失败: %@" = "[VhostScan] Error al crear el directorio de registros: %@";
"[VhostScan] 创建日志目录失败: %lld" = "[VhostScan] Error al crear el directorio de registros: %lld";
"用户名: %@" = "Nombre de usuario: %@";
"用户名: %lld" = "Nombre de usuario: %lld";
"有 %@ 个域名配置异常" = "Hay %@ nombres de dominio con configuraciones anómalas";
"有 %lld 个域名配置异常" = "Hay %lld nombres de dominio con configuraciones anómalas";
"忽略重复错误" = "Ignorar errores duplicados";
"密码" = "Contraseña";
"未运行" = "No en ejecución";
"加载站点数据失败: %@" = "Error al cargar los datos del sitio: %@";
"加载站点数据失败: %lld" = "Error al cargar los datos del sitio: %lld";
"确认卸载" = "Confirmar desinstalación";
"添加远程数据库" = "Agregar base de datos remota";
"私钥路径" = "Ruta de la clave privada";
"已啟用 %@ / %@" = "Activado %@ / %@";
"已啟用 %lld / %lld" = "Activado %lld / %lld";
"... (显示最后 %@ 行)\\n\\n" = "... (mostrar las últimas %@ líneas)\\n\\n";
"... (显示最后 %lld 行)\\n\\n" = "... (mostrar las últimas %lld líneas)\\n\\n";
"提示" = "Consejos";
"权限被拒绝" = "Permiso denegado";
"SSL状态" = "Estado SSL";
"选择文件" = "Seleccionar archivo";
"密码已复制到剪贴板" = "Contraseña copiada al portapapeles";
"日志查看器" = "Visor de registros";
"服务器排序规则" = "Interclase del servidor";
"已存在" = "Ya existe";
"请输入要编码的文本" = "Ingrese el texto que desea codificar";
"• 注意2：请确保填写的管理员账号具备足够的权限" = "• Nota 2: Asegúrese de que la cuenta de administrador que ingrese tenga suficientes permisos";
"日志保留天数" = "Días de retención de registros";
"导入成功" = "Importación exitosa";
"暂不支持该软件的端口修改" = "Actualmente no se admite la modificación del puerto de este software.";
"访问限制" = "Restricciones de acceso";
"高级设置" = "Configuración avanzada";
"例如: 192.168.1.100" = "Por ejemplo: 192.168.1.100";
"安装失败" = "La instalación falló";
"修复" = "Reparar";
"邮件服务" = "Servicio de correo electrónico";
"域名" = "nombre de dominio";
"✗ 配置文件语法错误" = "✗ Error de sintaxis en el archivo de configuración";
"修改 %@ %@ 的端口配置" = "Modificar la configuración del puerto de %@ %@";
"修改 %lld %lld 的端口配置" = "Modificar la configuración del puerto de %lld %lld";
"创建数据库: %@, 密码: %@" = "Crear base de datos: %@, contraseña: %@";
"创建数据库: %lld, 密码: %lld" = "Crear base de datos: %lld, contraseña: %lld";
"默认路径: %@)" = "Ruta predeterminada: %@)";
"默认路径: %lld)" = "Ruta predeterminada: %lld)";
"未安装" = "No instalado";
"批量导入站点" = "Importar sitios por lotes";
"MySQL自动重连" = "MySQL se reconecta automáticamente";
"密码验证" = "Verificación de la contraseña";
"• 浏览器将自动信任由该 CA 签发的证书" = "• Los navegadores confiarán automáticamente en los certificados firmados por esta autoridad de certificación (CA)";
"数据库文件" = "archivo de base de datos";
"需要安装 acme.sh" = "Es necesario instalar acme.sh";
"私钥文件" = "archivo de clave privada";
"扫描行为" = "comportamiento de exploración";
"数据库名称不能为空" = "El nombre de la base de datos no puede estar vacío";
"设置工作空间" = "Configurar el área de trabajo";
"正在停止中..." = "Deteniéndose...";
"关系型数据库管理系统" = "sistema de gestión de bases de datos relacionales";
"修复所有站点" = "Reparar todos los sitios";
"✓ HTTPS 连接正常" = "✓ La conexión HTTPS es normal";
"正在安装 Nginx..." = "Instalando Nginx...";
"备份旧版本失败: %@" = "Error al hacer copia de seguridad de la versión anterior: %@";
"备份旧版本失败: %lld" = "Error al hacer copia de seguridad de la versión anterior: %lld";
"压缩级别(1-9)" = "Nivel de compresión (1-9)";
"后台任务频率" = "Frecuencia de la tarea en segundo plano";
"已启用" = "Habilitado";
"系统盘" = "disco del sistema";
"绑定地址" = "Dirección de enlace";
"AOF 同步策略" = "Estrategia de sincronización AOF";
"您可以开始使用了" = "Ya estás listo para comenzar";
"if 条件已转换为 RewriteCond，请验证逻辑" = "Si la condición se ha convertido en RewriteCond, verifica la lógica";
"关系型数据库" = "base de datos relacional";
"正在导入 %@ (%@/%@)" = "Importando %@ (%@/%@)";
"正在导入 %lld (%lld/%lld)" = "Importando %lld (%lld/%lld)";
"备份失败" = "Error en la copia de seguridad";
"正在识别子目录并推导域名" = "Identificando subdirectorios y derivando nombres de dominio";
"添加新规则" = "Agregar nueva regla";
"确定要删除选中的 %@ 个 hosts 条目吗？" = "¿Estás seguro de que deseas eliminar las entradas seleccionadas de hosts %@?";
"确定要删除选中的 %lld 个 hosts 条目吗？" = "¿Estás seguro de que deseas eliminar las entradas seleccionadas de hosts %lld?";
"邮件服务器协议支持" = "Compatibilidad con protocolos de servidor de correo";
"6-32位字符" = "6-32 caracteres";
"建议使用 1024-65535 范围内的端口" = "Se recomienda utilizar puertos en el rango 1024-65535";
"⚠ %@ → %@ (非本地)" = "⚠ %@ → %@ (no local)";
"⚠ %lld → %lld (非本地)" = "⚠ %lld → %lld (no local)";
"详细信息:" = "Detalles:";
"站点设置 - %@" = "Configuración del sitio - %@";
"站点设置 - %lld" = "Configuración del sitio - %lld";
"安装模式" = "Modo de instalación";
"检测到配置冲突：同时存在完整的 Nginx 和 Apache 配置" = "Se ha detectado un conflicto de configuración: existen tanto la configuración completa de Nginx como la de Apache";
"暂无站点证书" = "Aún no hay ningún certificado para el sitio";
"所有人" = "todos";
"日志配置" = "Configuración de registros";
"日志文件大小" = "Tamaño del archivo de registro";
"域名可达性验证" = "Verificación de accesibilidad del nombre de dominio";
"GC触发除数" = "Divisor para activar la recolección de basura (GC)";
"应用" = "Aplicación";
"• 支持多个域名，用逗号分隔" = "• Admite múltiples nombres de dominio, separados por comas";
"已包含" = "Ya incluido";
"需要安装 GoAccess 才能使用高级日志分析功能" = "GoAccess es necesario para utilizar funciones avanzadas de análisis de registros";
"工作空间用于存储服务配置、数据库文件、日志和网站文件。" = "El área de trabajo se utiliza para almacenar la configuración del servicio, los archivos de base de datos, los registros y los archivos del sitio web.";
"证书申请" = "Solicitud de certificado";
"选择父目录，自动识别子目录并批量创建站点" = "Seleccione un directorio padre, identifique automáticamente los subdirectorios y cree sitios por lotes";
"服务运行正常" = "El servicio se está ejecutando normalmente";
"AI 助手设置" = "Configuración del asistente de IA";
"配置第三方大模型用于错误分析" = "Configure modelos de lenguaje de terceros (LLM) para el análisis de errores";
"AI 解释日志设置" = "Configuración del análisis de registros mediante IA";
"配置第三方大模型，用于一键分析和解释错误日志" = "Configure modelos de lenguaje de terceros (LLM) para el análisis y explicación de registros de errores con un solo clic";
"添加模型" = "Agregar modelo";
"当前使用中" = "Actualmente en uso";
"编辑模型" = "Editar modelo";
"提供商名称:" = "Nombre del proveedor:";
"API 地址:" = "URL de la API:";
"模型名称:" = "Nombre del modelo:";
"API Key:" = "Clave de la API:";
"通义千问" = "Qwen";
"智谱 GLM" = "Zhipu GLM";
"火山引擎 (Doubao)" = "Volcengine (Doubao)";
"DeepSeek (中文)" = "DeepSeek";
"自定义模型" = "Modelo personalizado";
"Log view" = "Visualización de registros";
"Port management" = "Gestión de puertos";
"模型标识，例如 qwen-plus" = "Identificador del modelo, por ejemplo, qwen-plus";
"✅ 扫描到 %d 个证书文件" = "✅ Se han escaneado %d archivos de certificado";
"\n            }\n\n            result.append(updatedConfig)\n        }\n\n        // 检查每个 Nginx 代理配置是否有对应的 Apache 后端\n        // 只添加那些缺少 Apache 后端的 Nginx 代理配置\n        for proxyConfig in nginxProxyConfigs {\n            // 查找是否有对应的 Apache 后端配置\n            let hasBackend = apacheConfigs.contains { apacheConfig in\n                // 检查域名是否匹配（考虑默认配置的特殊情况）\n                configsMatch(proxyConfig, apacheConfig)\n            }\n\n            // 只有当缺少 Apache 后端时，才添加这个 Nginx 代理配置\n            if !hasBackend {\n                var updatedConfig = proxyConfig\n                // 缺少 Apache 后端配置\n                updatedConfig.completenessStatus = .missingBackendConfig\n                // 计算应该存在的 Apache 配置路径\n                let siteName = proxyConfig.parsed.fileName.replacingOccurrences(of: " = "\n            }\n\n            result.append(updatedConfig)\n        }\n\n        // 检查每个 Nginx 代理配置是否有对应的 Apache 后端\n        // 只添加那些缺少 Apache 后端的 Nginx 代理配置\n        for proxyConfig in nginxProxyConfigs {\n            // 查找是否有对应的 Apache 后端配置\n            let hasBackend = apacheConfigs.contains { apacheConfig in\n                // 检查域名是否匹配（考虑默认配置的特殊情况）\n                configsMatch(proxyConfig, apacheConfig)\n            }\n\n            // 只有当缺少 Apache 后端时，才添加这个 Nginx 代理配置\n            if !hasBackend {\n                var updatedConfig = proxyConfig\n                // 缺少 Apache 后端配置\n                updatedConfig.completenessStatus = .missingBackendConfig\n                // 计算应该存在的 Apache 配置路径\n                let siteName = proxyConfig.parsed.fileName.replacingOccurrences(of: ";
"连接失败: \\(msg)" = "连接失败: \\(msg)";
", isOn: $appSettings.defaultSiteEnabled)\n\n            if appSettings.defaultSiteEnabled {\n                // 端口配置\n                HStack {\n                    Text(" = ", isOn: $appSettings.defaultSiteEnabled)\n\n            if appSettings.defaultSiteEnabled {\n                // 端口配置\n                HStack {\n                    Text(";
" ? 0 : -12)\n        .zIndex(2) // Ensure tabs are above the content card\n    }\n\n    // 打开数据库标签页\n    private func openDatabaseTab(name: String, isLocal: Bool, host: String, port: Int, databaseId: UUID? = nil) {\n        let tabId = " = " ? 0 : -12)\n        .zIndex(2) // Ensure tabs are above the content card\n    }\n\n    // 打开数据库标签页\n    private func openDatabaseTab(name: String, isLocal: Bool, host: String, port: Int, databaseId: UUID? = nil) {\n        let tabId = ";
")\n                .font(.system(size: 10))\n                .foregroundColor(isLocal ? .blue : .orange)\n\n            // 标签名称\n            Text(title)\n                .font(.system(size: 13, weight: isActive ? .medium : .regular))\n                .foregroundColor(isActive ? .primary : .secondary)\n                .lineLimit(1)\n\n            // 关闭按钮\n            if let onClose = onClose {\n                Button(action: onClose) {\n                    Image(systemName: " = ")\n                .font(.system(size: 10))\n                .foregroundColor(isLocal ? .blue : .orange)\n\n            // 标签名称\n            Text(title)\n                .font(.system(size: 13, weight: isActive ? .medium : .regular))\n                .foregroundColor(isActive ? .primary : .secondary)\n                .lineLimit(1)\n\n            // 关闭按钮\n            if let onClose = onClose {\n                Button(action: onClose) {\n                    Image(systemName: ";
"\n            }\n        } else {\n            // 未启用SSL：只有HTTP\n            configContent = " = "\n            }\n        } else {\n            // 未启用SSL：只有HTTP\n            configContent = ";
", // 默认 PHP 版本\n            enableSSL: parsed.hasSSL,\n            forceHttps: false,\n            expiryDate: " = ", // 默认 PHP 版本\n            enableSSL: parsed.hasSSL,\n            forceHttps: false,\n            expiryDate: ";
")\n                        .font(.system(size: 12))\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n\n            // 说明文字\n            Text(" = ")\n                        .font(.system(size: 12))\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n\n            // 说明文字\n            Text(";
"✅ mkcert 生成证书成功: \\(certPath)" = "✅ mkcert 生成证书成功: \\(certPath)";
")\n                            }\n                        }\n                        .buttonStyle(.borderedProminent)\n                        .disabled(newPort.isEmpty || !isPortValid || isPortSaving || newPort == currentPort)\n                    }\n                }\n\n                // 端口验证消息\n                if !portValidationMessage.isEmpty {\n                    HStack(spacing: 8) {\n                        Image(systemName: isPortValid ? " = ")\n                            }\n                        }\n                        .buttonStyle(.borderedProminent)\n                        .disabled(newPort.isEmpty || !isPortValid || isPortSaving || newPort == currentPort)\n                    }\n                }\n\n                // 端口验证消息\n                if !portValidationMessage.isEmpty {\n                    HStack(spacing: 8) {\n                        Image(systemName: isPortValid ? ";
") ? .red : .green)\n                }\n            }\n        }\n    }\n\n    // Web 服务器状态行\n    private var webServerStatusRow: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            HStack {\n                Text(" = ") ? .red : .green)\n                }\n            }\n        }\n    }\n\n    // Web 服务器状态行\n    private var webServerStatusRow: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            HStack {\n                Text(";
"成功: \\(successCount) 个，失败: \\(failCount) 个\\n导出目录: \\(url.path)" = "成功: \\(successCount) 个，失败: \\(failCount) 个\\n导出目录: \\(url.path)";
"\n        }\n\n        // 安装 Nginx\n        let success = await runBrewCommandWithProgress(brewPath, arguments: [" = "\n        }\n\n        // 安装 Nginx\n        let success = await runBrewCommandWithProgress(brewPath, arguments: [";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // MARK: - 基本信息\n    private var basicInfoSection: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            Text(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // MARK: - 基本信息\n    private var basicInfoSection: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            Text(";
"\n        if fm.fileExists(atPath: brewDefaultConf) {\n            // 检查是否是软链接\n            let isSymlink = (try? fm.destinationOfSymbolicLink(atPath: brewDefaultConf)) != nil\n\n            if isSymlink {\n                // 如果是软链接，直接删除（不备份）\n                try fm.removeItem(atPath: brewDefaultConf)\n            } else {\n                // 如果是普通文件，且 .bak 不存在，则重命名为 .bak\n                if !fm.fileExists(atPath: brewDefaultConfBak) {\n                    try fm.moveItem(atPath: brewDefaultConf, toPath: brewDefaultConfBak)\n                } else {\n                    // 如果 .bak 已存在，直接删除原始文件\n                    try fm.removeItem(atPath: brewDefaultConf)\n                }\n            }\n        }\n        try fm.createSymbolicLink(atPath: brewDefaultConf, withDestinationPath: wsDefaultConf)\n    }\n\n    /// 删除 nginx.conf 中的示例 server 块，避免与 default.conf 冲突\n    private func removeExampleServerBlock(at path: String) throws {\n        guard var content = try? String(contentsOfFile: path, encoding: .utf8) else {\n            return\n        }\n\n        // 查找 http 块内、include servers/*; 之前的 server 块并注释掉\n        var lines = content.components(separatedBy: .newlines)\n        var inHttpBlock = false\n        var inServerBlock = false\n        var serverBlockStart = -1\n        var braceCount = 0\n        var httpBraceCount = 0\n\n        for i in 0..<lines.count {\n            let line = lines[i].trimmingCharacters(in: .whitespaces)\n\n            // 检测 http 块\n            if line.hasPrefix(" = "\n        if fm.fileExists(atPath: brewDefaultConf) {\n            // 检查是否是软链接\n            let isSymlink = (try? fm.destinationOfSymbolicLink(atPath: brewDefaultConf)) != nil\n\n            if isSymlink {\n                // 如果是软链接，直接删除（不备份）\n                try fm.removeItem(atPath: brewDefaultConf)\n            } else {\n                // 如果是普通文件，且 .bak 不存在，则重命名为 .bak\n                if !fm.fileExists(atPath: brewDefaultConfBak) {\n                    try fm.moveItem(atPath: brewDefaultConf, toPath: brewDefaultConfBak)\n                } else {\n                    // 如果 .bak 已存在，直接删除原始文件\n                    try fm.removeItem(atPath: brewDefaultConf)\n                }\n            }\n        }\n        try fm.createSymbolicLink(atPath: brewDefaultConf, withDestinationPath: wsDefaultConf)\n    }\n\n    /// 删除 nginx.conf 中的示例 server 块，避免与 default.conf 冲突\n    private func removeExampleServerBlock(at path: String) throws {\n        guard var content = try? String(contentsOfFile: path, encoding: .utf8) else {\n            return\n        }\n\n        // 查找 http 块内、include servers/*; 之前的 server 块并注释掉\n        var lines = content.components(separatedBy: .newlines)\n        var inHttpBlock = false\n        var inServerBlock = false\n        var serverBlockStart = -1\n        var braceCount = 0\n        var httpBraceCount = 0\n\n        for i in 0..<lines.count {\n            let line = lines[i].trimmingCharacters(in: .whitespaces)\n\n            // 检测 http 块\n            if line.hasPrefix(";
"端口 \\(port)" = "端口 \\(port)";
") && !inManagedSection) {\n                continue\n            }\n            \n            // 解析 hosts 条目（支持空格和Tab分隔符）\n            let parts = trimmed.split(maxSplits: 2, whereSeparator: { $0.isWhitespace })\n            if parts.count >= 2 {\n                let ip = String(parts[0])\n                let hostname = String(parts[1])\n                var comment: String? = nil\n                \n                // 提取注释\n                if parts.count == 3 && parts[2].hasPrefix(" = ") && !inManagedSection) {\n                continue\n            }\n            \n            // 解析 hosts 条目（支持空格和Tab分隔符）\n            let parts = trimmed.split(maxSplits: 2, whereSeparator: { $0.isWhitespace })\n            if parts.count >= 2 {\n                let ip = String(parts[0])\n                let hostname = String(parts[1])\n                var comment: String? = nil\n                \n                // 提取注释\n                if parts.count == 3 && parts[2].hasPrefix(";
"]\n        brewPath = paths.first { FileManager.default.fileExists(atPath: $0) }\n    }\n\n    // MARK: - 获取已安装的 PHP 版本\n\n    func getInstalledPHPVersions() -> [String] {\n        // 从 SoftwareManager 获取已安装的 PHP 版本\n        let phpVersions = [" = "]\n        brewPath = paths.first { FileManager.default.fileExists(atPath: $0) }\n    }\n\n    // MARK: - 获取已安装的 PHP 版本\n\n    func getInstalledPHPVersions() -> [String] {\n        // 从 SoftwareManager 获取已安装的 PHP 版本\n        let phpVersions = [";
")\n                    }\n                }\n\n                // 热门请求\n                if let requests = report.requests, !requests.isEmpty {\n                    Text(" = ")\n                    }\n                }\n\n                // 热门请求\n                if let requests = report.requests, !requests.isEmpty {\n                    Text(";
"\n        }\n\n        // 扫描可用子目录\n        scanSubdirectories()\n\n        // 检查域名是否在 hosts 文件中且已启用\n        updateHosts = checkDomainsInHosts()\n    }\n\n    // 检查站点域名是否在 hosts 文件中且已启用\n    private func checkDomainsInHosts() -> Bool {\n        // 检查站点的每个域名是否在 hosts 文件中\n        for domain in site.domains {\n            // 查找该域名对应的 hosts 条目\n            if let entry = hostsManager.entries.first(where: { $0.hostname == domain }) {\n                // 如果找到且已启用，返回 true\n                if entry.isEnabled {\n                    return true\n                }\n            }\n        }\n        // 如果没有找到任何已启用的域名，返回 false\n        return false\n    }\n\n    // 表单验证\n    private var isFormValid: Bool {\n        let domainList = domains.split(separator: " = "\n        }\n\n        // 扫描可用子目录\n        scanSubdirectories()\n\n        // 检查域名是否在 hosts 文件中且已启用\n        updateHosts = checkDomainsInHosts()\n    }\n\n    // 检查站点域名是否在 hosts 文件中且已启用\n    private func checkDomainsInHosts() -> Bool {\n        // 检查站点的每个域名是否在 hosts 文件中\n        for domain in site.domains {\n            // 查找该域名对应的 hosts 条目\n            if let entry = hostsManager.entries.first(where: { $0.hostname == domain }) {\n                // 如果找到且已启用，返回 true\n                if entry.isEnabled {\n                    return true\n                }\n            }\n        }\n        // 如果没有找到任何已启用的域名，返回 false\n        return false\n    }\n\n    // 表单验证\n    private var isFormValid: Bool {\n        let domainList = domains.split(separator: ";
")\n            }\n        }\n    }\n\n    // V1 左侧菜单\n    private var v1LeftMenu: some View {\n        VStack(spacing: 0) {\n            ForEach(ConfigTab.allCases, id: \\.self) { tab in\n                HStack {\n                    Text(LocalizedStringKey(tab.rawValue))\n                        .font(.system(size: 13))\n                        .foregroundColor(selectedTab == tab ? .blue : .secondary)\n                    Spacer()\n                }\n                .padding(.horizontal, 24)\n                .padding(.vertical, 12)\n                .frame(maxWidth: .infinity) // 撑满宽度\n                .background(\n                    selectedTab == tab ? Color.white : Color.clear\n                )\n                .overlay(\n                    Rectangle()\n                        .frame(width: 3)\n                        .foregroundColor(selectedTab == tab ? .blue : .clear),\n                    alignment: .leading\n                )\n                .contentShape(Rectangle()) // 整个区域可点击\n                .onTapGesture { // 改用 onTapGesture\n                    withAnimation(.easeInOut(duration: 0.2)) {\n                        selectedTab = tab\n                    }\n                }\n            }\n            Spacer()\n        }\n        .frame(width: 180)\n        .background(Color(hex: " = ")\n            }\n        }\n    }\n\n    // V1 左侧菜单\n    private var v1LeftMenu: some View {\n        VStack(spacing: 0) {\n            ForEach(ConfigTab.allCases, id: \\.self) { tab in\n                HStack {\n                    Text(LocalizedStringKey(tab.rawValue))\n                        .font(.system(size: 13))\n                        .foregroundColor(selectedTab == tab ? .blue : .secondary)\n                    Spacer()\n                }\n                .padding(.horizontal, 24)\n                .padding(.vertical, 12)\n                .frame(maxWidth: .infinity) // 撑满宽度\n                .background(\n                    selectedTab == tab ? Color.white : Color.clear\n                )\n                .overlay(\n                    Rectangle()\n                        .frame(width: 3)\n                        .foregroundColor(selectedTab == tab ? .blue : .clear),\n                    alignment: .leading\n                )\n                .contentShape(Rectangle()) // 整个区域可点击\n                .onTapGesture { // 改用 onTapGesture\n                    withAnimation(.easeInOut(duration: 0.2)) {\n                        selectedTab = tab\n                    }\n                }\n            }\n            Spacer()\n        }\n        .frame(width: 180)\n        .background(Color(hex: ";
") }\n\n                    continuation.resume(returning: Set(modules))\n                } catch {\n                    continuation.resume(returning: [])\n                }\n            }\n        }\n    }\n\n    // MARK: - 安装扩展\n\n    func installExtension(_ ext: PHPExtensionMeta, for phpVersion: String) async -> Bool {\n        operatingExtension = ext.name\n        updateExtensionStatus(ext.name, for: phpVersion, status: .installing)\n\n        // 初始化进度\n        updateInstallProgress(ext.name, for: phpVersion, phase: " = ") }\n\n                    continuation.resume(returning: Set(modules))\n                } catch {\n                    continuation.resume(returning: [])\n                }\n            }\n        }\n    }\n\n    // MARK: - 安装扩展\n\n    func installExtension(_ ext: PHPExtensionMeta, for phpVersion: String) async -> Bool {\n        operatingExtension = ext.name\n        updateExtensionStatus(ext.name, for: phpVersion, status: .installing)\n\n        // 初始化进度\n        updateInstallProgress(ext.name, for: phpVersion, phase: ";
"✓ 端口 \\(port) 可用" = "✓ 端口 \\(port) 可用";
")\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n\n            if isExpanded {\n                Divider()\n\n                // 检查结果列表\n                VStack(spacing: 6) {\n                    ForEach(report.results) { result in\n                        let isCurrentlyChecking = report.isChecking &&\n                                                 healthChecker.currentCheckingType == result.type\n                        HealthCheckResultRow(result: result, isCurrentlyChecking: isCurrentlyChecking)\n                    }\n                }\n            }\n        }\n        .padding(10)\n            .glass(.input)\n    }\n}\n\n// MARK: - Health Check Result Row\n\nstruct HealthCheckResultRow: View {\n    let result: HealthCheckResult\n    let isCurrentlyChecking: Bool\n\n    @State private var showDetails: Bool = false\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            HStack(spacing: 10) {\n                // 状态图标\n                if isCurrentlyChecking {\n                    ProgressView()\n                        .scaleEffect(0.8)\n                        .frame(width: 16, height: 16)\n                } else if result.message == " = ")\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n\n            if isExpanded {\n                Divider()\n\n                // 检查结果列表\n                VStack(spacing: 6) {\n                    ForEach(report.results) { result in\n                        let isCurrentlyChecking = report.isChecking &&\n                                                 healthChecker.currentCheckingType == result.type\n                        HealthCheckResultRow(result: result, isCurrentlyChecking: isCurrentlyChecking)\n                    }\n                }\n            }\n        }\n        .padding(10)\n            .glass(.input)\n    }\n}\n\n// MARK: - Health Check Result Row\n\nstruct HealthCheckResultRow: View {\n    let result: HealthCheckResult\n    let isCurrentlyChecking: Bool\n\n    @State private var showDetails: Bool = false\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 6) {\n            HStack(spacing: 10) {\n                // 状态图标\n                if isCurrentlyChecking {\n                    ProgressView()\n                        .scaleEffect(0.8)\n                        .frame(width: 16, height: 16)\n                } else if result.message == ";
"\n\n        if savePanel.runModal() == .OK, let url = savePanel.url {\n            Task {\n                let result = await connector.backupDatabase(name: db.name)\n                if result.success, let sourcePath = result.filePath {\n                    try? FileManager.default.copyItem(\n                        atPath: sourcePath,\n                        toPath: url.path\n                    )\n                }\n            }\n        }\n    }\n\n    // 删除数据库\n    private func deleteDatabase(_ db: MySQLDatabaseInfo) {\n        let alert = NSAlert()\n        alert.messageText = " = "\n\n        if savePanel.runModal() == .OK, let url = savePanel.url {\n            Task {\n                let result = await connector.backupDatabase(name: db.name)\n                if result.success, let sourcePath = result.filePath {\n                    try? FileManager.default.copyItem(\n                        atPath: sourcePath,\n                        toPath: url.path\n                    )\n                }\n            }\n        }\n    }\n\n    // 删除数据库\n    private func deleteDatabase(_ db: MySQLDatabaseInfo) {\n        let alert = NSAlert()\n        alert.messageText = ";
"\n        }\n\n        let brewName = software.brewName ?? software.id\n\n        // 无论是否正在运行，都先停止服务（避免旧端口残留）\n        if software.isRunning {\n            let stopSuccess = await runBrewCommand(brewPath, arguments: [" = "\n        }\n\n        let brewName = software.brewName ?? software.id\n\n        // 无论是否正在运行，都先停止服务（避免旧端口残留）\n        if software.isRunning {\n            let stopSuccess = await runBrewCommand(brewPath, arguments: [";
"\n    }\n}\n\n// MARK: - 日志行视图\n\nstruct LogLineView: View {\n    let line: String\n    let searchText: String\n\n    var body: some View {\n        HStack(alignment: .top, spacing: 0) {\n            Text(highlightedText)\n                .foregroundColor(lineColor)\n                .padding(.horizontal, 8)\n                .padding(.vertical, 2)\n        }\n        .frame(maxWidth: .infinity, alignment: .leading)\n        .background(lineBackgroundColor)\n    }\n\n    private var highlightedText: AttributedString {\n        var attributedString = AttributedString(line)\n\n        if !searchText.isEmpty {\n            if let range = line.range(of: searchText, options: .caseInsensitive) {\n                let nsRange = NSRange(range, in: line)\n                if let attrRange = Range<AttributedString.Index>(nsRange, in: attributedString) {\n                    attributedString[attrRange].backgroundColor = .yellow\n                    attributedString[attrRange].foregroundColor = .black\n                }\n            }\n        }\n\n        return attributedString\n    }\n\n    private var lineColor: Color {\n        if line.localizedCaseInsensitiveContains(" = "\n    }\n}\n\n// MARK: - 日志行视图\n\nstruct LogLineView: View {\n    let line: String\n    let searchText: String\n\n    var body: some View {\n        HStack(alignment: .top, spacing: 0) {\n            Text(highlightedText)\n                .foregroundColor(lineColor)\n                .padding(.horizontal, 8)\n                .padding(.vertical, 2)\n        }\n        .frame(maxWidth: .infinity, alignment: .leading)\n        .background(lineBackgroundColor)\n    }\n\n    private var highlightedText: AttributedString {\n        var attributedString = AttributedString(line)\n\n        if !searchText.isEmpty {\n            if let range = line.range(of: searchText, options: .caseInsensitive) {\n                let nsRange = NSRange(range, in: line)\n                if let attrRange = Range<AttributedString.Index>(nsRange, in: attributedString) {\n                    attributedString[attrRange].backgroundColor = .yellow\n                    attributedString[attrRange].foregroundColor = .black\n                }\n            }\n        }\n\n        return attributedString\n    }\n\n    private var lineColor: Color {\n        if line.localizedCaseInsensitiveContains(";
"))\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n                    .lineLimit(1)\n            }\n\n            Spacer()\n\n            // 操作按钮（hover 时显示，在状态徽章左侧）\n            if isHovered && service.installed {\n                hoverActionButtons\n                    .transition(.opacity.combined(with: .scale(scale: 0.9)))\n            }\n\n            // 右侧：状态徽章\n            statusBadge\n        }\n        .padding(12)\n        .frame(minHeight: 64)\n        .background(\n            RoundedRectangle(cornerRadius: 8)\n                .fill(isHovered ? Color.gray.opacity(AppTokens.Opacity.light) : Color.gray.opacity(AppTokens.Opacity.ultraLight))\n        )\n        .overlay(\n            RoundedRectangle(cornerRadius: 8)\n                .stroke(Color.gray.opacity(AppTokens.Opacity.hover), lineWidth: 1)\n        )\n        .animation(.easeInOut(duration: 0.15), value: isHovered)\n        .onHover { hovering in\n            isHovered = hovering\n        }\n        .contextMenu {\n            contextMenuItems\n        }\n    }\n\n    // Hover 时显示的操作按钮（齿轮 + 停止/启动）\n    @ViewBuilder\n    private var hoverActionButtons: some View {\n        HStack(spacing: 4) {\n            // 配置按钮（齿轮图标）\n            if service.hasConfigFile {\n                ServiceIconButton(icon: " = "))\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n                    .lineLimit(1)\n            }\n\n            Spacer()\n\n            // 操作按钮（hover 时显示，在状态徽章左侧）\n            if isHovered && service.installed {\n                hoverActionButtons\n                    .transition(.opacity.combined(with: .scale(scale: 0.9)))\n            }\n\n            // 右侧：状态徽章\n            statusBadge\n        }\n        .padding(12)\n        .frame(minHeight: 64)\n        .background(\n            RoundedRectangle(cornerRadius: 8)\n                .fill(isHovered ? Color.gray.opacity(AppTokens.Opacity.light) : Color.gray.opacity(AppTokens.Opacity.ultraLight))\n        )\n        .overlay(\n            RoundedRectangle(cornerRadius: 8)\n                .stroke(Color.gray.opacity(AppTokens.Opacity.hover), lineWidth: 1)\n        )\n        .animation(.easeInOut(duration: 0.15), value: isHovered)\n        .onHover { hovering in\n            isHovered = hovering\n        }\n        .contextMenu {\n            contextMenuItems\n        }\n    }\n\n    // Hover 时显示的操作按钮（齿轮 + 停止/启动）\n    @ViewBuilder\n    private var hoverActionButtons: some View {\n        HStack(spacing: 4) {\n            // 配置按钮（齿轮图标）\n            if service.hasConfigFile {\n                ServiceIconButton(icon: ";
")\n                            .font(.system(size: 11))\n                            .foregroundColor(.blue)\n\n                        Text(candidate.directoryName)\n                            .font(.system(size: 12, weight: .medium))\n\n                        statusBadge(for: candidate)\n\n                        Spacer()\n                    }\n\n                    // 第二行：域名（可编辑）\n                    HStack(spacing: 8) {\n                        Text(" = ")\n                            .font(.system(size: 11))\n                            .foregroundColor(.blue)\n\n                        Text(candidate.directoryName)\n                            .font(.system(size: 12, weight: .medium))\n\n                        statusBadge(for: candidate)\n\n                        Spacer()\n                    }\n\n                    // 第二行：域名（可编辑）\n                    HStack(spacing: 8) {\n                        Text(";
"\n        \n        // 如果网站目录为空，或者网站目录路径不包含完整域名，则更新\n        if siteDirectory.isEmpty || !siteDirectory.contains(cleanDomain) {\n            siteDirectory = expectedPath\n        }\n        \n        // 更新完整路径\n        let siteDir = siteDirectory.hasSuffix(" = "\n        \n        // 如果网站目录为空，或者网站目录路径不包含完整域名，则更新\n        if siteDirectory.isEmpty || !siteDirectory.contains(cleanDomain) {\n            siteDirectory = expectedPath\n        }\n        \n        // 更新完整路径\n        let siteDir = siteDirectory.hasSuffix(";
")\n                .font(.system(size: 32))\n                .foregroundColor(tab.isLocal ? .blue : .orange)\n                .frame(width: 50)\n\n            // 连接详情\n            VStack(alignment: .leading, spacing: 4) {\n                Text(tab.name)\n                    .font(.system(size: 16, weight: .semibold))\n                HStack(spacing: 12) {\n                    Label(" = ")\n                .font(.system(size: 32))\n                .foregroundColor(tab.isLocal ? .blue : .orange)\n                .frame(width: 50)\n\n            // 连接详情\n            VStack(alignment: .leading, spacing: 4) {\n                Text(tab.name)\n                    .font(.system(size: 16, weight: .semibold))\n                HStack(spacing: 12) {\n                    Label(";
"当前连接" = "当前连接";
"保存配置失败: \\(error.localizedDescription)" = "保存配置失败: \\(error.localizedDescription)";
"[BatchImport] 导入站点失败: \\(candidate.siteName) - \\(error)" = "[BatchImport] 导入站点失败: \\(candidate.siteName) - \\(error)";
" {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(minWidth: 1000, minHeight: 700)\n    }\n\n    // V1 旧版布局\n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 标签页切换\n            tabBar\n\n            Divider()\n\n            // 工具栏\n            toolbar\n\n            Divider()\n\n            // 日志内容区域\n            logContentArea\n        }\n        .background(Color(NSColor.windowBackgroundColor))\n    }\n\n    // V2 玻璃拟物化布局\n    private var v2Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBarV2\n\n            Divider()\n                .background(Color.gray.opacity(0.2))\n\n            // 标签页切换\n            tabBarV2\n\n            Divider()\n                .background(Color.gray.opacity(0.2))\n\n            // 工具栏\n            toolbarV2\n\n            Divider()\n                .background(Color.gray.opacity(0.2))\n\n            // 日志内容区域\n            logContentArea\n                .background(Color.white.opacity(0.1))\n        }\n        .glass(.panel)\n        .overlay(\n            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                .stroke(Color.white.opacity(0.2), lineWidth: 1)\n        )\n    }\n\n    // MARK: - V2 标题栏\n    private var headerBarV2: some View {\n        HStack {\n            Text(LocalizedStringKey(" = " {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(minWidth: 1000, minHeight: 700)\n    }\n\n    // V1 旧版布局\n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 标签页切换\n            tabBar\n\n            Divider()\n\n            // 工具栏\n            toolbar\n\n            Divider()\n\n            // 日志内容区域\n            logContentArea\n        }\n        .background(Color(NSColor.windowBackgroundColor))\n    }\n\n    // V2 玻璃拟物化布局\n    private var v2Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBarV2\n\n            Divider()\n                .background(Color.gray.opacity(0.2))\n\n            // 标签页切换\n            tabBarV2\n\n            Divider()\n                .background(Color.gray.opacity(0.2))\n\n            // 工具栏\n            toolbarV2\n\n            Divider()\n                .background(Color.gray.opacity(0.2))\n\n            // 日志内容区域\n            logContentArea\n                .background(Color.white.opacity(0.1))\n        }\n        .glass(.panel)\n        .overlay(\n            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                .stroke(Color.white.opacity(0.2), lineWidth: 1)\n        )\n    }\n\n    // MARK: - V2 标题栏\n    private var headerBarV2: some View {\n        HStack {\n            Text(LocalizedStringKey(";
") {\n            NSApplication.shared.applicationIconImage = icon\n        }\n    }\n\n    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {\n        // 如果设置了退出时保留服务，则不退出应用，只隐藏窗口\n        return false\n    }\n}\n\nstruct AppLocaleModifier: ViewModifier {\n    @AppStorage(" = ") {\n            NSApplication.shared.applicationIconImage = icon\n        }\n    }\n\n    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {\n        // 如果设置了退出时保留服务，则不退出应用，只隐藏窗口\n        return false\n    }\n}\n\nstruct AppLocaleModifier: ViewModifier {\n    @AppStorage(";
"\n\n        // 使用正则表达式解析\n        let pattern = #" = "\n\n        // 使用正则表达式解析\n        let pattern = #";
" 或 " = " 或 ";
"\n    }\n\n    private init() {\n        loadConfig()\n    }\n\n    // MARK: - 配置加载与保存\n\n    /// 加载配置\n    func loadConfig() {\n        let fm = FileManager.default\n\n        // 确保应用支持目录存在\n        let appDir = configFilePath.deletingLastPathComponent()\n        if !fm.fileExists(atPath: appDir.path) {\n            try? fm.createDirectory(at: appDir, withIntermediateDirectories: true)\n        }\n\n        // 读取配置文件\n        if fm.fileExists(atPath: configFilePath.path),\n           let data = try? Data(contentsOf: configFilePath),\n           let savedConfig = try? JSONDecoder().decode(WorkspaceConfig.self, from: data) {\n            self.config = savedConfig\n            self.isConfigured = savedConfig.isInitialized\n        } else {\n            self.config = nil\n            self.isConfigured = false\n        }\n    }\n\n    /// 保存配置\n    private func saveConfig() {\n        guard var config = config else { return }\n        config.updatedAt = Date()\n        self.config = config\n\n        if let data = try? JSONEncoder().encode(config) {\n            try? data.write(to: configFilePath)\n        }\n    }\n\n    // MARK: - 工作空间设置\n\n    /// 设置工作空间路径\n    func setWorkspacePath(_ path: String) throws {\n        let fm = FileManager.default\n\n        // 验证路径\n        var isDir: ObjCBool = false\n        if fm.fileExists(atPath: path, isDirectory: &isDir) {\n            if !isDir.boolValue {\n                throw WorkspaceError.pathIsNotDirectory\n            }\n            // 检查写入权限\n            if !fm.isWritableFile(atPath: path) {\n                throw WorkspaceError.noWritePermission\n            }\n        } else {\n            // 尝试创建目录\n            do {\n                try fm.createDirectory(atPath: path, withIntermediateDirectories: true)\n            } catch {\n                throw WorkspaceError.cannotCreateDirectory\n            }\n        }\n\n        // 创建配置\n        config = WorkspaceConfig(rootPath: path)\n        saveConfig()\n    }\n\n    /// 使用默认路径初始化\n    func useDefaultPath() throws {\n        try setWorkspacePath(defaultWorkspacePath)\n    }\n\n    /// 获取默认路径\n    func getDefaultPath() -> String {\n        defaultWorkspacePath\n    }\n\n    // MARK: - 目录初始化\n\n    /// 初始化工作空间目录结构\n    func initializeDirectories() throws {\n        guard let config = config else {\n            throw WorkspaceError.notConfigured\n        }\n\n        let fm = FileManager.default\n\n        // 创建基础目录\n        let baseDirs = WorkspaceDirectories.getBaseDirectories(config: config)\n        for dir in baseDirs {\n            if !fm.fileExists(atPath: dir) {\n                try fm.createDirectory(atPath: dir, withIntermediateDirectories: true)\n            }\n        }\n\n        // 设置 Nginx 配置文件软链接\n        try setupNginxConfigSymlinks(config: config)\n\n        // 标记为已初始化\n        self.config?.isInitialized = true\n        self.isConfigured = true\n        saveConfig()\n    }\n\n    /// 设置 Nginx 配置文件软链接（让 Homebrew 的 Nginx 加载工作空间的配置）\n    private func setupNginxConfigSymlinks(config: WorkspaceConfig) throws {\n        let fm = FileManager.default\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n\n        // Homebrew Nginx 配置文件路径\n        let brewNginxConf = " = "\n    }\n\n    private init() {\n        loadConfig()\n    }\n\n    // MARK: - 配置加载与保存\n\n    /// 加载配置\n    func loadConfig() {\n        let fm = FileManager.default\n\n        // 确保应用支持目录存在\n        let appDir = configFilePath.deletingLastPathComponent()\n        if !fm.fileExists(atPath: appDir.path) {\n            try? fm.createDirectory(at: appDir, withIntermediateDirectories: true)\n        }\n\n        // 读取配置文件\n        if fm.fileExists(atPath: configFilePath.path),\n           let data = try? Data(contentsOf: configFilePath),\n           let savedConfig = try? JSONDecoder().decode(WorkspaceConfig.self, from: data) {\n            self.config = savedConfig\n            self.isConfigured = savedConfig.isInitialized\n        } else {\n            self.config = nil\n            self.isConfigured = false\n        }\n    }\n\n    /// 保存配置\n    private func saveConfig() {\n        guard var config = config else { return }\n        config.updatedAt = Date()\n        self.config = config\n\n        if let data = try? JSONEncoder().encode(config) {\n            try? data.write(to: configFilePath)\n        }\n    }\n\n    // MARK: - 工作空间设置\n\n    /// 设置工作空间路径\n    func setWorkspacePath(_ path: String) throws {\n        let fm = FileManager.default\n\n        // 验证路径\n        var isDir: ObjCBool = false\n        if fm.fileExists(atPath: path, isDirectory: &isDir) {\n            if !isDir.boolValue {\n                throw WorkspaceError.pathIsNotDirectory\n            }\n            // 检查写入权限\n            if !fm.isWritableFile(atPath: path) {\n                throw WorkspaceError.noWritePermission\n            }\n        } else {\n            // 尝试创建目录\n            do {\n                try fm.createDirectory(atPath: path, withIntermediateDirectories: true)\n            } catch {\n                throw WorkspaceError.cannotCreateDirectory\n            }\n        }\n\n        // 创建配置\n        config = WorkspaceConfig(rootPath: path)\n        saveConfig()\n    }\n\n    /// 使用默认路径初始化\n    func useDefaultPath() throws {\n        try setWorkspacePath(defaultWorkspacePath)\n    }\n\n    /// 获取默认路径\n    func getDefaultPath() -> String {\n        defaultWorkspacePath\n    }\n\n    // MARK: - 目录初始化\n\n    /// 初始化工作空间目录结构\n    func initializeDirectories() throws {\n        guard let config = config else {\n            throw WorkspaceError.notConfigured\n        }\n\n        let fm = FileManager.default\n\n        // 创建基础目录\n        let baseDirs = WorkspaceDirectories.getBaseDirectories(config: config)\n        for dir in baseDirs {\n            if !fm.fileExists(atPath: dir) {\n                try fm.createDirectory(atPath: dir, withIntermediateDirectories: true)\n            }\n        }\n\n        // 设置 Nginx 配置文件软链接\n        try setupNginxConfigSymlinks(config: config)\n\n        // 标记为已初始化\n        self.config?.isInitialized = true\n        self.isConfigured = true\n        saveConfig()\n    }\n\n    /// 设置 Nginx 配置文件软链接（让 Homebrew 的 Nginx 加载工作空间的配置）\n    private func setupNginxConfigSymlinks(config: WorkspaceConfig) throws {\n        let fm = FileManager.default\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n\n        // Homebrew Nginx 配置文件路径\n        let brewNginxConf = ";
"正在请求 AI 模型分析，请稍候..." = "正在请求 AI 模型分析，请稍候...";
")\n                .foregroundColor(.secondary)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n    }\n\n    // 内容视图\n    private var contentView: some View {\n        VStack(spacing: 0) {\n            // 方案选择区域\n            planSelectionArea\n\n            Divider()\n\n            // 配置列表\n            configListView\n        }\n    }\n\n    // 方案选择区域\n    private var planSelectionArea: some View {\n        HStack(spacing: 24) {\n            // 优化方案选择\n            HStack(spacing: 8) {\n                Text(" = ")\n                .foregroundColor(.secondary)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n    }\n\n    // Vista de contenido\n    private var contentView: some View {\n        VStack(spacing: 0) {\n            // Área de selección de planes\n            planSelectionArea\n\n            Divider()\n\n            // Lista de configuraciones\n            configListView\n        }\n    }\n\n    // Área de selección de planes\n    private var planSelectionArea: some View {\n        HStack(spacing: 24) {\n            // Selección de plan de optimización\n            HStack(spacing: 8) {\n                Text(";
"]\n\n                let pipe = Pipe()\n                process.standardOutput = pipe\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n\n        isApacheRunning = result\n\n        if result {\n            apachePort = await detectApachePort()\n        } else {\n            apachePort = nil\n        }\n    }\n\n    /// 检测 Apache 监听端口\n    private func detectApachePort() async -> Int? {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: " = "]\n\n                let pipe = Pipe()\n                process.standardOutput = pipe\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n\n        isApacheRunning = result\n\n        if result {\n            apachePort = await detectApachePort()\n        } else {\n            apachePort = nil\n        }\n    }\n\n    /// Detectar el puerto en el que escucha Apache\n    private func detectApachePort() async -> Int? {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: ";
")\n                    .foregroundColor(.secondary)\n                    .frame(width: 70, alignment: .trailing)\n                Text(username)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n            }\n\n            // 访问权限\n            HStack(spacing: 16) {\n                Text(" = ")\n                    .foregroundColor(.secondary)\n                    .frame(width: 70, alignment: .trailing)\n                Text(username)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n            }\n\n            // Permisos de acceso\n            HStack(spacing: 16) {\n                Text(";
")\n                    .foregroundColor(isSelected ? .blue : .secondary)\n            }\n            .buttonStyle(.plain)\n            .frame(width: 40)\n            \n            // 网站名\n            HStack(spacing: 2) {\n                Image(systemName: " = ")\n                    .foregroundColor(isSelected ? .blue : .secondary)\n            }\n            .buttonStyle(.plain)\n            .frame(width: 40)\n            \n            // Nombre del sitio web\n            HStack(spacing: 2) {\n                Image(systemName: ";
")\n                    .font(.system(size: 10, weight: .semibold))\n                    .foregroundColor(.blue)\n            }\n            .frame(width: 50, alignment: .leading)\n            .contentShape(Rectangle())\n            .onTapGesture {\n                withAnimation(.easeInOut(duration: 0.2)) {\n                    showPHPDropdown.toggle()\n                    showWebServerDropdown = false\n                }\n            }\n            .popoverWithLocale(isPresented: $showPHPDropdown, arrowEdge: .bottom) {\n                VStack(alignment: .leading, spacing: 0) {\n                    // 纯静态选项\n                    Button(action: {\n                        changePHPVersion(" = ")\n                    .font(.system(size: 10, weight: .semibold))\n                    .foregroundColor(.blue)\n            }\n            .frame(width: 50, alignment: .leading)\n            .contentShape(Rectangle())\n            .onTapGesture {\n                withAnimation(.easeInOut(duration: 0.2)) {\n                    showPHPDropdown.toggle()\n                    showWebServerDropdown = false\n                }\n            }\n            .popoverWithLocale(isPresented: $showPHPDropdown, arrowEdge: .bottom) {\n                VStack(alignment: .leading, spacing: 0) {\n                    // Opción estática pura\n                    Button(action: {\n                        changePHPVersion(";
"\n    }\n\n    /// Apache 专用安装方法，支持不同安装模式\n    func installApache(mode: WebServerInstallMode, port: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(" = "\n    }\n\n    /// Método de instalación específico para Apache, compatible con distintos modos de instalación\n    func installApache(mode: WebServerInstallMode, port: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(";
"\n            port = String(database.serverPort ?? 3306)\n        } else {\n            // 本地数据库显示默认值\n            serverAddress = " = "\n            port = String(database.serverPort ?? 3306)\n        } else {\n            // Base de datos local: mostrar valor predeterminado\n            serverAddress = ";
")\n        }\n        try content.write(toFile: path, atomically: true, encoding: .utf8)\n    }\n\n    // MARK: - MySQL 端口修改\n\n    func changeMySQLPort(_ software: SoftwareVersion, newPort: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(" = ")\n        }\n        try content.write(toFile: path, atomically: true, encoding: .utf8)\n    }\n\n    // MARK: - Modificación del puerto MySQL\n\n    func changeMySQLPort(_ software: SoftwareVersion, newPort: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(";
"))) { _ in\n            withAnimation(.easeInOut(duration: 0.1)) {\n                selectedTab = .tools\n                selectedTool = .aiSettings\n            }\n        }\n        .task(id: colorScheme) {\n            // 当外观改变时，重新加载和处理模糊背景图\n            if let originalImage = loadBackgroundImage() {\n                self.blurredBackgroundImage = await processAndBlurImage(originalImage)\n            } else {\n                self.blurredBackgroundImage = nil\n            }\n        }\n        .onAppear {\n            // 异步加载远程图片\n            Task {\n                if let url = URL(string: " = "))) { _ in\n            withAnimation(.easeInOut(duration: 0.1)) {\n                selectedTab = .tools\n                selectedTool = .aiSettings\n            }\n        }\n        .task(id: colorScheme) {\n            // Volver a cargar y procesar la imagen de fondo desenfocada al cambiar el esquema de apariencia\n            if let originalImage = loadBackgroundImage() {\n                self.blurredBackgroundImage = await processAndBlurImage(originalImage)\n            } else {\n                self.blurredBackgroundImage = nil\n            }\n        }\n        .onAppear {\n            // Cargar asincrónicamente la imagen remota\n            Task {\n                if let url = URL(string: ";
"\n        if let appleScript = NSAppleScript(source: script) {\n            appleScript.executeAndReturnError(nil)\n        }\n    }\n\n    private func openRootDirectory() {\n        // 使用默认站点主目录\n        let rootPath = appSettings.defaultSiteRoot.isEmpty ? " = "\n        if let appleScript = NSAppleScript(source: script) {\n            appleScript.executeAndReturnError(nil)\n        }\n    }\n\n    private func openRootDirectory() {\n        // Usar el directorio raíz predeterminado del sitio\n        let rootPath = appSettings.defaultSiteRoot.isEmpty ? ";
"操作: \\(log.operation)" = "Operación: \\(log.operation)";
", version: displayVer,\n                    installed: false, isDefault: false, isRunning: false,\n                    brewName: pkg, port: nil\n                ))\n            }\n        }\n\n        // 检测端口冲突\n        versions = detectPortConflicts(in: versions)\n\n        return versions.sorted { $0.version > $1.version }\n    }\n\n    /// 检测端口冲突\n    private nonisolated func detectPortConflicts(in versions: [SoftwareVersion]) -> [SoftwareVersion] {\n        var result = versions\n        var portMap: [Int: [Int]] = [:]  // port -> [indices]\n\n        // 收集所有已安装且有端口的版本\n        for (index, version) in result.enumerated() {\n            if version.installed, let port = version.port {\n                portMap[port, default: []].append(index)\n            }\n        }\n\n        // 标记冲突\n        for (port, indices) in portMap where indices.count > 1 {\n            for index in indices {\n                result[index].hasPortConflict = true\n                // 找出冲突的服务名称（除了自己）\n                let conflictingServices = indices.filter { $0 != index }.map { result[$0].displayName }\n                result[index].conflictingService = conflictingServices.joined(separator: " = ", version: displayVer,\n                    installed: false, isDefault: false, isRunning: false,\n                    brewName: pkg, port: nil\n                ))\n            }\n        }\n\n        // Detectar conflictos de puertos\n        versions = detectPortConflicts(in: versions)\n\n        return versions.sorted { $0.version > $1.version }\n    }\n\n    /// Detectar conflictos de puertos\n    private nonisolated func detectPortConflicts(in versions: [SoftwareVersion]) -> [SoftwareVersion] {\n        var result = versions\n        var portMap: [Int: [Int]] = [:]  // puerto -> [índices]\n\n        // Recopilar todas las versiones instaladas que tengan un puerto asignado\n        for (index, version) in result.enumerated() {\n            if version.installed, let port = version.port {\n                portMap[port, default: []].append(index)\n            }\n        }\n\n        // Marcar los conflictos\n        for (port, indices) in portMap where indices.count > 1 {\n            for index in indices {\n                result[index].hasPortConflict = true\n                // Identificar los nombres de los servicios en conflicto (excepto el propio)\n                let conflictingServices = indices.filter { $0 != index }.map { result[$0].displayName }\n                result[index].conflictingService = conflictingServices.joined(separator: ";
"\n    }\n\n    // Apache 配置路径\n    private var apacheConfigPath: String {\n        let workspaceManager = WorkspaceManager.shared\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.apacheConfigFile\n        }\n        return " = "\n    }\n\n    // Ruta de configuración de Apache\n    private var apacheConfigPath: String {\n        let workspaceManager = WorkspaceManager.shared\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.apacheConfigFile\n        }\n        return ";
") {\n                    if service.installed {\n                        return (service.version, service.isRunning, service.installed)\n                    }\n                }\n            }\n            // 服务加载完成但没找到 MySQL\n            return (" = ") {\n                    if service.installed {\n                        return (service.version, service.isRunning, service.installed)\n                    }\n                }\n            }\n            // Servicio cargado pero MySQL no encontrado\n            return (";
"]),\n    ]\n\n    // MARK: - PHP 配置字典\n    static let php: [String: ConfigMeta] = [\n        // ===== 资源限制 =====\n        " = "]),\n    ]\n\n    // MARK: - Diccionario de configuración de PHP\n    static let php: [String: ConfigMeta] = [\n        // ===== Límites de recursos =====\n        ";
" ? Color.white.opacity(0.2) : AppColors.border, lineWidth: 1)\n                            )\n                        }\n                        .menuStyle(.borderlessButton)\n\n                        // 自定义运行目录输入框（如果需要）\n                        if showCustomRunningDir {\n                            TextField(" = " ? Color.white.opacity(0.2) : AppColors.border, lineWidth: 1)\n                            )\n                        }\n                        .menuStyle(.borderlessButton)\n\n                        // Campo de entrada para directorio personalizado de ejecución (si procede)\n                        if showCustomRunningDir {\n                            TextField(";
") {\n                inHttpBlock = true\n                httpBraceCount = 1\n                continue\n            }\n\n            if inHttpBlock {\n                // 计算 http 块的大括号层级\n                httpBraceCount += line.filter { $0 == " = ") {\n                inHttpBlock = true\n                httpBraceCount = 1\n                continue\n            }\n\n            if inHttpBlock {\n                // Calcular el nivel de llaves dentro del bloque http\n                httpBraceCount += line.filter { $0 == ";
"确定要删除数据库「\\(db.name)」吗？此操作不可恢复！" = "¿Está seguro de que desea eliminar la base de datos «\\(db.name)»? Esta acción no se puede deshacer.";
")\n                            .font(.system(size: 14))\n                            .foregroundColor(.secondary)\n                            .frame(width: 24, height: 24)\n                            .background(Color.white.opacity(colorScheme == .dark ? 0.1 : 0.5))\n                            .cornerRadius(6)\n                    }\n                    .buttonStyle(.plain)\n                }\n                .padding(.trailing, 20)\n                .padding(.top, 12) // 向上移动，对齐原生交通灯\n                .frame(height: 38)\n                \n                // 悬浮面板内容区\n                HStack(spacing: 20) {\n                    // 侧边栏 (悬浮面板 1)\n                    SidebarViewV2(selectedTab: $selectedTab, selectedTool: $selectedTool)\n                        .frame(width: 240)\n                        .fixedSize(horizontal: true, vertical: false)\n                    \n                    // 主内容区 (悬浮面板 2)\n                    MainContentViewV2(selectedTab: selectedTab, selectedTool: $selectedTool)\n                        .frame(maxWidth: .infinity, maxHeight: .infinity)\n                }\n                .padding([.bottom, .horizontal], 20)\n            }\n            .ignoresSafeArea(edges: .top) // 让顶部控制栏突破安全区进入原生标题栏位置\n            \n            // 抖音引导浮窗（仅在简体中文环境下，且在右下角悬浮显示）\n            if showDouyinGuide && currentLanguage == .simplifiedChinese {\n                VStack {\n                    Spacer()\n                    HStack {\n                        Spacer()\n                        VStack(spacing: 8) {\n                            HStack {\n                                Spacer()\n                                Button(action: {\n                                    withAnimation {\n                                        showDouyinGuide = false\n                                        hasShownDouyinGuide = true // 永久关闭\n                                    }\n                                }) {\n                                    Image(systemName: " = ")\n                            .font(.system(size: 14))\n                            .foregroundColor(.secondary)\n                            .frame(width: 24, height: 24)\n                            .background(Color.white.opacity(colorScheme == .dark ? 0.1 : 0.5))\n                            .cornerRadius(6)\n                    }\n                    .buttonStyle(.plain)\n                }\n                .padding(.trailing, 20)\n                .padding(.top, 12) // Desplazar hacia arriba para alinearse con las luces de tráfico nativas\n                .frame(height: 38)\n                \n                // Área de contenido del panel flotante\n                HStack(spacing: 20) {\n                    // Barra lateral (panel flotante 1)\n                    SidebarViewV2(selectedTab: $selectedTab, selectedTool: $selectedTool)\n                        .frame(width: 240)\n                        .fixedSize(horizontal: true, vertical: false)\n                    \n                    // Área principal de contenido (panel flotante 2)\n                    MainContentViewV2(selectedTab: selectedTab, selectedTool: $selectedTool)\n                        .frame(maxWidth: .infinity, maxHeight: .infinity)\n                }\n                .padding([.bottom, .horizontal], 20)\n            }\n            .ignoresSafeArea(edges: .top) // Permitir que la barra superior supere la zona segura y se ubique en la posición nativa de la barra de título\n            \n            // Ventana emergente de guía Douyin (solo en entorno chino simplificado y mostrada como flotante en la esquina inferior derecha)\n            if showDouyinGuide && currentLanguage == .simplifiedChinese {\n                VStack {\n                    Spacer()\n                    HStack {\n                        Spacer()\n                        VStack(spacing: 8) {\n                            HStack {\n                                Spacer()\n                                Button(action: {\n                                    withAnimation {\n                                        showDouyinGuide = false\n                                        hasShownDouyinGuide = true // Desactivación permanente\n                                    }\n                                }) {\n                                    Image(systemName: ";
"\n        default:\n            return defaultValue\n        }\n    }\n\n    /// PHP 动态路径\n    private static func getPHPDynamicPath(key: String, config: WorkspaceConfig, version: String?, defaultValue: String) -> String {\n        switch key {\n        case " = "\n        default:\n            return defaultValue\n        }\n    }\n\n    /// Ruta dinámica de PHP\n    private static func getPHPDynamicPath(key: String, config: WorkspaceConfig, version: String?, defaultValue: String) -> String {\n        switch key {\n        case ";
"\n                        }\n                    }\n                }\n\n                // 转换 rewrite 指令\n                if trimmed.hasPrefix(" = "\n                        }\n                    }\n                }\n\n                // Convertir la directiva de reescritura\n                if trimmed.hasPrefix(";
"\n        default:\n            return defaultValue\n        }\n    }\n\n    /// Redis 动态路径\n    private static func getRedisDynamicPath(key: String, config: WorkspaceConfig, defaultValue: String) -> String {\n        switch key {\n        case " = "\n        default:\n            return defaultValue\n        }\n    }\n\n    /// Ruta dinámica de Redis\n    private static func getRedisDynamicPath(key: String, config: WorkspaceConfig, defaultValue: String) -> String {\n        switch key {\n        case ";
")\n                }\n                .buttonStyle(.bordered)\n            }\n            .padding()\n\n            Divider()\n\n            // 表头\n            databaseTableHeader\n\n            Divider()\n\n            // 数据库列表\n            ScrollView {\n                LazyVStack(spacing: 0) {\n                    ForEach(filteredDatabases) { db in\n                        RemoteDatabaseRow(\n                            database: db,\n                            isExporting: false,\n                            onExport: { exportDatabase(db) },\n                            onDelete: { deleteDatabase(db) },\n                            connector: connector\n                        )\n                        Divider()\n                    }\n                }\n            }\n\n            Divider()\n\n            // 底部工具栏\n            bottomToolbar\n        }\n        .sheetWithLocale(isPresented: $showAddDatabase) {\n            RemoteAddDatabaseSheet(connector: connector)\n        }\n    }\n\n    // 过滤后的数据库列表\n    private var filteredDatabases: [MySQLDatabaseInfo] {\n        var dbs = connector.databases\n        if hideSystemDatabases {\n            dbs = dbs.filter { !$0.isSystem }\n        }\n        if !searchText.isEmpty {\n            dbs = dbs.filter { $0.name.localizedCaseInsensitiveContains(searchText) }\n        }\n        return dbs\n    }\n\n    // 底部工具栏\n    private var bottomToolbar: some View {\n        HStack {\n            // 批量操作\n            Menu {\n                Button(" = ")\n                }\n                .buttonStyle(.bordered)\n            }\n            .padding()\n\n            Divider()\n\n            // Encabezado de la tabla\n            databaseTableHeader\n\n            Divider()\n\n            // Lista de bases de datos\n            ScrollView {\n                LazyVStack(spacing: 0) {\n                    ForEach(filteredDatabases) { db in\n                        RemoteDatabaseRow(\n                            database: db,\n                            isExporting: false,\n                            onExport: { exportDatabase(db) },\n                            onDelete: { deleteDatabase(db) },\n                            connector: connector\n                        )\n                        Divider()\n                    }\n                }\n            }\n\n            Divider()\n\n            // Barra de herramientas inferior\n            bottomToolbar\n        }\n        .sheetWithLocale(isPresented: $showAddDatabase) {\n            RemoteAddDatabaseSheet(connector: connector)\n        }\n    }\n\n    // Lista de bases de datos filtradas\n    private var filteredDatabases: [MySQLDatabaseInfo] {\n        var dbs = connector.databases\n        if hideSystemDatabases {\n            dbs = dbs.filter { !$0.isSystem }\n        }\n        if !searchText.isEmpty {\n            dbs = dbs.filter { $0.name.localizedCaseInsensitiveContains(searchText) }\n        }\n        return dbs\n    }\n\n    // Barra de herramientas inferior\n    private var bottomToolbar: some View {\n        HStack {\n            // Operaciones por lotes\n            Menu {\n                Button(";
")\n                .font(.system(size: 11, weight: .medium))\n                .foregroundColor(statusCodeColor(entry.statusCode))\n                .padding(.horizontal, 8)\n                .padding(.vertical, 4)\n                .background(statusCodeBackground(entry.statusCode))\n                .cornerRadius(4)\n                .frame(width: 60, alignment: .center)\n\n            // 大小\n            Text(formatSize(entry.size))\n                .font(.system(size: 11, design: .monospaced))\n                .foregroundColor(.secondary)\n                .frame(width: 80, alignment: .trailing)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 8)\n        .background(selectedEntry?.id == entry.id ? Color.blue.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n        .contentShape(Rectangle())\n        .onTapGesture {\n            selectedEntry = entry\n        }\n    }\n\n    // MARK: - 辅助方法\n    private func loadLogs() async {\n        isLoading = true\n        defer { isLoading = false }\n\n        do {\n            logEntries = try await logService.readAccessLog(for: site, limit: 1000)\n            filteredEntries = logEntries\n        } catch {\n            print(" = ")\n                .font(.system(size: 11, weight: .medium))\n                .foregroundColor(statusCodeColor(entry.statusCode))\n                .padding(.horizontal, 8)\n                .padding(.vertical, 4)\n                .background(statusCodeBackground(entry.statusCode))\n                .cornerRadius(4)\n                .frame(width: 60, alignment: .center)\n\n            // Tamaño\n            Text(formatSize(entry.size))\n                .font(.system(size: 11, design: .monospaced))\n                .foregroundColor(.secondary)\n                .frame(width: 80, alignment: .trailing)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 8)\n        .background(selectedEntry?.id == entry.id ? Color.blue.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n        .contentShape(Rectangle())\n        .onTapGesture {\n            selectedEntry = entry\n        }\n    }\n\n    // MARK: - Métodos auxiliares\n    private func loadLogs() async {\n        isLoading = true\n        defer { isLoading = false }\n\n        do {\n            logEntries = try await logService.readAccessLog(for: site, limit: 1000)\n            filteredEntries = logEntries\n        } catch {\n            print(";
"读取失败: \\(error.localizedDescription)" = "Error al leer: \\(error.localizedDescription)";
"\n        }\n    }\n\n    // 获取默认配置文件路径\n    private func getDefaultConfigPath() -> String? {\n        let homebrewPrefix = FileManager.default.fileExists(atPath: " = "\n        }\n    }\n\n    // Obtener la ruta predeterminada del archivo de configuración\n    private func getDefaultConfigPath() -> String? {\n        let homebrewPrefix = FileManager.default.fileExists(atPath: ";
") {\n                    rewriteConditions.append(trimmed)\n                    continue\n                }\n\n                // 处理 RewriteRule\n                if trimmed.hasPrefix(" = ") {\n                    rewriteConditions.append(trimmed)\n                    continue\n                }\n\n                // Procesar RewriteRule\n                if trimmed.hasPrefix(";
"  // 失败的服务名称，用于查看日志\n\n    let statusBarManager = StatusBarManager()\n    private var cancellables = Set<AnyCancellable>()\n\n    // MARK: - Homebrew 缓存（加快首次检测速度）\n    private nonisolated(unsafe) static var cachedBrewPath: String?\n    /// 缓存的 brew services list 输出，供 SoftwareManager 复用\n    nonisolated(unsafe) static var cachedBrewServicesOutput: String?\n    /// 缓存的已安装包列表，供 SoftwareManager 复用\n    nonisolated(unsafe) static var cachedInstalledPackages: [String: String]?\n\n    // MARK: - 服务缓存文件路径\n    private let servicesCacheURL: URL = {\n        let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!\n        let folderURL = appSupport.appendingPathComponent(" = "  // Nombre del servicio fallido, para ver los registros\n\n    let statusBarManager = StatusBarManager()\n    private var cancellables = Set<AnyCancellable>()\n\n    // MARK: - Caché de Homebrew (para acelerar la detección inicial)\n    private nonisolated(unsafe) static var cachedBrewPath: String?\n    /// Salida en caché del comando \"brew services list\", reutilizada por SoftwareManager\n    nonisolated(unsafe) static var cachedBrewServicesOutput: String?\n    /// Lista de paquetes instalados en caché, reutilizada por SoftwareManager\n    nonisolated(unsafe) static var cachedInstalledPackages: [String: String]?\n\n    // MARK: - Rutas de archivos de caché de servicios\n    private let servicesCacheURL: URL = {\n        let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!\n        let folderURL = appSupport.appendingPathComponent(";
")\n            }\n            .buttonStyle(.bordered)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 12)\n    }\n\n    // 批量启动站点\n    private func batchStartSites() {\n        Task {\n            for siteId in selectedSites {\n                if let site = siteManager.sites.first(where: { $0.id == siteId }) {\n                    try? await siteManager.startSite(site)\n                }\n            }\n        }\n    }\n\n    // 批量停止站点\n    private func batchStopSites() {\n        Task {\n            for siteId in selectedSites {\n                if let site = siteManager.sites.first(where: { $0.id == siteId }) {\n                    try? await siteManager.stopSite(site)\n                }\n            }\n        }\n    }\n\n    // 批量删除站点到回收站\n    private func batchDeleteSites() {\n        Task {\n            do {\n                try await siteManager.deleteSites(selectedSites)\n                selectedSites.removeAll()\n            } catch {\n                let alert = NSAlert()\n                alert.messageText = " = ")\n            }\n            .buttonStyle(.bordered)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 12)\n    }\n\n    // Iniciar varios sitios a la vez\n    private func batchStartSites() {\n        Task {\n            for siteId in selectedSites {\n                if let site = siteManager.sites.first(where: { $0.id == siteId }) {\n                    try? await siteManager.startSite(site)\n                }\n            }\n        }\n    }\n\n    // Detener varios sitios a la vez\n    private func batchStopSites() {\n        Task {\n            for siteId in selectedSites {\n                if let site = siteManager.sites.first(where: { $0.id == siteId }) {\n                    try? await siteManager.stopSite(site)\n                }\n            }\n        }\n    }\n\n    // Eliminar varios sitios en la papelera\n    private func batchDeleteSites() {\n        Task {\n            do {\n                try await siteManager.deleteSites(selectedSites)\n                selectedSites.removeAll()\n            } catch {\n                let alert = NSAlert()\n                alert.messageText = ";
", databaseName]\n\n                // 将 SQL 文件作为标准输入\n                guard let inputHandle = FileHandle(forReadingAtPath: filePath) else {\n                    continuation.resume(returning: false)\n                    return\n                }\n                process.standardInput = inputHandle\n                process.standardOutput = Pipe()\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n\n                    // 模拟进度更新（因为无法精确获取导入进度）\n                    DispatchQueue.main.async { progressCallback(50) }\n\n                    process.waitUntilExit()\n\n                    DispatchQueue.main.async { progressCallback(100) }\n\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // 批量删除数据库\n    func deleteDatabases(_ databaseIds: Set<UUID>) async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        // 批量删除本地数据库\n        for databaseId in databaseIds {\n            if let database = databases.first(where: { $0.id == databaseId }) {\n                if database.type == .local {\n                    _ = await deleteMySQLDatabase(name: database.name, username: database.username)\n                }\n            }\n        }\n\n        databases.removeAll { databaseIds.contains($0.id) }\n        saveDatabases()\n    }\n    \n    // 更新数据库信息\n    func updateDatabase(_ database: Database, note: String? = nil, password: String? = nil) async throws {\n        guard let index = databases.firstIndex(where: { $0.id == database.id }) else {\n            throw DatabaseError.operationFailed(" = ", databaseName]\n\n                // Usar el archivo SQL como entrada estándar\n                guard let inputHandle = FileHandle(forReadingAtPath: filePath) else {\n                    continuation.resume(returning: false)\n                    return\n                }\n                process.standardInput = inputHandle\n                process.standardOutput = Pipe()\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n\n                    // Simular actualización del progreso (ya que no se puede obtener el progreso exacto de la importación)\n                    DispatchQueue.main.async { progressCallback(50) }\n\n                    process.waitUntilExit()\n\n                    DispatchQueue.main.async { progressCallback(100) }\n\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // Eliminar varias bases de datos\n    func deleteDatabases(_ databaseIds: Set<UUID>) async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        // Eliminación por lotes de bases de datos locales\n        for databaseId in databaseIds {\n            if let database = databases.first(where: { $0.id == databaseId }) {\n                if database.type == .local {\n                    _ = await deleteMySQLDatabase(name: database.name, username: database.username)\n                }\n            }\n        }\n\n        databases.removeAll { databaseIds.contains($0.id) }\n        saveDatabases()\n    }\n    \n    // Actualizar información de la base de datos\n    func updateDatabase(_ database: Database, note: String? = nil, password: String? = nil) async throws {\n        guard let index = databases.firstIndex(where: { $0.id == database.id }) else {\n            throw DatabaseError.operationFailed(";
")\n                            Text(ip.displayName)\n                        }\n                    }\n                }\n            }\n\n            Divider()\n\n            // 刷新 IP 列表\n            Button(action: {\n                Task {\n                    await pmaService.detectLocalIPAddresses()\n                }\n            }) {\n                HStack {\n                    Image(systemName: " = ")\n                            Text(ip.displayName)\n                        }\n                    }\n                }\n            }\n\n            Divider()\n\n            // Actualizar lista de direcciones IP\n            Button(action: {\n                Task {\n                    await pmaService.detectLocalIPAddresses()\n                }\n            }) {\n                HStack {\n                    Image(systemName: ";
"\\(cert.daysRemaining) 天" = "\\(cert.daysRemaining) días";
")\n                .foregroundColor(.green)\n            Text(user.displayName)\n                .font(.system(.body, design: .monospaced))\n            Spacer()\n\n            // 修改密码按钮\n            Button(action: {\n                // TODO: 显示修改密码对话框\n            }) {\n                Image(systemName: " = ")\n                .foregroundColor(.green)\n            Text(user.displayName)\n                .font(.system(.body, design: .monospaced))\n            Spacer()\n\n            // Botón para cambiar la contraseña\n            Button(action: {\n                // TODO: Mostrar el cuadro de diálogo para cambiar la contraseña\n            }) {\n                Image(systemName: ";
"已成功导出 \\(successCount) 个数据库到: \\(url.path)" = "Se exportaron correctamente \\(successCount) bases de datos a: \\(url.path)";
",\n            isValid: true\n        ),\n    ]\n\n    var body: some View {\n        VStack {\n            // 工具栏\n            HStack {\n                Text(" = ",\n            isValid: true\n        ),\n    ]\n\n    var body: some View {\n        VStack {\n            // Barra de herramientas\n            HStack {\n                Text(";
"\n        )\n        installProgressMap[software.id] = initialProgress\n\n        // 添加到正在安装的版本列表\n        installingVersions[software.id] = InstallingVersion(\n            id: software.id,\n            name: software.name,\n            version: software.version,\n            progress: initialProgress\n        )\n\n        // 如果是旧版本，先确保 tap 已添加\n        if isLegacyVersion {\n            installProgressMap[software.id]?.currentStep = " = "\n        )\n        installProgressMap[software.id] = initialProgress\n\n        // Añadir a la lista de versiones en instalación\n        installingVersions[software.id] = InstallingVersion(\n            id: software.id,\n            name: software.name,\n            version: software.version,\n            progress: initialProgress\n        )\n\n        // Si es una versión antigua, asegurarse primero de que el tap esté añadido\n        if isLegacyVersion {\n            installProgressMap[software.id]?.currentStep = ";
"删除选中项 (\\(selectedConfigs.count))" = "Eliminar elementos seleccionados (\\(selectedConfigs.count))";
", path]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n\n        try process.run()\n        process.waitUntilExit()\n\n        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n        guard let content = String(data: data, encoding: .utf8) else {\n            return []\n        }\n\n        let lines = content.components(separatedBy: .newlines)\n            .filter { !$0.isEmpty }\n            .reversed()\n\n        var entries: [SiteErrorLogEntry] = []\n        for line in lines {\n            if let entry = parseErrorLogLine(line) {\n                entries.append(entry)\n            }\n        }\n\n        return entries\n    }\n\n    // MARK: - 解析单行错误日志\n    private func parseErrorLogLine(_ line: String) -> SiteErrorLogEntry? {\n        // Nginx Error Log Format:\n        // 2026/02/10 00:28:51 [error] 12345#0: *1 message\n\n        // Apache Error Log Format:\n        // [Sun Feb 01 23:00:27.587593 2026] [authz_core:error] [pid 2534102:tid 139652715497216] message\n\n        // 尝试解析Nginx格式\n        if let entry = parseNginxErrorLog(line) {\n            return entry\n        }\n\n        // 尝试解析Apache格式\n        if let entry = parseApacheErrorLog(line) {\n            return entry\n        }\n\n        // 如果都无法解析，返回原始行\n        return SiteErrorLogEntry(\n            timestamp: Date(),\n            level: " = ", path]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n\n        try process.run()\n        process.waitUntilExit()\n\n        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n        guard let content = String(data: data, encoding: .utf8) else {\n            return []\n        }\n\n        let lines = content.components(separatedBy: .newlines)\n            .filter { !$0.isEmpty }\n            .reversed()\n\n        var entries: [SiteErrorLogEntry] = []\n        for line in lines {\n            if let entry = parseErrorLogLine(line) {\n                entries.append(entry)\n            }\n        }\n\n        return entries\n    }\n\n    // MARK: - Analizar una línea de registro de errores\n    private func parseErrorLogLine(_ line: String) -> SiteErrorLogEntry? {\n        // Formato de registro de errores de Nginx:\n        // 2026/02/10 00:28:51 [error] 12345#0: *1 mensaje\n\n        // Formato de registro de errores de Apache:\n        // [Sun Feb 01 23:00:27.587593 2026] [authz_core:error] [pid 2534102:tid 139652715497216] mensaje\n\n        // Intentar analizar el formato de Nginx\n        if let entry = parseNginxErrorLog(line) {\n            return entry\n        }\n\n        // Intentar analizar el formato de Apache\n        if let entry = parseApacheErrorLog(line) {\n            return entry\n        }\n\n        // Si no se puede analizar ninguno, devolver la línea original\n        return SiteErrorLogEntry(\n            timestamp: Date(),\n            level: ";
") {\n            return false\n        }\n\n        // 验证 IPv4 格式\n        let parts = address.split(separator: " = ") {\n            return false\n        }\n\n        // Validar el formato IPv4\n        let parts = address.split(separator: ";
")\n                    return\n                }\n\n                // 查询 mysql.db 表获取对该数据库有权限的用户\n                let query = " = ")\n                    return\n                }\n\n                // Consultar la tabla mysql.db para obtener los usuarios con permisos sobre esta base de datos\n                let query = ";
"\n    }\n\n    // MARK: - 加载扩展列表\n\n    func loadExtensions(for phpVersion: String) async {\n        isLoading = true\n        currentOperation = " = "\n    }\n\n    // MARK: - Load Extension List\n\n    func loadExtensions(for phpVersion: String) async {\n        isLoading = true\n        currentOperation = ";
"无法修复站点 \\(site.name)：域名 \\(domain) 在 hosts 中已指向 \\(conflictingEntry.ip)，请先手动处理冲突后再重试。" = "Failed to fix site \\(site.name): domain \\(domain) is already pointing to \\(conflictingEntry.ip) in hosts. Please manually resolve the conflict before retrying.";
"\n        }\n    }\n}\n\n/// 健康检查状态\nenum HealthCheckStatus {\n    case checking\n    case success\n    case warning\n    case error\n    case skipped\n\n    var color: String {\n        switch self {\n        case .checking: return " = "\n        }\n    }\n}\n\n/// Health Check Status\nenum HealthCheckStatus {\n    case checking\n    case success\n    case warning\n    case error\n    case skipped\n\n    var color: String {\n        switch self {\n        case .checking: return ";
",\n                 webServer: WebServerType, phpVersion: String,\n                 enableSSL: Bool, forceHttps: Bool = false, sslMode: SSLMode = .nginxTermination,\n                 expiryDate: String?, note: String,\n                 createFTP: Bool = false, ftpUsername: String? = nil, ftpPassword: String? = nil,\n                 createDatabase: Bool = false, dbUsername: String? = nil, dbPassword: String? = nil) async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        // 验证输入\n        guard !domains.isEmpty else {\n            throw SiteError.invalidInput(" = ",\n                 webServer: WebServerType, phpVersion: String,\n                 enableSSL: Bool, forceHttps: Bool = false, sslMode: SSLMode = .nginxTermination,\n                 expiryDate: String?, note: String,\n                 createFTP: Bool = false, ftpUsername: String? = nil, ftpPassword: String? = nil,\n                 createDatabase: Bool = false, dbUsername: String? = nil, dbPassword: String? = nil) async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        // Validate input\n        guard !domains.isEmpty else {\n            throw SiteError.invalidInput(";
"\n    }\n\n    /// PHP-FPM 配置文件路径\n    func phpFpmConfigFile(version: String) -> String {\n        " = "\n    }\n\n    /// PHP-FPM Configuration File Path\n    func phpFpmConfigFile(version: String) -> String {\n        ";
") {\n            return NSImage(contentsOf: url)\n        }\n        return nil\n    }\n\n    // 软件图标视图\n    @ViewBuilder\n    private var softwareIconView: some View {\n        ZStack {\n            RoundedRectangle(cornerRadius: 12)\n                .fill(Color.gray.opacity(AppTokens.Opacity.light))\n                .frame(width: 48, height: 48)\n\n            if let iconName = softwareIconName, let nsImage = loadSoftwareIcon(iconName) {\n                Image(nsImage: nsImage)\n                    .resizable()\n                    .aspectRatio(contentMode: .fit)\n                    .frame(width: 32, height: 32)\n            } else {\n                // 备用方案：显示字母图标\n                RoundedRectangle(cornerRadius: 12)\n                    .fill(iconBgColor)\n                    .frame(width: 48, height: 48)\n                    .overlay(\n                        Text(iconCharacter)\n                            .font(.system(size: 24, weight: .bold))\n                            .foregroundColor(iconColor)\n                    )\n            }\n        }\n    }\n\n    private var iconBgColor: Color {\n        switch softwareName.lowercased() {\n        case let n where n.contains(" = ") {\n            return NSImage(contentsOf: url)\n        }\n        return nil\n    }\n\n    // Software Icon View\n    @ViewBuilder\n    private var softwareIconView: some View {\n        ZStack {\n            RoundedRectangle(cornerRadius: 12)\n                .fill(Color.gray.opacity(AppTokens.Opacity.light))\n                .frame(width: 48, height: 48)\n\n            if let iconName = softwareIconName, let nsImage = loadSoftwareIcon(iconName) {\n                Image(nsImage: nsImage)\n                    .resizable()\n                    .aspectRatio(contentMode: .fit)\n                    .frame(width: 32, height: 32)\n            } else {\n                // Fallback: display letter icon\n                RoundedRectangle(cornerRadius: 12)\n                    .fill(iconBgColor)\n                    .frame(width: 48, height: 48)\n                    .overlay(\n                        Text(iconCharacter)\n                            .font(.system(size: 24, weight: .bold))\n                            .foregroundColor(iconColor)\n                    )\n            }\n        }\n    }\n\n    private var iconBgColor: Color {\n        switch softwareName.lowercased() {\n        case let n where n.contains(";
"\n    }\n\n    /// 获取 Nginx 站点配置目录（优先返回存在的目录）\n    func getNginxVhostsPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.nginxVhostsPath\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        return " = "\n    }\n\n    /// Get Nginx Virtual Hosts Directory (return existing directory first)\n    func getNginxVhostsPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.nginxVhostsPath\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        return ";
") private var douyinGuideFirstLaunchTime: Double = 0\n    @State private var showDouyinGuide = false\n    @State private var isHoveringDouyin = false\n    \n    @State private var showDouyinPopover = false\n    @State private var remoteDouyinImage: NSImage? = nil\n\n    // 用于缓存处理后的模糊背景\n    @State private var blurredBackgroundImage: NSImage? = nil\n\n    private var currentLanguage: AppLanguage {\n        AppLanguage(rawValue: appLanguageRaw) ?? .english\n    }\n\n    var body: some View {\n        ZStack {\n            // 1. 全屏底层背景：使用预处理过的静态模糊图，避免实时渲染造成卡顿\n            GeometryReader { geometry in\n                if let bgImage = blurredBackgroundImage {\n                    Image(nsImage: bgImage)\n                        .resizable()\n                        .scaledToFill()\n                        .frame(width: geometry.size.width, height: geometry.size.height)\n                        .clipped()\n                } else {\n                    // Fallback background while loading or if missing\n                    Group {\n                        if colorScheme == .dark {\n                            Color(red: 0.12, green: 0.12, blue: 0.14)\n                        } else {\n                            LinearGradient(\n                                colors: [Color(red: 0.85, green: 0.9, blue: 1.0), Color(red: 0.95, green: 0.95, blue: 1.0)],\n                                startPoint: .topLeading,\n                                endPoint: .bottomTrailing\n                            )\n                        }\n                    }\n                }\n            }\n            .ignoresSafeArea()\n            \n            // 2. 全局色调微调层 (非常轻量的颜色覆盖，用于加深或提亮背景)\n            Rectangle()\n                .fill(colorScheme == .dark ? Color.black.opacity(0.4) : Color.white.opacity(0.3))\n                .ignoresSafeArea()\n            \n            // 3. 顶层内容布局\n            VStack(spacing: 0) {\n                // 顶部应用控制栏（模拟系统标题栏高度）\n                HStack(spacing: 12) {\n                    Spacer()\n\n                    Menu {\n                        ForEach(AppLanguage.allCases) { language in\n                            Button {\n                                appLanguageRaw = language.rawValue\n                            } label: {\n                                HStack {\n                                    Text(language.nativeName)\n                                    Spacer()\n                                    Text(language.englishName)\n                                        .foregroundStyle(.secondary)\n                                }\n                            }\n                        }\n                    } label: {\n                        HStack(spacing: 8) {\n                            RoundedRectangle(cornerRadius: 7)\n                                .fill(Color.white.opacity(colorScheme == .dark ? 0.12 : 0.7))\n                                .frame(width: 24, height: 24)\n                                .overlay(\n                                    Text(currentLanguage.shortName)\n                                        .font(.system(size: 10, weight: .semibold))\n                                )\n\n                            VStack(alignment: .leading, spacing: 1) {\n                                Text(currentLanguage.nativeName)\n                                    .font(.system(size: 12, weight: .semibold))\n                                    .foregroundColor(.primary)\n                                Text(currentLanguage.languageCapacityText)\n                                    .font(.system(size: 10))\n                                    .foregroundColor(.secondary)\n                            }\n\n                            Image(systemName: " = ") private var douyinGuideFirstLaunchTime: Double = 0\n    @State private var showDouyinGuide = false\n    @State private var isHoveringDouyin = false\n    \n    @State private var showDouyinPopover = false\n    @State private var remoteDouyinImage: NSImage? = nil\n\n    // Cache for processed blurred background\n    @State private var blurredBackgroundImage: NSImage? = nil\n\n    private var currentLanguage: AppLanguage {\n        AppLanguage(rawValue: appLanguageRaw) ?? .english\n    }\n\n    var body: some View {\n        ZStack {\n            // 1. Full-screen background layer: use pre-processed static blurred image to avoid stutter from real-time rendering\n            GeometryReader { geometry in\n                if let bgImage = blurredBackgroundImage {\n                    Image(nsImage: bgImage)\n                        .resizable()\n                        .scaledToFill()\n                        .frame(width: geometry.size.width, height: geometry.size.height)\n                        .clipped()\n                } else {\n                    // Fallback background while loading or if missing\n                    Group {\n                        if colorScheme == .dark {\n                            Color(red: 0.12, green: 0.12, blue: 0.14)\n                        } else {\n                            LinearGradient(\n                                colors: [Color(red: 0.85, green: 0.9, blue: 1.0), Color(red: 0.95, green: 0.95, blue: 1.0)],\n                                startPoint: .topLeading,\n                                endPoint: .bottomTrailing\n                            )\n                        }\n                    }\n                }\n            }\n            .ignoresSafeArea()\n            \n            // 2. Global tone adjustment layer (very light color overlay to subtly darken or brighten background)\n            Rectangle()\n                .fill(colorScheme == .dark ? Color.black.opacity(0.4) : Color.white.opacity(0.3))\n                .ignoresSafeArea()\n            \n            // 3. Top-level content layout\n            VStack(spacing: 0) {\n                // Top application control bar (simulating system title bar height)\n                HStack(spacing: 12) {\n                    Spacer()\n\n                    Menu {\n                        ForEach(AppLanguage.allCases) { language in\n                            Button {\n                                appLanguageRaw = language.rawValue\n                            } label: {\n                                HStack {\n                                    Text(language.nativeName)\n                                    Spacer()\n                                    Text(language.englishName)\n                                        .foregroundStyle(.secondary)\n                                }\n                            }\n                        }\n                    } label: {\n                        HStack(spacing: 8) {\n                            RoundedRectangle(cornerRadius: 7)\n                                .fill(Color.white.opacity(colorScheme == .dark ? 0.12 : 0.7))\n                                .frame(width: 24, height: 24)\n                                .overlay(\n                                    Text(currentLanguage.shortName)\n                                        .font(.system(size: 10, weight: .semibold))\n                                )\n\n                            VStack(alignment: .leading, spacing: 1) {\n                                Text(currentLanguage.nativeName)\n                                    .font(.system(size: 12, weight: .semibold))\n                                    .foregroundColor(.primary)\n                                Text(currentLanguage.languageCapacityText)\n                                    .font(.system(size: 10))\n                                    .foregroundColor(.secondary)\n                            }\n\n                            Image(systemName: ";
")\n    }()\n    \n    // JSON存储管理器（使用actor确保线程安全）\n    private let storage: JSONStorage<Site>\n\n    private enum ManagedWebService: String {\n        case nginx\n        case apache\n\n        var displayName: String {\n            switch self {\n            case .nginx:\n                return " = ")\n    }()\n    \n    // JSON Storage Manager (using actor for thread safety)\n    private let storage: JSONStorage<Site>\n\n    private enum ManagedWebService: String {\n        case nginx\n        case apache\n\n        var displayName: String {\n            switch self {\n            case .nginx:\n                return ";
", text: $ip)\n                        .macInput()\n                }\n\n                // 域名\n                FormField(" = ", text: $ip)\n                        .macInput()\n                }\n\n                // Domain name\n                FormField(";
")\n            }\n\n            var updatedContent = content\n            var hasChanges = false\n\n            // 应用所有模式\n            for pattern in patterns {\n                let newContent = pattern.replacePort(in: updatedContent, newPort: newPort)\n                if newContent != updatedContent {\n                    updatedContent = newContent\n                    hasChanges = true\n                }\n            }\n\n            // 如果有变化，保存文件\n            if hasChanges {\n                try updatedContent.write(toFile: path, atomically: true, encoding: .utf8)\n                return (true, nil)\n            }\n\n            // 没有变化，也算成功\n            return (true, nil)\n\n        } catch {\n            return (false, " = ")\n            }\n\n            var updatedContent = content\n            var hasChanges = false\n\n            // Apply all patterns\n            for pattern in patterns {\n                let newContent = pattern.replacePort(in: updatedContent, newPort: newPort)\n                if newContent != updatedContent {\n                    updatedContent = newContent\n                    hasChanges = true\n                }\n            }\n\n            // Save file if changes occurred\n            if hasChanges {\n                try updatedContent.write(toFile: path, atomically: true, encoding: .utf8)\n                return (true, nil)\n            }\n\n            // No changes — still considered successful\n            return (true, nil)\n\n        } catch {\n            return (false, ";
"站点配置变更前自动备份" = "Auto-backup before site configuration change";
"\n        while offset < packet.count && packet[offset] != 0 {\n            newPluginName.append(Character(UnicodeScalar(packet[offset])))\n            offset += 1\n        }\n        offset += 1\n\n        // 新的 scramble\n        authPluginData = Data(packet[offset...])\n        if authPluginData.last == 0 {\n            authPluginData.removeLast()\n        }\n        authPluginName = newPluginName\n\n        // 发送新的认证响应\n        let authResponse = generateAuthResponse()\n        try sendPacket(authResponse)\n\n        // 读取结果\n        try readAuthResult()\n    }\n\n    /// 处理 caching_sha2_password 额外认证\n    private func handleCachingSha2Auth(_ packet: Data) throws {\n        let status = packet[1]\n\n        if status == 0x03 {\n            // 快速认证成功，读取 OK 包\n            let okPacket = try readPacket()\n            if okPacket.count > 0 && okPacket[0] == MySQLConstants.okPacket {\n                return\n            }\n            throw MySQLError.authenticationFailed(" = "\n        while offset < packet.count && packet[offset] != 0 {\n            newPluginName.append(Character(UnicodeScalar(packet[offset])))\n            offset += 1\n        }\n        offset += 1\n\n        // New scramble\n        authPluginData = Data(packet[offset...])\n        if authPluginData.last == 0 {\n            authPluginData.removeLast()\n        }\n        authPluginName = newPluginName\n\n        // Send new authentication response\n        let authResponse = generateAuthResponse()\n        try sendPacket(authResponse)\n\n        // Read result\n        try readAuthResult()\n    }\n\n    /// Handle caching_sha2_password additional authentication\n    private func handleCachingSha2Auth(_ packet: Data) throws {\n        let status = packet[1]\n\n        if status == 0x03 {\n            // Fast authentication succeeded; read OK packet\n            let okPacket = try readPacket()\n            if okPacket.count > 0 && okPacket[0] == MySQLConstants.okPacket {\n                return\n            }\n            throw MySQLError.authenticationFailed(";
")\n        }\n\n        let packageId = software.brewName ?? software.id\n        let phpVersion = AppSettings.extractPHPVersion(from: packageId)\n\n        // 检查是否需要使用第三方 tap\n        let isLegacyVersion = legacyPHPVersions.contains(packageId)\n        let actualBrewName = isLegacyVersion ? getFullPHPBrewName(packageId) : packageId\n\n        loadingItems.insert(software.id)\n        currentOperation = " = ")\n        }\n\n        let packageId = software.brewName ?? software.id\n        let phpVersion = AppSettings.extractPHPVersion(from: packageId)\n\n        // Check whether third-party tap is required\n        let isLegacyVersion = legacyPHPVersions.contains(packageId)\n        let actualBrewName = isLegacyVersion ? getFullPHPBrewName(packageId) : packageId\n\n        loadingItems.insert(software.id)\n        currentOperation = ";
"\n        let backupPath = backupDirectory.appendingPathComponent(backupFileName).path\n\n        // 复制文件\n        try fileManager.copyItem(atPath: configPath, toPath: backupPath)\n\n        // 创建备份记录\n        let backup = ConfigBackup(\n            serviceName: serviceName,\n            originalPath: configPath,\n            backupPath: backupPath,\n            fileSize: fileSize,\n            note: note\n        )\n\n        // 保存记录\n        var records = loadBackupRecords()\n        records.append(backup)\n        saveBackupRecords(records)\n\n        // 清理旧备份\n        cleanupOldBackups(for: serviceName)\n\n        return backup\n    }\n\n    /// 创建站点配置文件备份\n    func createSiteBackup(siteId: UUID, siteName: String, serviceName: String, configPath: String, note: String? = nil) throws -> ConfigBackup {\n        let fileManager = FileManager.default\n\n        // 检查源文件是否存在\n        guard fileManager.fileExists(atPath: configPath) else {\n            throw BackupError.sourceFileNotFound\n        }\n\n        // 获取文件大小\n        let attributes = try fileManager.attributesOfItem(atPath: configPath)\n        let fileSize = attributes[.size] as? Int64 ?? 0\n\n        // 生成备份文件名：站点名_服务名_时间戳.bak\n        let timestamp = ISO8601DateFormatter().string(from: Date())\n            .replacingOccurrences(of: " = "\n        let backupPath = backupDirectory.appendingPathComponent(backupFileName).path\n\n        // Copy file\n        try fileManager.copyItem(atPath: configPath, toPath: backupPath)\n\n        // Create backup record\n        let backup = ConfigBackup(\n            serviceName: serviceName,\n            originalPath: configPath,\n            backupPath: backupPath,\n            fileSize: fileSize,\n            note: note\n        )\n\n        // Save record\n        var records = loadBackupRecords()\n        records.append(backup)\n        saveBackupRecords(records)\n\n        // Clean up old backups\n        cleanupOldBackups(for: serviceName)\n\n        return backup\n    }\n\n    /// Create site configuration file backup\n    func createSiteBackup(siteId: UUID, siteName: String, serviceName: String, configPath: String, note: String? = nil) throws -> ConfigBackup {\n        let fileManager = FileManager.default\n\n        // Verify source file exists\n        guard fileManager.fileExists(atPath: configPath) else {\n            throw BackupError.sourceFileNotFound\n        }\n\n        // Get file size\n        let attributes = try fileManager.attributesOfItem(atPath: configPath)\n        let fileSize = attributes[.size] as? Int64 ?? 0\n\n        // Generate backup filename: siteName_serviceName_timestamp.bak\n        let timestamp = ISO8601DateFormatter().string(from: Date())\n            .replacingOccurrences(of: ";
"\n        let result = await executeSQL(sql)\n\n        if result.success {\n            await fetchUsers()\n        } else {\n            errorMessage = result.error\n        }\n        return result.success\n    }\n\n    /// 删除用户\n    func dropUser(username: String, host: String) async -> Bool {\n        let sql = " = "\n        let result = await executeSQL(sql)\n\n        if result.success {\n            await fetchUsers()\n        } else {\n            errorMessage = result.error\n        }\n        return result.success\n    }\n\n    /// Drop user\n    func dropUser(username: String, host: String) async -> Bool {\n        let sql = ";
") {\n                            continue\n                        }\n\n                        // 推导域名\n                        let (inferredDomain, domainStatus) = self.inferDomainFromDirectoryName(item)\n\n                        // 推导运行目录\n                        var documentRoot = itemPath\n                        var documentRootExists = true\n\n                        if let subPath = documentRootSubPath, !subPath.isEmpty {\n                            let fullDocRoot = " = ") {\n                            continue\n                        }\n\n                        // Infer domain\n                        let (inferredDomain, domainStatus) = self.inferDomainFromDirectoryName(item)\n\n                        // Infer document root\n                        var documentRoot = itemPath\n                        var documentRootExists = true\n\n                        if let subPath = documentRootSubPath, !subPath.isEmpty {\n                            let fullDocRoot = ";
") 语法检查通过" = ") Syntax check passed";
"\n        try content.write(toFile: tempPath, atomically: true, encoding: .utf8)\n\n        // 使用 AppleScript 和管理员权限复制文件\n        let script = " = "\n        try content.write(toFile: tempPath, atomically: true, encoding: .utf8)\n\n        // Use AppleScript with admin privileges to copy file\n        let script = ";
")\n                .font(.system(size: 20, weight: .semibold))\n\n            Spacer()\n\n            // 过滤选项\n            Picker(" = ")\n                .font(.system(size: 20, weight: .semibold))\n\n            Spacer()\n\n            // Filter options\n            Picker(";
" }\n\n    /// 日志目录\n    var logsPath: String { " = " }\n\n    /// Directorio de registros\n    var logsPath: String { ";
") {\n                    break\n                }\n\n                // 检测 server 块开始\n                if !inServerBlock && line.hasPrefix(" = ") {\n                    break\n                }\n\n                // Detectar el inicio del bloque \"server\"\n                if !inServerBlock && line.hasPrefix(";
") {\n                    return .stopped\n                }\n            }\n        }\n        return .stopped\n    }\n\n    /// 检测服务是否安装并获取信息\n    private nonisolated func detectService(name: String, brewName: String, port: Int) -> ServiceStatus? {\n        // 检查服务是否通过 Homebrew 安装\n        guard isBrewPackageInstalled(brewName) else {\n            return nil\n        }\n\n        // 获取版本信息\n        let version = getBrewPackageVersion(brewName)\n\n        // 获取运行状态\n        let state = getServiceState(brewName)\n\n        // 获取配置文件路径\n        let configPath = findConfigPath(for: name, version: version)\n\n        return ServiceStatus(\n            name: name,\n            version: version,\n            state: state,\n            port: port,\n            installed: true,\n            configPath: configPath,\n            brewName: brewName\n        )\n    }\n\n    /// 查找服务配置文件路径\n    private nonisolated func findConfigPath(for serviceName: String, version: String) -> String? {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        let configPaths: [String]\n\n        switch serviceName {\n        case " = ") {\n                    return .stopped\n                }\n            }\n        }\n        return .stopped\n    }\n\n    /// Detectar si el servicio está instalado y obtener su información\n    private nonisolated func detectService(name: String, brewName: String, port: Int) -> ServiceStatus? {\n        // Verificar si el servicio está instalado mediante Homebrew\n        guard isBrewPackageInstalled(brewName) else {\n            return nil\n        }\n\n        // Obtener la información de la versión\n        let version = getBrewPackageVersion(brewName)\n\n        // Obtener el estado de ejecución\n        let state = getServiceState(brewName)\n\n        // Obtener la ruta del archivo de configuración\n        let configPath = findConfigPath(for: name, version: version)\n\n        return ServiceStatus(\n            name: name,\n            version: version,\n            state: state,\n            port: port,\n            installed: true,\n            configPath: configPath,\n            brewName: brewName\n        )\n    }\n\n    /// Buscar la ruta del archivo de configuración del servicio\n    private nonisolated func findConfigPath(for serviceName: String, version: String) -> String? {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        let configPaths: [String]\n\n        switch serviceName {\n        case ";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 表单内容\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // 数据库信息\n                FormField(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // Contenido del formulario\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // Información de la base de datos\n                FormField(";
"\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 表单内容\n            formContent\n\n            Divider()\n\n            // 底部按钮\n            bottomButtons\n        }\n        .frame(width: 500, height: 400)\n        .alert(" = "\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Barra de título\n            headerBar\n\n            Divider()\n\n            // Contenido del formulario\n            formContent\n\n            Divider()\n\n            // Botones inferiores\n            bottomButtons\n        }\n        .frame(width: 500, height: 400)\n        .alert(";
"\n        isPortValid = true\n\n        // 检查端口格式\n        guard let port = Int(newPort), port > 0, port <= 65535 else {\n            portValidationMessage = " = "\n        isPortValid = true\n\n        // Verificar el formato del puerto\n        guard let port = Int(newPort), port > 0, port <= 65535 else {\n            portValidationMessage = ";
" ? Color.white.opacity(0.2) : AppColors.border, lineWidth: 1)\n                            )\n                        }\n                        .menuStyle(.borderlessButton)\n\n                        // 自定义运行目录输入框\n                        if showCustomRunningDir {\n                            TextField(" = " ? Color.white.opacity(0.2) : AppColors.border, lineWidth: 1)\n                            )\n                        }\n                        .menuStyle(.borderlessButton)\n\n                        // Campo de entrada personalizado para el directorio de ejecución\n                        if showCustomRunningDir {\n                            TextField(";
"否" = "No";
"\n        }\n\n        return jsonString\n    }\n    \n    // 复制错误页面目录和默认首页到站点目录\n    private func copyErrorPagesToSite(documentRoot: String) async throws {\n        let fileManager = FileManager.default\n        \n        // 获取 _errorpage_ 目录路径（应该打包在 App Resources 里，或者从 WorkspaceConfig 读取）\n        // 我们尝试从 Bundle 中寻找，如果找不到再尝试当前用户目录下的默认路径\n        var errorPageSourceDir = " = "\n        }\n\n        return jsonString\n    }\n    \n    // Copiar los directorios de páginas de error y la página de inicio predeterminada al directorio del sitio\n    private func copyErrorPagesToSite(documentRoot: String) async throws {\n        let fileManager = FileManager.default\n        \n        // Obtener la ruta del directorio \"_errorpage_\" (debería estar empaquetado en los recursos de la aplicación o leerse desde WorkspaceConfig)\n        // Intentamos buscarlo primero en el Bundle; si no se encuentra, probamos con la ruta predeterminada en el directorio del usuario actual\n        var errorPageSourceDir = ";
"\n    @State private var showRemoteDatabaseSheet = false\n    @State private var showAddDatabaseSheet = false\n    @State private var showRootPasswordSheet = false\n    @State private var showEditRemoteDatabaseSheet = false\n    @State private var selectedDatabases: Set<UUID> = []\n    @State private var selectedVersion: DatabaseVersion?\n    @State private var currentPage = 1\n    @State private var selectedBatchOperation: BatchOperation?\n\n    @State private var editingDatabase: Database?\n    @State private var showRootPassword = false  // 是否显示root密码\n    @State private var rootPassword: String? = nil  // MySQL root密码（nil表示未检测到）\n    @State private var actualMySQLPort: Int? = nil  // MySQL实际监听的端口\n    @State private var isDetectingPort = false  // 是否正在检测端口\n    @State private var isDetectingPassword = false  // 是否正在检测密码\n    @State private var passwordDetectionStatus = " = "\n    @State private var showRemoteDatabaseSheet = false\n    @State private var showAddDatabaseSheet = false\n    @State private var showRootPasswordSheet = false\n    @State private var showEditRemoteDatabaseSheet = false\n    @State private var selectedDatabases: Set<UUID> = []\n    @State private var selectedVersion: DatabaseVersion?\n    @State private var currentPage = 1\n    @State private var selectedBatchOperation: BatchOperation?\n\n    @State private var editingDatabase: Database?\n    @State private var showRootPassword = false  // ¿Mostrar la contraseña root?\n    @State private var rootPassword: String? = nil  // Contraseña root de MySQL (nil indica que no se ha detectado)\n    @State private var actualMySQLPort: Int? = nil  // Puerto real en el que escucha MySQL\n    @State private var isDetectingPort = false  // ¿Se está detectando el puerto?\n    @State private var isDetectingPassword = false  // ¿Se está detectando la contraseña?\n    @State private var passwordDetectionStatus = ";
"]),\n\n        // ===== 输出控制 =====\n        " = "]),\n\n        // ===== Control de salida =====\n        ";
")/\n                </VirtualHost>\n\n                # HTTPS VirtualHost\n                # Apache 后端端口：\\(httpsPort)\n                <VirtualHost *:\\(httpsPort)>\n                    ServerName \\(domains.first ?? " = ")/\n                </VirtualHost>\n\n                # VirtualHost HTTPS\n                # Puerto de backend de Apache: \\(httpsPort)\n                <VirtualHost *:\\(httpsPort)>\n                    ServerName \\(domains.first ?? ";
"\n        _ = await executeSQL(deleteSql2)\n\n        // 2. 更新 mysql.user 表\n        let sql1 = " = "\n        _ = await executeSQL(deleteSql2)\n\n        // 2. Actualizar la tabla mysql.user\n        let sql1 = ";
"\\(certificate.daysRemaining) 天" = "\\(certificate.daysRemaining) días";
" }\n\n    /// Redis 配置文件\n    var redisConfigFile: String { " = " }\n\n    /// Archivo de configuración de Redis\n    var redisConfigFile: String { ";
", Color(red: 0.87, green: 0.27, blue: 0.27)) // Apache红色\n        } else if name.contains(" = ", Color(red: 0.87, green: 0.27, blue: 0.27)) // Rojo de Apache\n        } else if name.contains(";
"  // 用户主机\n    var backupCount: Int = 0  // 备份数量\n    var location: String = " = "  // Host del usuario\n    var backupCount: Int = 0  // Cantidad de copias de seguridad\n    var location: String = ";
"\n            isPortValid = false\n            return\n        }\n\n        // 检查是否与当前端口相同\n        if newPort == currentPort {\n            portValidationMessage = " = "\n            isPortValid = false\n            return\n        }\n\n        // Verificar si coincide con el puerto actual\n        if newPort == currentPort {\n            portValidationMessage = ";
"\n        let length = Int.random(in: 6...8)\n        return String((0..<length).map { _ in letters.randomElement()! })\n    }\n\n    // 生成随机密码（数字+字母）\n    private func generateRandomPassword() -> String {\n        let characters = " = "\n        let length = Int.random(in: 6...8)\n        return String((0..<length).map { _ in letters.randomElement()! })\n    }\n\n    // Generar una contraseña aleatoria (números y letras)\n    private func generateRandomPassword() -> String {\n        let characters = ";
")\n        \n        // 检查源目录是否存在\n        var isDirectory: ObjCBool = false\n        guard fileManager.fileExists(atPath: errorPageSourceDir, isDirectory: &isDirectory),\n              isDirectory.boolValue else {\n            print(" = ")\n        \n        // Verificar si el directorio de origen existe\n        var isDirectory: ObjCBool = false\n        guard fileManager.fileExists(atPath: errorPageSourceDir, isDirectory: &isDirectory),\n              isDirectory.boolValue else {\n            print(";
"]\n                process.standardOutput = Pipe()\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    /// 禁用默认站点（删除配置文件）\n    func disableDefaultSite() async -> Bool {\n        // 优先使用工作空间路径\n        let workspaceManager = WorkspaceManager.shared\n        let configPath: String\n        if let wsConfig = workspaceManager.config, wsConfig.isInitialized {\n            configPath = " = "]\n                process.standardOutput = Pipe()\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    /// Desactivar el sitio predeterminado (eliminar el archivo de configuración)\n    func disableDefaultSite() async -> Bool {\n        // Priorizar la ruta del espacio de trabajo\n        let workspaceManager = WorkspaceManager.shared\n        let configPath: String\n        if let wsConfig = workspaceManager.config, wsConfig.isInitialized {\n            configPath = ";
"\n        var suggestions: [String] = []\n\n        // 建议1：停止占用端口的进程\n        suggestions.append(" = "\n        var suggestions: [String] = []\n\n        // Sugerencia 1: Detener el proceso que está utilizando el puerto\n        suggestions.append(";
"  // 运行目录\n    @State private var previousDomain = " = "  // Directorio de ejecución\n    @State private var previousDomain = ";
",\n            note: note,\n            version: DatabaseVersion(version: versionToUse, port: portInt, isRunning: true),\n            createdAt: Date(),\n            serverAddress: serverAddress,\n            serverPort: portInt\n        )\n\n        databases.append(remoteDatabase)\n\n        // 立即保存（关键操作）\n        try await forceSaveDatabases()\n    }\n\n    // 更新远程数据库连接信息\n    func updateRemoteDatabase(id: UUID, connectionName: String, serverAddress: String, port: String,\n                              username: String, password: String, note: String, skipConnectionTest: Bool = false) async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        // 验证输入\n        guard !connectionName.isEmpty else {\n            throw DatabaseError.invalidInput(" = ",\n            note: note,\n            version: DatabaseVersion(version: versionToUse, port: portInt, isRunning: true),\n            createdAt: Date(),\n            serverAddress: serverAddress,\n            serverPort: portInt\n        )\n\n        databases.append(remoteDatabase)\n\n        // Guardar inmediatamente (operación crítica)\n        try await forceSaveDatabases()\n    }\n\n    // Actualizar la información de conexión de una base de datos remota\n    func updateRemoteDatabase(id: UUID, connectionName: String, serverAddress: String, port: String,\n                              username: String, password: String, note: String, skipConnectionTest: Bool = false) async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        // Validar entradas\n        guard !connectionName.isEmpty else {\n            throw DatabaseError.invalidInput(";
"\n    }\n\n    /// 获取 Nginx 配置文件路径\n    func getNginxConfigPath() -> String {\n        if let config = config, config.isInitialized {\n            return config.nginxConfigFile\n        }\n        return " = "\n    }\n\n    /// Obtener la ruta del archivo de configuración de Nginx\n    func getNginxConfigPath() -> String {\n        if let config = config, config.isInitialized {\n            return config.nginxConfigFile\n        }\n        return ";
"显示最近 \\(logLines.count) 条日志" = "Mostrando los últimos \\(logLines.count) registros de bitácora";
"\n            }\n\n            return (true, output, nil)\n        } catch {\n            return (false, nil, error.localizedDescription)\n        }\n    }\n\n    /// 执行 SQL 查询（优先使用原生客户端）\n    private func executeSQL(_ sql: String) async -> (success: Bool, output: String?, error: String?) {\n        // 优先尝试原生客户端\n        let nativeResult = await executeWithNativeClient(sql)\n        if nativeResult.success {\n            return nativeResult\n        }\n\n        // 如果原生客户端失败，尝试命令行客户端\n        let cliResult = await executeSQLWithCLI(sql)\n        if cliResult.success {\n            return cliResult\n        }\n\n        // 两者都失败，返回原生客户端的错误（通常更有意义）\n        return nativeResult\n    }\n\n    // MARK: - 数据库管理\n\n    /// 获取数据库列表（包含详细信息）\n    func fetchDatabases() async {\n        isLoading = true\n        errorMessage = nil\n\n        // 使用 information_schema 获取详细信息\n        let sql = " = "\n            }\n\n            return (true, output, nil)\n        } catch {\n            return (false, nil, error.localizedDescription)\n        }\n    }\n\n    /// Ejecutar una consulta SQL (preferentemente mediante el cliente nativo)\n    private func executeSQL(_ sql: String) async -> (success: Bool, output: String?, error: String?) {\n        // Intentar primero con el cliente nativo\n        let nativeResult = await executeWithNativeClient(sql)\n        if nativeResult.success {\n            return nativeResult\n        }\n\n        // Si falla el cliente nativo, intentar con el cliente de línea de comandos\n        let cliResult = await executeSQLWithCLI(sql)\n        if cliResult.success {\n            return cliResult\n        }\n\n        // Si ambos fallan, devolver el error del cliente nativo (normalmente más significativo)\n        return nativeResult\n    }\n\n    // MARK: - Gestión de bases de datos\n\n    /// Obtener la lista de bases de datos (con información detallada)\n    func fetchDatabases() async {\n        isLoading = true\n        errorMessage = nil\n\n        // Usar information_schema para obtener información detallada\n        let sql = ";
" && !software.installed {\n            await MainActor.run {\n                showApacheInstallSheet = true\n            }\n            return\n        }\n\n        // PureFTP 特殊处理：显示安装选项\n        if software.id == " = " && !software.installed {\n            await MainActor.run {\n                showApacheInstallSheet = true\n            }\n            return\n        }\n\n        // Tratamiento especial para PureFTP: mostrar opciones de instalación\n        if software.id == ";
"⚠️ 证书目录不存在: \\(sslPath)" = "⚠️ El directorio de certificados no existe: \\(sslPath)";
"\n        } else {\n            // 标准端口，不需要指定\n            urlString = " = "\n        } else {\n            // Puerto estándar, no es necesario especificarlo\n            urlString = ";
"未找到 Homebrew，无法重载 \\(webServerDisplayName(webServer))。" = "Homebrew no encontrado; no se puede recargar \\(webServerDisplayName(webServer)).";
"\n                }\n                \n                // 在 appState.services 中查找服务\n                if let service = appState.services.first(where: { \n                    let name = $0.name.lowercased()\n                    return name == serviceName.lowercased() || \n                           (webServer == .apache && (name == " = "\n                }\n                \n                // Buscar el servicio en appState.services\n                if let service = appState.services.first(where: { \n                    let name = $0.name.lowercased()\n                    return name == serviceName.lowercased() || \n                           (webServer == .apache && (name == ";
"\n    }\n\n    /// 获取配置完整性描述\n    var completenessDescription: String? {\n        switch completenessStatus {\n        case .complete:\n            return nil\n        case .needsProxyConfig:\n            return " = "\n    }\n\n    /// Obtener la descripción del estado de integridad de la configuración\n    var completenessDescription: String? {\n        switch completenessStatus {\n        case .complete:\n            return nil\n        case .needsProxyConfig:\n            return ";
"\n        }\n        \n        // 在 appState.services 中查找服务\n        if let service = appState.services.first(where: { \n            let name = $0.name.lowercased()\n            return name == serviceName.lowercased() || \n                   (webServer == .apache && (name == " = "\n        }\n        \n        // Buscar el servicio en appState.services\n        if let service = appState.services.first(where: { \n            let name = $0.name.lowercased()\n            return name == serviceName.lowercased() || \n                   (webServer == .apache && (name == ";
" }\n        let formatter = ByteCountFormatter()\n        formatter.allowedUnits = [.useKB, .useMB, .useGB]\n        formatter.countStyle = .file\n        return formatter.string(fromByteCount: bytes)\n    }\n\n    // MARK: - 辅助方法与计算属性\n    \n    // 证书状态文字\n    private var sslStatusText: LocalizedStringKey {\n        if site.sslEnabled {\n            if site.expiryDate != nil {\n                return " = " }\n        let formatter = ByteCountFormatter()\n        formatter.allowedUnits = [.useKB, .useMB, .useGB]\n        formatter.countStyle = .file\n        return formatter.string(fromByteCount: bytes)\n    }\n\n    // MARK: - Métodos auxiliares y propiedades calculadas\n    \n    // Texto del estado del certificado SSL\n    private var sslStatusText: LocalizedStringKey {\n        if site.sslEnabled {\n            if site.expiryDate != nil {\n                return ";
"使用 \\(suggested)" = "Usando \\(suggested)";
", path]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n\n        try process.run()\n        process.waitUntilExit()\n\n        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n        guard let content = String(data: data, encoding: .utf8) else {\n            return []\n        }\n\n        let lines = content.components(separatedBy: .newlines)\n            .filter { !$0.isEmpty }\n            .reversed() // 最新的日志在前面\n\n        var entries: [LogEntry] = []\n        for line in lines {\n            if let entry = parseAccessLogLine(line) {\n                entries.append(entry)\n            }\n        }\n\n        return entries\n    }\n\n    // MARK: - 解析单行访问日志\n    private func parseAccessLogLine(_ line: String) -> LogEntry? {\n        // Nginx/Apache Combined Log Format:\n        // 127.0.0.1 - - [10/Feb/2026:00:28:51 +0800] " = ", path]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n\n        try process.run()\n        process.waitUntilExit()\n\n        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n        guard let content = String(data: data, encoding: .utf8) else {\n            return []\n        }\n\n        let lines = content.components(separatedBy: .newlines)\n            .filter { !$0.isEmpty }\n            .reversed() // Los registros más recientes aparecen primero\n\n        var entries: [LogEntry] = []\n        for line in lines {\n            if let entry = parseAccessLogLine(line) {\n                entries.append(entry)\n            }\n        }\n\n        return entries\n    }\n\n    // MARK: - Analizar una línea individual del registro de accesos\n    private func parseAccessLogLine(_ line: String) -> LogEntry? {\n        // Formato combinado de registro de accesos de Nginx/Apache:\n        // 127.0.0.1 - - [10/Feb/2026:00:28:51 +0800] ";
")\n                    .foregroundColor(.secondary)\n            }\n        }\n    }\n\n    // MARK: - 站点目录行\n    private var siteRootRow: some View {\n        HStack {\n            Text(" = ")\n                    .foregroundColor(.secondary)\n            }\n        }\n    }\n\n    // MARK: - Fila del directorio raíz del sitio\n    private var siteRootRow: some View {\n        HStack {\n            Text(";
"\\(inputCharCount) 字符" = "\\(inputCharCount) caracteres";
"\n    // 需要使用第三方 tap 的 PHP 版本（Homebrew 官方不再支持的旧版本）\n    private let legacyPHPVersions = [" = "\n    // Versiones antiguas de PHP que requieren un tap de terceros (no soportadas oficialmente por Homebrew)\n    private let legacyPHPVersions = [";
")\n    }\n\n    // 检查端口冲突（安装时）\n    private func checkPortConflict(port: Int, excluding: String, category: ComponentCategory) -> String? {\n        let allSoftware: [SoftwareVersion]\n        switch category {\n        case .all: allSoftware = phpVersions + webServers + databases + caches + tools\n        case .webServer: allSoftware = webServers\n        case .php: allSoftware = phpVersions\n        case .database: allSoftware = databases + caches\n        case .tool: allSoftware = tools\n        }\n\n        for sw in allSoftware {\n            if sw.id != excluding && sw.installed && sw.isRunning && sw.port == port {\n                return sw.name\n            }\n        }\n        return nil\n    }\n\n    // 检查运行中的端口冲突\n    private func checkRunningPortConflict(port: Int, excluding: String) -> String? {\n        let allSoftware = phpVersions + webServers + databases + caches + tools\n        for sw in allSoftware {\n            if sw.id != excluding && sw.isRunning && sw.port == port {\n                return sw.name\n            }\n        }\n        return nil\n    }\n\n    // MARK: - PHP 端口冲突处理\n\n    /// 检查 PHP 安装时的端口冲突\n    func checkPHPPortConflict(_ software: SoftwareVersion) -> PHPPortConflictInfo? {\n        guard software.name.lowercased().contains(" = ")\n    }\n\n    // 检查端口冲突（安装时）\n    private func checkPortConflict(port: Int, excluding: String, category: ComponentCategory) -> String? {\n        let allSoftware: [SoftwareVersion]\n        switch category {\n        case .all: allSoftware = phpVersions + webServers + databases + caches + tools\n        case .webServer: allSoftware = webServers\n        case .php: allSoftware = phpVersions\n        case .database: allSoftware = databases + caches\n        case .tool: allSoftware = tools\n        }\n\n        for sw in allSoftware {\n            if sw.id != excluding && sw.installed && sw.isRunning && sw.port == port {\n                return sw.name\n            }\n        }\n        return nil\n    }\n\n    // 检查运行中的端口冲突\n    private func checkRunningPortConflict(port: Int, excluding: String) -> String? {\n        let allSoftware = phpVersions + webServers + databases + caches + tools\n        for sw in allSoftware {\n            if sw.id != excluding && sw.isRunning && sw.port == port {\n                return sw.name\n            }\n        }\n        return nil\n    }\n\n    // MARK: - PHP 端口冲突处理\n\n    /// 检查 PHP 安装时的端口冲突\n    func checkPHPPortConflict(_ software: SoftwareVersion) -> PHPPortConflictInfo? {\n        guard software.name.lowercased().contains(";
"⚠ \\(domain) → \\(entry.ip) (非本地)" = "⚠ \\(domain) → \\(entry.ip) (非本地)";
")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n            .frame(height: 50)\n            \n            Divider()\n            \n            // 主体内容\n            HStack(spacing: 0) {\n                // 左侧导航栏\n                v1LeftMenu\n                \n                // 右侧内容区域\n                ScrollView {\n                    configContent\n                        .padding(32)\n                }\n                .frame(maxWidth: .infinity)\n                .background(Color.white)\n            }\n        }\n        .background(Color(NSColor.windowBackgroundColor))\n    }\n    \n    // V2 玻璃拟物化布局\n    private var v2Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            HStack {\n                Text(LocalizedStringKey(" = ")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n            .frame(height: 50)\n            \n            Divider()\n            \n            // 主体内容\n            HStack(spacing: 0) {\n                // 左侧导航栏\n                v1LeftMenu\n                \n                // 右侧内容区域\n                ScrollView {\n                    configContent\n                        .padding(32)\n                }\n                .frame(maxWidth: .infinity)\n                .background(Color.white)\n            }\n        }\n        .background(Color(NSColor.windowBackgroundColor))\n    }\n    \n    // V2 玻璃拟物化布局\n    private var v2Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            HStack {\n                Text(LocalizedStringKey(";
")\n            if components.count >= 2 {\n                return (directoryName, .auto)\n            }\n        }\n\n        // 规则2: 如果包含下划线，转换为 .\n        if directoryName.contains(" = ")\n            if components.count >= 2 {\n                return (directoryName, .auto)\n            }\n        }\n\n        // 规则2: 如果包含下划线，转换为 .\n        if directoryName.contains(";
")\n        }\n    }\n\n    // Nginx 安装模式\n    @Published var nginxInstallMode: String {\n        didSet {\n            UserDefaults.standard.set(nginxInstallMode, forKey: " = ")\n        }\n    }\n\n    // Nginx 安装模式\n    @Published var nginxInstallMode: String {\n        didSet {\n            UserDefaults.standard.set(nginxInstallMode, forKey: ";
"）\n    ///   - oldPort: 旧端口号\n    ///   - newPort: 新端口号\n    ///   - workspaceRoot: 工作空间根目录（如果使用工作空间模式）\n    ///   - homebrewPrefix: Homebrew 安装前缀\n    /// - Returns: 同步结果\n    func syncPortChange(\n        software: String,\n        oldPort: Int,\n        newPort: Int,\n        workspaceRoot: String?,\n        homebrewPrefix: String\n    ) async -> PortSyncResult {\n        var updatedFiles: [String] = []\n        var failedFiles: [String] = []\n        var errors: [String] = []\n\n        // 获取同步规则\n        let rules = configManager.getSyncRules(for: software)\n\n        guard !rules.isEmpty else {\n            // 没有需要同步的规则\n            return PortSyncResult(\n                success: true,\n                updatedFiles: [],\n                failedFiles: [],\n                errors: []\n            )\n        }\n\n        // 遍历每个规则\n        for rule in rules {\n            // 遍历每个配置文件位置\n            for location in rule.configLocations {\n                guard let actualPath = location.getActualPath(\n                    workspaceRoot: workspaceRoot,\n                    homebrewPrefix: homebrewPrefix\n                ) else {\n                    continue\n                }\n\n                // 检查路径是否存在\n                guard fileManager.fileExists(atPath: actualPath) else {\n                    continue\n                }\n\n                if location.isDirectory {\n                    // 遍历目录中的文件\n                    let result = await syncDirectory(\n                        path: actualPath,\n                        filePattern: location.filePattern,\n                        patterns: rule.patterns,\n                        newPort: newPort\n                    )\n                    updatedFiles.append(contentsOf: result.updatedFiles)\n                    failedFiles.append(contentsOf: result.failedFiles)\n                    errors.append(contentsOf: result.errors)\n                } else {\n                    // 单个文件\n                    let result = await syncFile(\n                        path: actualPath,\n                        patterns: rule.patterns,\n                        newPort: newPort\n                    )\n                    if result.success {\n                        updatedFiles.append(actualPath)\n                    } else {\n                        failedFiles.append(actualPath)\n                        if let error = result.error {\n                            errors.append(error)\n                        }\n                    }\n                }\n            }\n        }\n\n        return PortSyncResult(\n            success: failedFiles.isEmpty && errors.isEmpty,\n            updatedFiles: updatedFiles,\n            failedFiles: failedFiles,\n            errors: errors\n        )\n    }\n\n    // MARK: - Private Methods\n\n    /// 同步目录中的配置文件\n    private func syncDirectory(\n        path: String,\n        filePattern: String?,\n        patterns: [PortReferencePattern],\n        newPort: Int\n    ) async -> PortSyncResult {\n        var updatedFiles: [String] = []\n        var failedFiles: [String] = []\n        var errors: [String] = []\n\n        do {\n            let contents = try fileManager.contentsOfDirectory(atPath: path)\n\n            for file in contents {\n                // 检查文件是否匹配模式\n                if let pattern = filePattern {\n                    let matchesPattern = matchFilePattern(file, pattern: pattern)\n                    guard matchesPattern else { continue }\n                }\n\n                let filePath = " = "）\n    ///   - oldPort: 旧端口号\n    ///   - newPort: 新端口号\n    ///   - workspaceRoot: 工作空间根目录（如果使用工作空间模式）\n    ///   - homebrewPrefix: Homebrew 安装前缀\n    /// - Returns: 同步结果\n    func syncPortChange(\n        software: String,\n        oldPort: Int,\n        newPort: Int,\n        workspaceRoot: String?,\n        homebrewPrefix: String\n    ) async -> PortSyncResult {\n        var updatedFiles: [String] = []\n        var failedFiles: [String] = []\n        var errors: [String] = []\n\n        // 获取同步规则\n        let rules = configManager.getSyncRules(for: software)\n\n        guard !rules.isEmpty else {\n            // 没有需要同步的规则\n            return PortSyncResult(\n                success: true,\n                updatedFiles: [],\n                failedFiles: [],\n                errors: []\n            )\n        }\n\n        // 遍历每个规则\n        for rule in rules {\n            // 遍历每个配置文件位置\n            for location in rule.configLocations {\n                guard let actualPath = location.getActualPath(\n                    workspaceRoot: workspaceRoot,\n                    homebrewPrefix: homebrewPrefix\n                ) else {\n                    continue\n                }\n\n                // 检查路径是否存在\n                guard fileManager.fileExists(atPath: actualPath) else {\n                    continue\n                }\n\n                if location.isDirectory {\n                    // 遍历目录中的文件\n                    let result = await syncDirectory(\n                        path: actualPath,\n                        filePattern: location.filePattern,\n                        patterns: rule.patterns,\n                        newPort: newPort\n                    )\n                    updatedFiles.append(contentsOf: result.updatedFiles)\n                    failedFiles.append(contentsOf: result.failedFiles)\n                    errors.append(contentsOf: result.errors)\n                } else {\n                    // 单个文件\n                    let result = await syncFile(\n                        path: actualPath,\n                        patterns: rule.patterns,\n                        newPort: newPort\n                    )\n                    if result.success {\n                        updatedFiles.append(actualPath)\n                    } else {\n                        failedFiles.append(actualPath)\n                        if let error = result.error {\n                            errors.append(error)\n                        }\n                    }\n                }\n            }\n        }\n\n        return PortSyncResult(\n            success: failedFiles.isEmpty && errors.isEmpty,\n            updatedFiles: updatedFiles,\n            failedFiles: failedFiles,\n            errors: errors\n        )\n    }\n\n    // MARK: - Private Methods\n\n    /// 同步目录中的配置文件\n    private func syncDirectory(\n        path: String,\n        filePattern: String?,\n        patterns: [PortReferencePattern],\n        newPort: Int\n    ) async -> PortSyncResult {\n        var updatedFiles: [String] = []\n        var failedFiles: [String] = []\n        var errors: [String] = []\n\n        do {\n            let contents = try fileManager.contentsOfDirectory(atPath: path)\n\n            for file in contents {\n                // 检查文件是否匹配模式\n                if let pattern = filePattern {\n                    let matchesPattern = matchFilePattern(file, pattern: pattern)\n                    guard matchesPattern else { continue }\n                }\n\n                let filePath = ";
"\n            if let files = try? FileManager.default.contentsOfDirectory(atPath: mysqlDataDir) {\n                // 查找最新的 .err 文件\n                let errFiles = files.filter { $0.hasSuffix(" = "\n            if let files = try? FileManager.default.contentsOfDirectory(atPath: mysqlDataDir) {\n                // 查找最新的 .err 文件\n                let errFiles = files.filter { $0.hasSuffix(";
"\n\n    // 默认文档\n    var defaultDocuments: String = " = "\n\n    // 默认文档\n    var defaultDocuments: String = ";
")\n    }\n\n    // MARK: - 停止服务\n\n    func stopSoftware(_ software: SoftwareVersion, category: ComponentCategory) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(" = ")\n    }\n\n    // MARK: - 停止服务\n\n    func stopSoftware(_ software: SoftwareVersion, category: ComponentCategory) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(";
"\n\n        do {\n            try sqlContent.write(to: sqlFile, atomically: true, encoding: .utf8)\n        } catch {\n            return false\n        }\n\n        defer {\n            try? FileManager.default.removeItem(at: sqlFile)\n        }\n\n        // 使用 mysqld --init-file 方式重置\n        let mysqldPaths = [\n            " = "\n\n        do {\n            try sqlContent.write(to: sqlFile, atomically: true, encoding: .utf8)\n        } catch {\n            return false\n        }\n\n        defer {\n            try? FileManager.default.removeItem(at: sqlFile)\n        }\n\n        // 使用 mysqld --init-file 方式重置\n        let mysqldPaths = [\n            ";
"备份列表 - \\(database.name)" = "备份列表 - \\(database.name)";
"读取日志文件失败: \\(error.localizedDescription)" = "读取日志文件失败: \\(error.localizedDescription)";
", gb)\n    }\n\n    // 格式化日期\n    private func formatDate(_ date: Date) -> String {\n        let formatter = DateFormatter()\n        formatter.dateFormat = " = ", gb)\n    }\n\n    // 格式化日期\n    private func formatDate(_ date: Date) -> String {\n        let formatter = DateFormatter()\n        formatter.dateFormat = ";
"\n        return formatter\n    }\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            headerRow\n            errorMessageView\n            if isExpanded {\n                expandedContentView\n            }\n        }\n        .padding(12)\n        .background(Color.red.opacity(AppTokens.Opacity.ultraLight))\n        .cornerRadius(8)\n        .overlay(\n            RoundedRectangle(cornerRadius: 8)\n                .stroke(isExpanded ? Color.red.opacity(AppTokens.Opacity.disabled) : Color.red.opacity(AppTokens.Opacity.border), lineWidth: 1)\n        )\n        .contentShape(Rectangle())\n        .onTapGesture {\n            withAnimation(.easeInOut(duration: 0.2)) {\n                onToggle()\n            }\n        }\n    }\n\n    private var headerRow: some View {\n        HStack {\n            // 版本和操作\n            HStack(spacing: 6) {\n                Text(log.version)\n                    .font(.system(size: 12, weight: .medium, design: .monospaced))\n                Text(log.operation)\n                    .font(.system(size: 10))\n                    .padding(.horizontal, 6)\n                    .padding(.vertical, 2)\n                    .background(Color.red.opacity(AppTokens.Opacity.light))\n                    .foregroundColor(.red)\n                    .cornerRadius(4)\n            }\n\n            Spacer()\n\n            // 时间\n            Text(dateFormatter.string(from: log.timestamp))\n                .font(.system(size: 11))\n                .foregroundColor(.secondary)\n        }\n    }\n\n    private var errorMessageView: some View {\n        HStack(alignment: .top, spacing: 8) {\n            Image(systemName: " = "\n        return formatter\n    }\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            headerRow\n            errorMessageView\n            if isExpanded {\n                expandedContentView\n            }\n        }\n        .padding(12)\n        .background(Color.red.opacity(AppTokens.Opacity.ultraLight))\n        .cornerRadius(8)\n        .overlay(\n            RoundedRectangle(cornerRadius: 8)\n                .stroke(isExpanded ? Color.red.opacity(AppTokens.Opacity.disabled) : Color.red.opacity(AppTokens.Opacity.border), lineWidth: 1)\n        )\n        .contentShape(Rectangle())\n        .onTapGesture {\n            withAnimation(.easeInOut(duration: 0.2)) {\n                onToggle()\n            }\n        }\n    }\n\n    private var headerRow: some View {\n        HStack {\n            // 版本和操作\n            HStack(spacing: 6) {\n                Text(log.version)\n                    .font(.system(size: 12, weight: .medium, design: .monospaced))\n                Text(log.operation)\n                    .font(.system(size: 10))\n                    .padding(.horizontal, 6)\n                    .padding(.vertical, 2)\n                    .background(Color.red.opacity(AppTokens.Opacity.light))\n                    .foregroundColor(.red)\n                    .cornerRadius(4)\n            }\n\n            Spacer()\n\n            // 时间\n            Text(dateFormatter.string(from: log.timestamp))\n                .font(.system(size: 11))\n                .foregroundColor(.secondary)\n        }\n    }\n\n    private var errorMessageView: some View {\n        HStack(alignment: .top, spacing: 8) {\n            Image(systemName: ";
"确定要彻底删除站点 \\(site.name) 吗？此操作不可恢复。" = "确定要彻底删除站点 \\(site.name) 吗？此操作不可恢复。";
"\n        if FileManager.default.fileExists(atPath: altPath) {\n            return altPath\n        }\n        // 返回默认系统路径（即使不存在）\n        return phpMyAdminSystemPath\n    }\n\n    /// 检查 phpMyAdmin 是否已安装\n    var isPhpMyAdminInstalled: Bool {\n        FileManager.default.fileExists(atPath: phpMyAdminPath)\n    }\n\n    /// 检查应用是否内置了 phpMyAdmin\n    var hasBundledPhpMyAdmin: Bool {\n        guard let path = bundledPhpMyAdminPath else { return false }\n        return FileManager.default.fileExists(atPath: path)\n    }\n\n    /// 安装内置的 phpMyAdmin 到系统目录\n    func installBundledPhpMyAdmin() async -> Bool {\n        guard let bundledPath = bundledPhpMyAdminPath,\n              FileManager.default.fileExists(atPath: bundledPath) else {\n            lastError = " = "\n        if FileManager.default.fileExists(atPath: altPath) {\n            return altPath\n        }\n        // 返回默认系统路径（即使不存在）\n        return phpMyAdminSystemPath\n    }\n\n    /// 检查 phpMyAdmin 是否已安装\n    var isPhpMyAdminInstalled: Bool {\n        FileManager.default.fileExists(atPath: phpMyAdminPath)\n    }\n\n    /// 检查应用是否内置了 phpMyAdmin\n    var hasBundledPhpMyAdmin: Bool {\n        guard let path = bundledPhpMyAdminPath else { return false }\n        return FileManager.default.fileExists(atPath: path)\n    }\n\n    /// 安装内置的 phpMyAdmin 到系统目录\n    func installBundledPhpMyAdmin() async -> Bool {\n        guard let bundledPath = bundledPhpMyAdminPath,\n              FileManager.default.fileExists(atPath: bundledPath) else {\n            lastError = ";
"\n        ),\n\n        // ===== 图像处理 =====\n        " = "\n        ),\n\n        // ===== 图像处理 =====\n        ";
"\n        let regex = try? NSRegularExpression(pattern: pattern)\n        let range = NSRange(location: 0, length: name.utf16.count)\n        return regex?.firstMatch(in: name, range: range) != nil\n    }\n\n    // 验证用户名\n    private func isValidUsername(_ username: String) -> Bool {\n        let pattern = " = "\n        let regex = try? NSRegularExpression(pattern: pattern)\n        let range = NSRange(location: 0, length: name.utf16.count)\n        return regex?.firstMatch(in: name, range: range) != nil\n    }\n\n    // 验证用户名\n    private func isValidUsername(_ username: String) -> Bool {\n        let pattern = ";
")\n                            .foregroundColor(.orange)\n                        Text(path)\n                            .font(.system(size: 12, design: .monospaced))\n                            .lineLimit(1)\n                            .truncationMode(.middle)\n                    }\n                    .padding(12)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n                    .background(Color.orange.opacity(AppTokens.Opacity.light))\n                    .cornerRadius(8)\n                }\n                .frame(maxWidth: 600)\n            }\n\n            // 操作按钮\n            HStack(spacing: 12) {\n                if BuildInfo.uiVersion == " = ")\n                            .foregroundColor(.orange)\n                        Text(path)\n                            .font(.system(size: 12, design: .monospaced))\n                            .lineLimit(1)\n                            .truncationMode(.middle)\n                    }\n                    .padding(12)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n                    .background(Color.orange.opacity(AppTokens.Opacity.light))\n                    .cornerRadius(8)\n                }\n                .frame(maxWidth: 600)\n            }\n\n            // 操作按钮\n            HStack(spacing: 12) {\n                if BuildInfo.uiVersion == ";
")\n\n        let publicRoot = sitePublicRoot(documentRoot: documentRoot, publicDirectory: site.publicDirectory)\n        let phpPort = phpFpmPort(for: site.phpVersion)\n\n        // 日志目录路径\n        let logsDir = (documentRoot as NSString).appendingPathComponent(" = ")\n\n        let publicRoot = sitePublicRoot(documentRoot: documentRoot, publicDirectory: site.publicDirectory)\n        let phpPort = phpFpmPort(for: site.phpVersion)\n\n        // 日志目录路径\n        let logsDir = (documentRoot as NSString).appendingPathComponent(";
"\n    }\n\n    /// 生成 phpMyAdmin 的 Nginx 配置\n    func generateNginxConfig() -> String {\n        let allowedIPs = localIPAddresses.map { $0.address }\n        let allowRules = allowedIPs.map { " = "\n    }\n\n    /// 生成 phpMyAdmin 的 Nginx 配置\n    func generateNginxConfig() -> String {\n        let allowedIPs = localIPAddresses.map { $0.address }\n        let allowRules = allowedIPs.map { ";
"]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n            return process.terminationStatus == 0\n        } catch {\n            return true\n        }\n    }\n\n    /// 获取 brew service 状态（nonisolated，可在后台线程调用）\n    private nonisolated static func getBrewServiceStatus(_ brewName: String, brewPath: String?) -> String {\n        guard let brewPath = brewPath else { return " = "]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n            return process.terminationStatus == 0\n        } catch {\n            return true\n        }\n    }\n\n    /// 获取 brew service 状态（nonisolated，可在后台线程调用）\n    private nonisolated static func getBrewServiceStatus(_ brewName: String, brewPath: String?) -> String {\n        guard let brewPath = brewPath else { return ";
", type: .loading)\n\n        DispatchQueue.global(qos: .userInitiated).async {\n            // 保存前自动备份\n            do {\n                _ = try ConfigBackupManager.shared.createBackup(\n                    serviceName: service.name,\n                    configPath: path,\n                    note: " = ", type: .loading)\n\n        DispatchQueue.global(qos: .userInitiated).async {\n            // 保存前自动备份\n            do {\n                _ = try ConfigBackupManager.shared.createBackup(\n                    serviceName: service.name,\n                    configPath: path,\n                    note: ";
")\n                        .foregroundColor(.gray)\n                        .font(.system(size: 16))\n                } else {\n                    Image(systemName: result.status.icon)\n                        .foregroundColor(statusColor)\n                        .font(.system(size: 16))\n                }\n\n                // 检查项图标和名称\n                Label(result.type.rawValue, systemImage: result.type.icon)\n                    .font(.subheadline)\n\n                Spacer()\n\n                // 状态信息（正在检测时显示进度，完成后显示摘要，展开后隐藏）\n                if !showDetails {\n                    if isCurrentlyChecking {\n                        Text(getProgressMessage())\n                            .font(.caption)\n                            .foregroundColor(.blue)\n                    } else if result.message != " = ")\n                        .foregroundColor(.gray)\n                        .font(.system(size: 16))\n                } else {\n                    Image(systemName: result.status.icon)\n                        .foregroundColor(statusColor)\n                        .font(.system(size: 16))\n                }\n\n                // 检查项图标和名称\n                Label(result.type.rawValue, systemImage: result.type.icon)\n                    .font(.subheadline)\n\n                Spacer()\n\n                // 状态信息（正在检测时显示进度，完成后显示摘要，展开后隐藏）\n                if !showDetails {\n                    if isCurrentlyChecking {\n                        Text(getProgressMessage())\n                            .font(.caption)\n                            .foregroundColor(.blue)\n                    } else if result.message != ";
"\n    var configLastModified: Date? = nil\n\n    // CORS配置\n    var corsEnabled: Bool = false\n    var corsOrigins: String = " = "\n    var configLastModified: Date? = nil\n\n    // CORS配置\n    var corsEnabled: Bool = false\n    var corsOrigins: String = ";
"\n        let fileURL = directory.appendingPathComponent(fileName)\n\n        // 使用 mysqldump 命令导出\n        let success = await performMySQLDump(databaseName: database.name, to: fileURL)\n        if !success {\n            throw DatabaseError.operationFailed(" = "\n        let fileURL = directory.appendingPathComponent(fileName)\n\n        // 使用 mysqldump 命令导出\n        let success = await performMySQLDump(databaseName: database.name, to: fileURL)\n        if !success {\n            throw DatabaseError.operationFailed(";
", text: $port)\n                            .macInput()\n                            .disabled(database.type == .local)\n                    }\n                    .frame(width: 120) // 固定端口宽度\n                }\n\n                // 管理员名称 & 密码\n                HStack(spacing: 20) {\n                    FormField(" = ", text: $port)\n                            .macInput()\n                            .disabled(database.type == .local)\n                    }\n                    .frame(width: 120) // 固定端口宽度\n                }\n\n                // 管理员名称 & 密码\n                HStack(spacing: 20) {\n                    FormField(";
", Color(red: 0.47, green: 0.37, blue: 0.69)) // PHP紫色\n        } else if name.contains(" = ", Color(red: 0.47, green: 0.37, blue: 0.69)) // PHP紫色\n        } else if name.contains(";
")\n    }\n\n    /// 获取服务可能使用的端口列表\n    private func getServicePorts(for software: SoftwareVersion) -> [Int] {\n        // 如果软件有明确的端口，优先检查\n        if let port = software.port {\n            return [port]\n        }\n\n        // 根据软件类型返回常用端口\n        let name = software.name.lowercased()\n        if name.contains(" = ")\n    }\n\n    /// 获取服务可能使用的端口列表\n    private func getServicePorts(for software: SoftwareVersion) -> [Int] {\n        // 如果软件有明确的端口，优先检查\n        if let port = software.port {\n            return [port]\n        }\n\n        // 根据软件类型返回常用端口\n        let name = software.name.lowercased()\n        if name.contains(";
"]\n\n            let pipe = Pipe()\n            process.standardOutput = pipe\n            process.standardError = pipe\n\n            do {\n                try process.run()\n                // 使用带超时的等待\n                let completed = waitForProcessWithTimeout(process, timeout: 5.0)\n                if !completed {\n                    process.terminate()\n                    return .stopped\n                }\n\n                let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                output = String(data: data, encoding: .utf8)\n                // 缓存结果供后续服务复用\n                AppState.cachedBrewServicesOutput = output\n            } catch {\n                return .stopped\n            }\n        }\n\n        guard let servicesOutput = output else {\n            return .stopped\n        }\n\n        // 查找服务状态\n        let lines = servicesOutput.components(separatedBy: .newlines)\n        for line in lines {\n            if line.contains(serviceName) {\n                if line.contains(" = "]\n\n            let pipe = Pipe()\n            process.standardOutput = pipe\n            process.standardError = pipe\n\n            do {\n                try process.run()\n                // 使用带超时的等待\n                let completed = waitForProcessWithTimeout(process, timeout: 5.0)\n                if !completed {\n                    process.terminate()\n                    return .stopped\n                }\n\n                let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                output = String(data: data, encoding: .utf8)\n                // 缓存结果供后续服务复用\n                AppState.cachedBrewServicesOutput = output\n            } catch {\n                return .stopped\n            }\n        }\n\n        guard let servicesOutput = output else {\n            return .stopped\n        }\n\n        // 查找服务状态\n        let lines = servicesOutput.components(separatedBy: .newlines)\n        for line in lines {\n            if line.contains(serviceName) {\n                if line.contains(";
"\n    }\n\n    // MARK: - Apache 伪静态规则模板\n    private var wordpressApache: String {\n        " = "\n    }\n\n    // MARK: - Apache 伪静态规则模板\n    private var wordpressApache: String {\n        ";
"\n    }\n\n    /// 获取 PHP 配置搜索路径列表\n    func getPHPConfigSearchPaths(version: String) -> [String] {\n        var paths: [String] = []\n\n        if let config = workspaceManager.config, config.isInitialized {\n            paths.append(config.phpConfigFile(version: version))\n            paths.append(config.phpFpmConfigFile(version: version))\n        }\n\n        paths.append(" = "\n    }\n\n    /// 获取 PHP 配置搜索路径列表\n    func getPHPConfigSearchPaths(version: String) -> [String] {\n        var paths: [String] = []\n\n        if let config = workspaceManager.config, config.isInitialized {\n            paths.append(config.phpConfigFile(version: version))\n            paths.append(config.phpFpmConfigFile(version: version))\n        }\n\n        paths.append(";
"\n                    try updateNginxListenPort(at: filePath, port: port)\n                    modified = true\n                }\n            }\n        } else {\n            // Homebrew 模式：只修改 Homebrew 的配置文件\n            let brewPath = " = "\n                    try updateNginxListenPort(at: filePath, port: port)\n                    modified = true\n                }\n            }\n        } else {\n            // Homebrew 模式：只修改 Homebrew 的配置文件\n            let brewPath = ";
"\n        let backupPath = backupDirectory.appendingPathComponent(backupFileName).path\n\n        // 复制文件\n        try fileManager.copyItem(atPath: configPath, toPath: backupPath)\n\n        // 创建备份记录\n        let backup = ConfigBackup(\n            serviceName: serviceName,\n            originalPath: configPath,\n            backupPath: backupPath,\n            fileSize: fileSize,\n            note: note,\n            siteId: siteId,\n            siteName: siteName\n        )\n\n        // 保存记录\n        var records = loadBackupRecords()\n        records.append(backup)\n        saveBackupRecords(records)\n\n        // 清理旧备份\n        cleanupOldBackups(for: siteId)\n\n        return backup\n    }\n\n    /// 恢复配置文件\n    func restoreBackup(_ backup: ConfigBackup) throws {\n        let fileManager = FileManager.default\n\n        // 检查备份文件是否存在\n        guard fileManager.fileExists(atPath: backup.backupPath) else {\n            throw BackupError.backupFileNotFound\n        }\n\n        // 先备份当前配置（以防万一）\n        if fileManager.fileExists(atPath: backup.originalPath) {\n            _ = try? createBackup(\n                serviceName: backup.serviceName,\n                configPath: backup.originalPath,\n                note: " = "\n        let backupPath = backupDirectory.appendingPathComponent(backupFileName).path\n\n        // 复制文件\n        try fileManager.copyItem(atPath: configPath, toPath: backupPath)\n\n        // 创建备份记录\n        let backup = ConfigBackup(\n            serviceName: serviceName,\n            originalPath: configPath,\n            backupPath: backupPath,\n            fileSize: fileSize,\n            note: note,\n            siteId: siteId,\n            siteName: siteName\n        )\n\n        // 保存记录\n        var records = loadBackupRecords()\n        records.append(backup)\n        saveBackupRecords(records)\n\n        // 清理旧备份\n        cleanupOldBackups(for: siteId)\n\n        return backup\n    }\n\n    /// 恢复配置文件\n    func restoreBackup(_ backup: ConfigBackup) throws {\n        let fileManager = FileManager.default\n\n        // 检查备份文件是否存在\n        guard fileManager.fileExists(atPath: backup.backupPath) else {\n            throw BackupError.backupFileNotFound\n        }\n\n        // 先备份当前配置（以防万一）\n        if fileManager.fileExists(atPath: backup.originalPath) {\n            _ = try? createBackup(\n                serviceName: backup.serviceName,\n                configPath: backup.originalPath,\n                note: ";
"重启失败: \\(error.localizedDescription)" = "重启失败: \\(error.localizedDescription)";
" else {\n            directoryPermissionStatus = (true, true, nil)\n            return\n        }\n        \n        // 检查父目录权限\n        let validation = appSettings.validateDirectory(parentDir)\n        if !validation.exists {\n            directoryPermissionStatus = (false, true, " = " else {\n            directoryPermissionStatus = (true, true, nil)\n            return\n        }\n        \n        // 检查父目录权限\n        let validation = appSettings.validateDirectory(parentDir)\n        if !validation.exists {\n            directoryPermissionStatus = (false, true, ";
"配置失败: \\(error.localizedDescription)" = "配置失败: \\(error.localizedDescription)";
", isIconOnly: true) {\n                        showSettingsSheet = true\n                    }\n                }\n            }\n            .padding(16)\n            \n            // 表格容器\n            VStack(spacing: 0) {\n                // 表头与内容 (移除包裹的横向 ScrollView 以实现 100% 宽度自适应)\n                VStack(alignment: .leading, spacing: 0) {\n                    tableHeader\n                    \n                    Divider()\n                        .background(Color.gray.opacity(0.2))\n                    \n                    // 表格内容\n                    ScrollView(.vertical, showsIndicators: true) {\n                        LazyVStack(spacing: 0) {\n                            ForEach(filteredSites) { site in\n                                SiteTableRowV2(\n                                    site: site,\n                                    isTrashMode: showTrashOnly,\n                                    isSelected: selectedSites.contains(site.id),\n                                    onToggleSelection: {\n                                        if selectedSites.contains(site.id) {\n                                            selectedSites.remove(site.id)\n                                        } else {\n                                            selectedSites.insert(site.id)\n                                        }\n                                    }\n                                )\n                                \n                                Divider()\n                                    .background(Color.gray.opacity(0.1))\n                            }\n                        }\n                    }\n                }\n            }\n            .frame(maxWidth: .infinity)\n            .background(Color.white.opacity(0.1))\n            .cornerRadius(AppTokens.Radius.medium)\n            .padding(.horizontal, 16)\n            .padding(.bottom, 16)\n        }\n        .sheetWithLocale(isPresented: $showAddSiteSheet) {\n            AddSiteSheet().environmentObject(siteManager)\n        }\n        .sheetWithLocale(isPresented: $showVhostSyncSheet) {\n            VhostSyncSheet().environmentObject(siteManager).environmentObject(appState)\n        }\n        .sheetWithLocale(isPresented: $showBatchImportSheet) {\n            BatchImportByDirectorySheet().environmentObject(siteManager).environmentObject(appState)\n        }\n        .sheetWithLocale(isPresented: $showRepairSheet) {\n            SiteRepairSheet().environmentObject(siteManager)\n        }\n        .sheetWithLocale(isPresented: $showHealthCheckSheet) {\n            SiteHealthCheckView(sites: siteManager.sites.filter { selectedSites.contains($0.id) })\n        }\n        .sheetWithLocale(isPresented: $showSettingsSheet) {\n            SettingsView()\n        }\n        .onAppear {\n            siteManager.installedWebServers = installedWebServers\n            siteManager.checkWebServerRunning = { webServer in\n                let serviceName: String\n                switch webServer {\n                case .nginx:\n                    serviceName = " = ", isIconOnly: true) {\n                        showSettingsSheet = true\n                    }\n                }\n            }\n            .padding(16)\n            \n            // 表格容器\n            VStack(spacing: 0) {\n                // 表头与内容 (移除包裹的横向 ScrollView 以实现 100% 宽度自适应)\n                VStack(alignment: .leading, spacing: 0) {\n                    tableHeader\n                    \n                    Divider()\n                        .background(Color.gray.opacity(0.2))\n                    \n                    // 表格内容\n                    ScrollView(.vertical, showsIndicators: true) {\n                        LazyVStack(spacing: 0) {\n                            ForEach(filteredSites) { site in\n                                SiteTableRowV2(\n                                    site: site,\n                                    isTrashMode: showTrashOnly,\n                                    isSelected: selectedSites.contains(site.id),\n                                    onToggleSelection: {\n                                        if selectedSites.contains(site.id) {\n                                            selectedSites.remove(site.id)\n                                        } else {\n                                            selectedSites.insert(site.id)\n                                        }\n                                    }\n                                )\n                                \n                                Divider()\n                                    .background(Color.gray.opacity(0.1))\n                            }\n                        }\n                    }\n                }\n            }\n            .frame(maxWidth: .infinity)\n            .background(Color.white.opacity(0.1))\n            .cornerRadius(AppTokens.Radius.medium)\n            .padding(.horizontal, 16)\n            .padding(.bottom, 16)\n        }\n        .sheetWithLocale(isPresented: $showAddSiteSheet) {\n            AddSiteSheet().environmentObject(siteManager)\n        }\n        .sheetWithLocale(isPresented: $showVhostSyncSheet) {\n            VhostSyncSheet().environmentObject(siteManager).environmentObject(appState)\n        }\n        .sheetWithLocale(isPresented: $showBatchImportSheet) {\n            BatchImportByDirectorySheet().environmentObject(siteManager).environmentObject(appState)\n        }\n        .sheetWithLocale(isPresented: $showRepairSheet) {\n            SiteRepairSheet().environmentObject(siteManager)\n        }\n        .sheetWithLocale(isPresented: $showHealthCheckSheet) {\n            SiteHealthCheckView(sites: siteManager.sites.filter { selectedSites.contains($0.id) })\n        }\n        .sheetWithLocale(isPresented: $showSettingsSheet) {\n            SettingsView()\n        }\n        .onAppear {\n            siteManager.installedWebServers = installedWebServers\n            siteManager.checkWebServerRunning = { webServer in\n                let serviceName: String\n                switch webServer {\n                case .nginx:\n                    serviceName = ";
"\n\n    private init() {}\n\n    // MARK: - 检查 Homebrew 是否安装\n    func isHomebrewInstalled() async -> Bool {\n        return await Task.detached {\n            let process = Process()\n            process.executableURL = URL(fileURLWithPath: " = "\n\n    private init() {}\n\n    // MARK: - 检查 Homebrew 是否安装\n    func isHomebrewInstalled() async -> Bool {\n        return await Task.detached {\n            let process = Process()\n            process.executableURL = URL(fileURLWithPath: ";
")\n                .font(.system(size: 64))\n                .foregroundColor(.orange)\n\n            // 连接信息\n            VStack(spacing: 8) {\n                Text(tab.name)\n                    .font(.title2)\n                    .fontWeight(.semibold)\n\n                Text(" = ")\n                .font(.system(size: 64))\n                .foregroundColor(.orange)\n\n            // 连接信息\n            VStack(spacing: 8) {\n                Text(tab.name)\n                    .font(.title2)\n                    .fontWeight(.semibold)\n\n                Text(";
"停止 \\(conflictingService)" = "Detener \\(conflictingService)";
")\n        }) {\n            appState.startService(mysqlService)\n        }\n    }\n\n    // 信息项\n    private func infoItem(icon: String, iconColor: Color, title: LocalizedStringKey, value: String, status: LocalizedStringKey, statusColor: Color) -> some View {\n        HStack(spacing: 12) {\n            Image(systemName: icon)\n                .font(.system(size: 24))\n                .foregroundColor(iconColor)\n                .frame(width: 40)\n\n            VStack(alignment: .leading, spacing: 4) {\n                Text(title)\n                    .font(.system(size: 12))\n                    .foregroundColor(.secondary)\n                Text(value)\n                    .font(.system(size: 16, weight: .semibold))\n                HStack(spacing: 4) {\n                    Circle()\n                        .fill(statusColor)\n                        .frame(width: 6, height: 6)\n                    Text(status)\n                        .font(.system(size: 11))\n                        .foregroundColor(statusColor)\n                }\n            }\n        }\n        .frame(minWidth: 120)\n    }\n\n    // Root密码项\n    private var rootPasswordItem: some View {\n        HStack(spacing: 12) {\n            Image(systemName: " = ")\n        }) {\n            appState.startService(mysqlService)\n        }\n    }\n\n    // Elemento de información\n    private func infoItem(icon: String, iconColor: Color, title: LocalizedStringKey, value: String, status: LocalizedStringKey, statusColor: Color) -> some View {\n        HStack(spacing: 12) {\n            Image(systemName: icon)\n                .font(.system(size: 24))\n                .foregroundColor(iconColor)\n                .frame(width: 40)\n\n            VStack(alignment: .leading, spacing: 4) {\n                Text(title)\n                    .font(.system(size: 12))\n                    .foregroundColor(.secondary)\n                Text(value)\n                    .font(.system(size: 16, weight: .semibold))\n                HStack(spacing: 4) {\n                    Circle()\n                        .fill(statusColor)\n                        .frame(width: 6, height: 6)\n                    Text(status)\n                        .font(.system(size: 11))\n                        .foregroundColor(statusColor)\n                }\n            }\n        }\n        .frame(minWidth: 120)\n    }\n\n    // Elemento de contraseña raíz\n    private var rootPasswordItem: some View {\n        HStack(spacing: 12) {\n            Image(systemName: ";
"\n        ]\n\n        for path in paths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n        return nil\n    }\n\n    // 保存配置\n    private func saveConfig() {\n        isSaving = true\n        errorMessage = nil\n        successMessage = nil\n\n        guard let configPath = getMySQLConfigPath() else {\n            errorMessage = " = "\n        ]\n\n        for path in paths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n        return nil\n    }\n\n    // Guardar configuración\n    private func saveConfig() {\n        isSaving = true\n        errorMessage = nil\n        successMessage = nil\n\n        guard let configPath = getMySQLConfigPath() else {\n            errorMessage = ";
"\n        let result3 = await executeSQL(sql3)\n        if !result3.success {\n            errorMessage = result3.error\n            return false\n        }\n\n        return true\n    }\n\n    // MARK: - 备份与导入\n\n    /// 获取备份目录\n    private func getBackupDirectory() -> URL {\n        let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!\n        let backupDir = appSupport.appendingPathComponent(" = "\n        let result3 = await executeSQL(sql3)\n        if !result3.success {\n            errorMessage = result3.error\n            return false\n        }\n\n        return true\n    }\n\n    // MARK: - Copia de seguridad e importación\n\n    /// Obtener el directorio de copias de seguridad\n    private func getBackupDirectory() -> URL {\n        let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!\n        let backupDir = appSupport.appendingPathComponent(";
")\n\n        return paths\n    }\n\n    // MARK: - Nginx 路径\n\n    /// 获取 Nginx 配置文件路径（优先返回存在的文件）\n    func getNginxConfigPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.nginxConfigFile\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        return " = ")\n\n        return paths\n    }\n\n    // MARK: - Rutas de Nginx\n\n    /// Obtener la ruta del archivo de configuración de Nginx (devuelve primero el archivo existente)\n    func getNginxConfigPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.nginxConfigFile\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        return ";
") {\n                    ports.append(80)\n                }\n            }\n\n            // 检查 SSL 配置\n            if trimmed.hasPrefix(" = ") {\n                    ports.append(80)\n                }\n            }\n\n            // Verificar la configuración SSL\n            if trimmed.hasPrefix(";
"\n        let result = await executeSQL(sql)\n\n        if result.success {\n            await fetchUsers()\n        } else {\n            errorMessage = result.error\n        }\n        return result.success\n    }\n\n    /// 修改用户密码\n    func changePassword(username: String, host: String, newPassword: String) async -> Bool {\n        let sql = " = "\n        let result = await executeSQL(sql)\n\n        if result.success {\n            await fetchUsers()\n        } else {\n            errorMessage = result.error\n        }\n        return result.success\n    }\n\n    /// Cambiar la contraseña del usuario\n    func changePassword(username: String, host: String, newPassword: String) async -> Bool {\n        let sql = ";
" // 详细信息\n    var progress: Double = 0.0 // 进度 0.0-1.0\n}\n\n/// 扩展信息（运行时）\nstruct PHPExtensionInfo: Identifiable {\n    let id: String\n    let meta: PHPExtensionMeta\n    var status: ExtensionStatus\n    var isEnabled: Bool\n    var version: String?\n    var installProgress: InstallationProgress? // 安装进度\n\n    init(meta: PHPExtensionMeta, status: ExtensionStatus = .notInstalled, isEnabled: Bool = false) {\n        self.id = meta.name\n        self.meta = meta\n        self.status = status\n        self.isEnabled = isEnabled\n    }\n}\n\n/// PHP 扩展管理器\n@MainActor\nclass PHPExtensionManager: ObservableObject {\n    static let shared = PHPExtensionManager()\n\n    // 按 PHP 版本存储扩展状态\n    @Published var extensionsByVersion: [String: [PHPExtensionInfo]] = [:]\n    @Published var installedExtensionsByVersion: [String: Set<String>] = [:]\n    @Published var isLoading = false\n    @Published var currentOperation: String = " = " // Información detallada\n    var progress: Double = 0.0 // Progreso: 0.0-1.0\n}\n\n/// Información extendida (en tiempo de ejecución)\nstruct PHPExtensionInfo: Identifiable {\n    let id: String\n    let meta: PHPExtensionMeta\n    var status: ExtensionStatus\n    var isEnabled: Bool\n    var version: String?\n    var installProgress: InstallationProgress? // Progreso de instalación\n\n    init(meta: PHPExtensionMeta, status: ExtensionStatus = .notInstalled, isEnabled: Bool = false) {\n        self.id = meta.name\n        self.meta = meta\n        self.status = status\n        self.isEnabled = isEnabled\n    }\n}\n\n/// Administrador de extensiones PHP\n@MainActor\nclass PHPExtensionManager: ObservableObject {\n    static let shared = PHPExtensionManager()\n\n    // Almacenar el estado de las extensiones por versión de PHP\n    @Published var extensionsByVersion: [String: [PHPExtensionInfo]] = [:]\n    @Published var installedExtensionsByVersion: [String: Set<String>] = [:]\n    @Published var isLoading = false\n    @Published var currentOperation: String = ";
"\n        }\n        // 只有目录存在时才添加\n        if fileManager.fileExists(atPath: apachePath) {\n            directories.append((label: " = "\n        }\n        // Solo agregar si el directorio existe\n        if fileManager.fileExists(atPath: apachePath) {\n            directories.append((label: ";
")\n                    .font(.title2)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // 加载视图\n    private var loadingView: some View {\n        VStack(spacing: 16) {\n            ProgressView()\n                .scaleEffect(1.5)\n            Text(" = ")\n                    .font(.title2)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // Vista de carga\n    private var loadingView: some View {\n        VStack(spacing: 16) {\n            ProgressView()\n                .scaleEffect(1.5)\n            Text(";
", brewName])\n\n        if success {\n            // 只更新单个软件的运行状态，不重新加载整个列表\n            updateSoftwareRunningState(software.id, isRunning: false)\n        } else {\n            let errorMsg = " = ", brewName])\n\n        if success {\n            // Actualizar únicamente el estado de ejecución del software específico, sin recargar toda la lista\n            updateSoftwareRunningState(software.id, isRunning: false)\n        } else {\n            let errorMsg = ";
", isDirectory: true)\n        \n        // 确保目录存在\n        try? fileManager.createDirectory(at: appDir, withIntermediateDirectories: true)\n        \n        return appDir.appendingPathComponent(" = ", isDirectory: true)\n        \n        // Asegurarse de que el directorio exista\n        try? fileManager.createDirectory(at: appDir, withIntermediateDirectories: true)\n        \n        return appDir.appendingPathComponent(";
"\n    @State private var showPassword = false  // 控制密码显示/隐藏\n    @State private var note = " = "\n    @State private var showPassword = false  // Controla la visualización/ocultación de la contraseña\n    @State private var note = ";
", selection: $sourceType) {\n                        ForEach(RewriteType.allCases, id: \\.self) { type in\n                            Text(LocalizedStringKey(type.rawValue)).tag(type)\n                        }\n                    }\n                    .pickerStyle(.segmented)\n                    .frame(width: 200)\n                }\n\n                TextEditor(text: $sourceRules)\n                    .font(.system(.body, design: .monospaced))\n                        .macTextEditor()\n\n                // 字符统计和清空按钮\n                HStack {\n                    Text(" = ", selection: $sourceType) {\n                        ForEach(RewriteType.allCases, id: \\.self) { type in\n                            Text(LocalizedStringKey(type.rawValue)).tag(type)\n                        }\n                    }\n                    .pickerStyle(.segmented)\n                    .frame(width: 200)\n                }\n\n                TextEditor(text: $sourceRules)\n                    .font(.system(.body, design: .monospaced))\n                        .macTextEditor()\n\n                // Contador de caracteres y botón para borrar\n                HStack {\n                    Text(";
")\n\n        // 使用用户主目录下的日志路径，避免沙盒权限问题\n        let homeDir = FileManager.default.homeDirectoryForCurrentUser.path\n        let logDir = " = ")\n\n        // Usar la ruta de los registros en el directorio principal del usuario para evitar problemas de permisos del entorno protegido (sandbox)\n        let homeDir = FileManager.default.homeDirectoryForCurrentUser.path\n        let logDir = ";
"  // 运行目录\n    @State private var showCustomRunningDir = false  // 自定义运行目录\n    @State private var availableSubdirs: [String] = []  // 可用子目录\n    @State private var forceHttps = false  // 强制HTTPS\n    @State private var selectedWebServer: WebServerType = .nginx\n    @State private var selectedPHPVersion = " = "  // Directorio de ejecución\n    @State private var showCustomRunningDir = false  // Directorio de ejecución personalizado\n    @State private var availableSubdirs: [String] = []  // Subdirectorios disponibles\n    @State private var forceHttps = false  // Forzar HTTPS\n    @State private var selectedWebServer: WebServerType = .nginx\n    @State private var selectedPHPVersion = ";
")\n                        .font(.caption)\n                        .foregroundColor(.secondary)\n                }\n            }\n\n            Spacer()\n\n            // 有效期\n            VStack(alignment: .trailing, spacing: 2) {\n                Text(" = ")\n                        .font(.caption)\n                        .foregroundColor(.secondary)\n                }\n            }\n\n            Spacer()\n\n            // Fecha de vencimiento\n            VStack(alignment: .trailing, spacing: 2) {\n                Text(";
"), toPath: hostsPath)\n    }\n\n    // 保存 hosts 文件\n    // 使用管理员权限写入文件\n    private func writeWithAdminPrivileges(content: String, toPath path: String) throws {\n        // 创建临时文件\n        let tempPath = NSTemporaryDirectory() + " = "), toPath: hostsPath)\n    }\n\n    // Guardar el archivo hosts\n    // Escribir el archivo con privilegios de administrador\n    private func writeWithAdminPrivileges(content: String, toPath path: String) throws {\n        // Crear un archivo temporal\n        let tempPath = NSTemporaryDirectory() + ";
"\n\n        return config\n    }\n\n    /// 重载 Nginx\n    private func reloadNginx() async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let brewPath = " = "\n\n        return config\n    }\n\n    /// Recargar Nginx\n    private func reloadNginx() async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let brewPath = ";
")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            // 内容\n            VStack(spacing: 24) {\n                // 图标\n                Image(systemName: " = ")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            // Contenido\n            VStack(spacing: 24) {\n                // Icono\n                Image(systemName: ";
")\n            }\n        }\n\n        return result\n    }\n\n    /// 检测当前默认的 PHP 包名（通过符号链接）\n    private nonisolated func detectDefaultPHPPackage() -> String? {\n        let phpPath = " = ")\n            }\n        }\n\n        return result\n    }\n\n    /// Detectar el nombre del paquete PHP predeterminado actual (mediante enlaces simbólicos)\n    private nonisolated func detectDefaultPHPPackage() -> String? {\n        let phpPath = ";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(20)\n        .frame(height: 60)\n    }\n\n    // 内容区域\n    private var contentArea: some View {\n        HStack(spacing: 0) {\n            // 左侧：源规则\n            VStack(alignment: .leading, spacing: 12) {\n                HStack {\n                    Text(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(20)\n        .frame(height: 60)\n    }\n\n    // Área de contenido\n    private var contentArea: some View {\n        HStack(spacing: 0) {\n            // Izquierda: reglas de origen\n            VStack(alignment: .leading, spacing: 12) {\n                HStack {\n                    Text(";
") {\n                // 本地 MySQL 连接（只显示一个连接入口）\n                if mysqlServiceInfo.installed {\n                    Button(action: {\n                        openDatabaseTab(name: " = ") {\n                // Conexión local de MySQL (mostrar solo una entrada de conexión)\n                if mysqlServiceInfo.installed {\n                    Button(action: {\n                        openDatabaseTab(name: ";
", serviceName: serviceName) { success in\n            DispatchQueue.main.async {\n                self.services[index].state = success ? .running : .stopped\n                // 只在失败时显示提示\n                if !success {\n                    self.showServiceAlert(\n                        title: " = ", serviceName: serviceName) { success in\n            DispatchQueue.main.async {\n                self.services[index].state = success ? .running : .stopped\n                // Mostrar advertencia únicamente en caso de fallo\n                if !success {\n                    self.showServiceAlert(\n                        title: ";
")\n            return\n        }\n        \n        // 确保目标目录存在\n        var targetIsDirectory: ObjCBool = false\n        if !fileManager.fileExists(atPath: documentRoot, isDirectory: &targetIsDirectory) {\n            // 如果目标目录不存在，尝试创建\n            try fileManager.createDirectory(atPath: documentRoot, withIntermediateDirectories: true, attributes: nil)\n        } else if !targetIsDirectory.boolValue {\n            throw SiteError.operationFailed(" = ")\n            return\n        }\n        \n        // Asegurarse de que el directorio de destino exista\n        var targetIsDirectory: ObjCBool = false\n        if !fileManager.fileExists(atPath: documentRoot, isDirectory: &targetIsDirectory) {\n            // Si el directorio de destino no existe, intentar crearlo\n            try fileManager.createDirectory(atPath: documentRoot, withIntermediateDirectories: true, attributes: nil)\n        } else if !targetIsDirectory.boolValue {\n            throw SiteError.operationFailed(";
")\n                        .font(.system(size: 14, weight: .medium))\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            // 内容区域\n            VStack(alignment: .leading, spacing: 20) {\n                // 访问权限选择\n                HStack(spacing: 16) {\n                    Text(" = ")\n                        .font(.system(size: 14, weight: .medium))\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            // Área de contenido\n            VStack(alignment: .leading, spacing: 20) {\n                // Selección de permisos de acceso\n                HStack(spacing: 16) {\n                    Text(";
"安装失败: \\(message)" = "Instalación fallida: \\(message)";
")\n            }\n            .buttonStyle(.bordered)\n            .disabled(!defaultSiteManager.isNginxRunning && !defaultSiteManager.isApacheRunning)\n\n            // 打开本地目录\n            Button(action: { defaultSiteManager.openDefaultSiteInFinder() }) {\n                Label(" = ")\n            }\n            .buttonStyle(.bordered)\n            .disabled(!defaultSiteManager.isNginxRunning && !defaultSiteManager.isApacheRunning)\n\n            // Abrir directorio local\n            Button(action: { defaultSiteManager.openDefaultSiteInFinder() }) {\n                Label(";
"\n\n        return URL(fileURLWithPath: backupDir).appendingPathComponent(fileName)\n    }\n\n    // 操作按钮\n    private var actionButtons: some View {\n        HStack(spacing: 6) {\n            // 编辑按钮（仅本地数据库显示）\n            if database.type == .local {\n                Button(" = "\n\n        return URL(fileURLWithPath: backupDir).appendingPathComponent(fileName)\n    }\n\n    // Botones de acción\n    private var actionButtons: some View {\n        HStack(spacing: 6) {\n            // Botón de edición (solo para bases de datos locales)\n            if database.type == .local {\n                Button(";
")\n                .foregroundColor(certificate.isValid ? .green : .orange)\n\n            // 域名信息\n            VStack(alignment: .leading, spacing: 2) {\n                Text(certificate.primaryDomain)\n                    .fontWeight(.medium)\n\n                if certificate.domains.count > 1 {\n                    Text(" = ")\n                .foregroundColor(certificate.isValid ? .green : .orange)\n\n            // Información del dominio\n            VStack(alignment: .leading, spacing: 2) {\n                Text(certificate.primaryDomain)\n                    .fontWeight(.medium)\n\n                if certificate.domains.count > 1 {\n                    Text(";
")\n        if let image = NSImage(named: baseName) {\n            return image\n        }\n\n        // 尝试 SPM 资源目录\n        if let resourceURL = Bundle.main.url(forResource: baseName, withExtension: " = ")\n        if let image = NSImage(named: baseName) {\n            return image\n        }\n\n        // Intentar el directorio de recursos de SPM\n        if let resourceURL = Bundle.main.url(forResource: baseName, withExtension: ";
"请分析以下日志:\\n\\n\\(text)" = "Por favor analice los siguientes registros:\\n\\n\\(text)";
")\n                    .font(.title2)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // 加载视图\n    private var loadingView: some View {\n        VStack(spacing: 16) {\n            ProgressView()\n                .scaleEffect(1.5)\n            Text(LocalizedStringKey(" = ")\n                    .font(.title2)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // Vista de carga\n    private var loadingView: some View {\n        VStack(spacing: 16) {\n            ProgressView()\n                .scaleEffect(1.5)\n            Text(LocalizedStringKey(";
")\n        }\n    }\n\n    /// 用户是否手动修改过站点目录（手动修改后不再自动跟随工作空间）\n    @Published var siteRootManuallySet: Bool {\n        didSet {\n            UserDefaults.standard.set(siteRootManuallySet, forKey: " = ")\n        }\n    }\n\n    /// ¿El usuario ha modificado manualmente el directorio del sitio? (Tras la modificación manual, ya no se seguirá automáticamente el espacio de trabajo)\n    @Published var siteRootManuallySet: Bool {\n        didSet {\n            UserDefaults.standard.set(siteRootManuallySet, forKey: ";
")\n                .font(.subheadline)\n                .fontWeight(.medium)\n\n            PHPExtensionsView(phpVersion: site.phpVersion)\n                .frame(height: 400)\n                .background(Color(NSColor.controlBackgroundColor).opacity(AppTokens.Opacity.border))\n                .cornerRadius(8)\n        }\n    }\n\n    // 数据库\n    private var databaseSettings: some View {\n        VStack(alignment: .leading, spacing: 16) {\n            Text(" = ")\n                .font(.subheadline)\n                .fontWeight(.medium)\n\n            PHPExtensionsView(phpVersion: site.phpVersion)\n                .frame(height: 400)\n                .background(Color(NSColor.controlBackgroundColor).opacity(AppTokens.Opacity.border))\n                .cornerRadius(8)\n        }\n    }\n\n    // Base de datos\n    private var databaseSettings: some View {\n        VStack(alignment: .leading, spacing: 16) {\n            Text(";
", Color(red: 0.0, green: 0.21, blue: 0.27)) // MariaDB深蓝色\n        } else {\n            let firstLetter = String(service.name.prefix(1)).uppercased()\n            return (firstLetter, Color.blue)\n        }\n    }\n\n    // 获取服务简介\n    private var serviceDescription: LocalizedStringKey {\n        let name = service.name.lowercased()\n        if name.contains(" = ", Color(red: 0.0, green: 0.21, blue: 0.27)) // Azul oscuro para MariaDB\n        } else {\n            let firstLetter = String(service.name.prefix(1)).uppercased()\n            return (firstLetter, Color.blue)\n        }\n    }\n\n    // Obtener descripción del servicio\n    private var serviceDescription: LocalizedStringKey {\n        let name = service.name.lowercased()\n        if name.contains(";
"\n            }\n\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            if let output = String(data: data, encoding: .utf8) {\n                // 输出格式: " = "\n            }\n\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            if let output = String(data: data, encoding: .utf8) {\n                // Formato de salida: ";
"创建网站目录失败: \\(error.localizedDescription)\\n请检查路径权限或修改网站目录" = "Error al crear el directorio del sitio: \\(error.localizedDescription)\\nVerifique los permisos de la ruta o modifique el directorio del sitio";
"启动 \\(software.name) 失败" = "Error al iniciar \\(software.name)";
"\n    }\n\n    // MARK: - 服务缓存管理\n\n    /// 从缓存加载服务，立即显示服务列表，异步更新状态\n    private func loadServicesWithCache() async {\n        // 尝试从缓存加载\n        if let cachedServices = loadServicesFromCache(), !cachedServices.isEmpty {\n            // 立即显示服务列表，但状态设置为 loading\n            var loadingServices = cachedServices\n            for i in 0..<loadingServices.count {\n                loadingServices[i].state = .loading\n            }\n            self.services = loadingServices\n            self.isLoadingServices = false\n\n            // 异步更新真实状态\n            Task {\n                let updatedServices = await updateServicesState(cachedServices)\n                await MainActor.run {\n                    self.services = updatedServices\n                }\n            }\n        } else {\n            // 无缓存，执行完整检测\n            await detectInstalledServicesAsync()\n        }\n    }\n\n    /// 从缓存文件加载服务列表\n    private func loadServicesFromCache() -> [ServiceStatus]? {\n        guard FileManager.default.fileExists(atPath: servicesCacheURL.path) else {\n            return nil\n        }\n        do {\n            let data = try Data(contentsOf: servicesCacheURL)\n            let services = try JSONDecoder().decode([ServiceStatus].self, from: data)\n            return services\n        } catch {\n            print(" = "\n    }\n\n    // MARK: - Gestión de caché de servicios\n\n    /// Cargar servicios desde la caché: mostrar inmediatamente la lista de servicios y actualizar su estado de forma asíncrona\n    private func loadServicesWithCache() async {\n        // Intentar cargar desde la caché\n        if let cachedServices = loadServicesFromCache(), !cachedServices.isEmpty {\n            // Mostrar inmediatamente la lista de servicios, pero establecer su estado como \"cargando\"\n            var loadingServices = cachedServices\n            for i in 0..<loadingServices.count {\n                loadingServices[i].state = .loading\n            }\n            self.services = loadingServices\n            self.isLoadingServices = false\n\n            // Actualizar de forma asíncrona el estado real\n            Task {\n                let updatedServices = await updateServicesState(cachedServices)\n                await MainActor.run {\n                    self.services = updatedServices\n                }\n            }\n        } else {\n            // Sin caché, realizar detección completa\n            await detectInstalledServicesAsync()\n        }\n    }\n\n    /// Cargar la lista de servicios desde el archivo de caché\n    private func loadServicesFromCache() -> [ServiceStatus]? {\n        guard FileManager.default.fileExists(atPath: servicesCacheURL.path) else {\n            return nil\n        }\n        do {\n            let data = try Data(contentsOf: servicesCacheURL)\n            let services = try JSONDecoder().decode([ServiceStatus].self, from: data)\n            return services\n        } catch {\n            print(";
"\n        连接信息已复制到剪贴板\n\n        您可以在 Navicat 中使用以下信息：\n        主机: \\(host)\n        端口: \\(port)\n        用户名: root\n        密码: \\(password.isEmpty ? " = "\n        连接信息已复制到剪贴板\n\n        您可以在 Navicat 中使用以下信息：\n        主机: \\(host)\n        端口: \\(port)\n        用户名: root\n        密码: \\(password.isEmpty ? ";
")\n                        .foregroundColor(.green)\n                    Text(success)\n                        .font(.caption)\n                        .foregroundColor(.green)\n                    Spacer()\n                }\n                .padding(.horizontal)\n            }\n\n            // 按钮\n            HStack {\n                Spacer()\n\n                Button(" = ")\n                        .foregroundColor(.green)\n                    Text(success)\n                        .font(.caption)\n                        .foregroundColor(.green)\n                    Spacer()\n                }\n                .padding(.horizontal)\n            }\n\n            // 按钮\n            HStack {\n                Spacer()\n\n                Button(";
")\n        }\n\n        // 服务器版本（null-terminated string）\n        while offset < packet.count && packet[offset] != 0 {\n            offset += 1\n        }\n        offset += 1 // skip null\n\n        // 连接 ID (4 bytes)\n        offset += 4\n\n        // auth-plugin-data-part-1 (8 bytes)\n        let authData1 = packet[offset..<offset+8]\n        offset += 8\n\n        // filler (1 byte)\n        offset += 1\n\n        // capability flags (lower 2 bytes)\n        let capLower = UInt32(packet[offset]) | (UInt32(packet[offset+1]) << 8)\n        offset += 2\n\n        // character set (1 byte)\n        offset += 1\n\n        // status flags (2 bytes)\n        offset += 2\n\n        // capability flags (upper 2 bytes)\n        let capUpper = UInt32(packet[offset]) | (UInt32(packet[offset+1]) << 8)\n        offset += 2\n\n        serverCapabilities = capLower | (capUpper << 16)\n\n        // auth-plugin-data-len (1 byte)\n        let authDataLen = Int(packet[offset])\n        offset += 1\n\n        // reserved (10 bytes)\n        offset += 10\n\n        // auth-plugin-data-part-2\n        var authData2 = Data()\n        if serverCapabilities & MySQLConstants.clientSecureConnection != 0 {\n            let part2Len = max(13, authDataLen - 8)\n            if offset + part2Len <= packet.count {\n                authData2 = Data(packet[offset..<offset+part2Len-1]) // exclude null terminator\n                offset += part2Len\n            }\n        }\n\n        authPluginData = Data(authData1) + authData2\n\n        // auth-plugin name\n        if serverCapabilities & MySQLConstants.clientPluginAuth != 0 {\n            var pluginName = " = ")\n        }\n\n        // 服务器版本（null-terminated string）\n        while offset < packet.count && packet[offset] != 0 {\n            offset += 1\n        }\n        offset += 1 // skip null\n\n        // 连接 ID (4 bytes)\n        offset += 4\n\n        // auth-plugin-data-part-1 (8 bytes)\n        let authData1 = packet[offset..<offset+8]\n        offset += 8\n\n        // filler (1 byte)\n        offset += 1\n\n        // capability flags (lower 2 bytes)\n        let capLower = UInt32(packet[offset]) | (UInt32(packet[offset+1]) << 8)\n        offset += 2\n\n        // character set (1 byte)\n        offset += 1\n\n        // status flags (2 bytes)\n        offset += 2\n\n        // capability flags (upper 2 bytes)\n        let capUpper = UInt32(packet[offset]) | (UInt32(packet[offset+1]) << 8)\n        offset += 2\n\n        serverCapabilities = capLower | (capUpper << 16)\n\n        // auth-plugin-data-len (1 byte)\n        let authDataLen = Int(packet[offset])\n        offset += 1\n\n        // reserved (10 bytes)\n        offset += 10\n\n        // auth-plugin-data-part-2\n        var authData2 = Data()\n        if serverCapabilities & MySQLConstants.clientSecureConnection != 0 {\n            let part2Len = max(13, authDataLen - 8)\n            if offset + part2Len <= packet.count {\n                authData2 = Data(packet[offset..<offset+part2Len-1]) // exclude null terminator\n                offset += part2Len\n            }\n        }\n\n        authPluginData = Data(authData1) + authData2\n\n        // auth-plugin name\n        if serverCapabilities & MySQLConstants.clientPluginAuth != 0 {\n            var pluginName = ";
"\n        let result = await executeSQL(sql)\n\n        if result.success {\n            await fetchDatabases()\n        } else {\n            errorMessage = result.error\n        }\n\n        return result.success\n    }\n\n    /// 创建数据库（指定字符集）\n    func createDatabase(name: String, charset: String) async -> Bool {\n        let collation = charset == " = "\n        let result = await executeSQL(sql)\n\n        if result.success {\n            await fetchDatabases()\n        } else {\n            errorMessage = result.error\n        }\n\n        return result.success\n    }\n\n    /// 创建数据库（指定字符集）\n    func createDatabase(name: String, charset: String) async -> Bool {\n        let collation = charset == ";
" {\n            return cachingSha2Password()\n        }\n\n        // 默认使用 mysql_native_password\n        return mysqlNativePassword()\n    }\n\n    /// mysql_native_password 认证\n    private func mysqlNativePassword() -> Data {\n        guard !password.isEmpty else { return Data() }\n\n        // SHA1(password)\n        let passwordHash1 = sha1(Data(password.utf8))\n        // SHA1(SHA1(password))\n        let passwordHash2 = sha1(passwordHash1)\n        // SHA1(scramble + SHA1(SHA1(password)))\n        let combined = authPluginData + passwordHash2\n        let hash3 = sha1(combined)\n\n        // XOR\n        var result = Data(count: 20)\n        for i in 0..<20 {\n            result[i] = passwordHash1[i] ^ hash3[i]\n        }\n\n        return result\n    }\n\n    /// caching_sha2_password 认证（快速路径）\n    private func cachingSha2Password() -> Data {\n        guard !password.isEmpty else { return Data() }\n\n        // SHA256(password)\n        let passwordHash = sha256(Data(password.utf8))\n        // SHA256(SHA256(password))\n        let passwordHash2 = sha256(passwordHash)\n        // SHA256(SHA256(SHA256(password)) + scramble)\n        let combined = passwordHash2 + authPluginData\n        let hash3 = sha256(combined)\n\n        // XOR\n        var result = Data(count: 32)\n        for i in 0..<32 {\n            result[i] = passwordHash[i] ^ hash3[i]\n        }\n\n        return result\n    }\n\n    /// 读取认证结果\n    private func readAuthResult() throws {\n        let packet = try readPacket()\n        guard packet.count > 0 else {\n            throw MySQLError.invalidResponse\n        }\n\n        let statusByte = packet[0]\n\n        if statusByte == MySQLConstants.okPacket {\n            // 认证成功\n            return\n        } else if statusByte == MySQLConstants.errPacket {\n            // 认证失败\n            let errorMsg = parseErrorPacket(packet)\n            throw MySQLError.authenticationFailed(errorMsg)\n        } else if statusByte == MySQLConstants.authSwitchRequest && packet.count > 1 {\n            // 认证方式切换请求\n            try handleAuthSwitch(packet)\n        } else if statusByte == 0x01 && packet.count >= 2 {\n            // caching_sha2_password 额外认证\n            try handleCachingSha2Auth(packet)\n        } else {\n            throw MySQLError.authenticationFailed(" = " {\n            return cachingSha2Password()\n        }\n\n        // 默认使用 mysql_native_password\n        return mysqlNativePassword()\n    }\n\n    /// mysql_native_password 认证\n    private func mysqlNativePassword() -> Data {\n        guard !password.isEmpty else { return Data() }\n\n        // SHA1(password)\n        let passwordHash1 = sha1(Data(password.utf8))\n        // SHA1(SHA1(password))\n        let passwordHash2 = sha1(passwordHash1)\n        // SHA1(scramble + SHA1(SHA1(password)))\n        let combined = authPluginData + passwordHash2\n        let hash3 = sha1(combined)\n\n        // XOR\n        var result = Data(count: 20)\n        for i in 0..<20 {\n            result[i] = passwordHash1[i] ^ hash3[i]\n        }\n\n        return result\n    }\n\n    /// caching_sha2_password 认证（快速路径）\n    private func cachingSha2Password() -> Data {\n        guard !password.isEmpty else { return Data() }\n\n        // SHA256(password)\n        let passwordHash = sha256(Data(password.utf8))\n        // SHA256(SHA256(password))\n        let passwordHash2 = sha256(passwordHash)\n        // SHA256(SHA256(SHA256(password)) + scramble)\n        let combined = passwordHash2 + authPluginData\n        let hash3 = sha256(combined)\n\n        // XOR\n        var result = Data(count: 32)\n        for i in 0..<32 {\n            result[i] = passwordHash[i] ^ hash3[i]\n        }\n\n        return result\n    }\n\n    /// 读取认证结果\n    private func readAuthResult() throws {\n        let packet = try readPacket()\n        guard packet.count > 0 else {\n            throw MySQLError.invalidResponse\n        }\n\n        let statusByte = packet[0]\n\n        if statusByte == MySQLConstants.okPacket {\n            // 认证成功\n            return\n        } else if statusByte == MySQLConstants.errPacket {\n            // 认证失败\n            let errorMsg = parseErrorPacket(packet)\n            throw MySQLError.authenticationFailed(errorMsg)\n        } else if statusByte == MySQLConstants.authSwitchRequest && packet.count > 1 {\n            // 认证方式切换请求\n            try handleAuthSwitch(packet)\n        } else if statusByte == 0x01 && packet.count >= 2 {\n            // caching_sha2_password 额外认证\n            try handleCachingSha2Auth(packet)\n        } else {\n            throw MySQLError.authenticationFailed(";
"域名 \\(domain) 无法访问：\\(reason)" = "域名 \\(domain) 无法访问：\\(reason)";
"没有运行的 Web 服务器，且默认目录不存在: \\(path)" = "没有运行的 Web 服务器，且默认目录不存在: \\(path)";
"\n\n        // 为每个密码保护规则生成 location 块\n        for rule in site.advancedConfig.accessRules where rule.type == .password {\n            let sanitizedDir = rule.directory.replacingOccurrences(of: " = "\n\n        // 为每个密码保护规则生成 location 块\n        for rule in site.advancedConfig.accessRules where rule.type == .password {\n            let sanitizedDir = rule.directory.replacingOccurrences(of: ";
")\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            // 备份列表\n            if backups.isEmpty {\n                emptyView\n            } else {\n                backupListView\n            }\n\n            Divider()\n\n            // 底部按钮\n            HStack {\n                Text(LocalizedStringKey(" = ")\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            // 备份列表\n            if backups.isEmpty {\n                emptyView\n            } else {\n                backupListView\n            }\n\n            Divider()\n\n            // 底部按钮\n            HStack {\n                Text(LocalizedStringKey(";
") {\n                            continue\n                        }\n\n                        // 按空格分割，获取服务名和状态\n                        let parts = trimmed.split(separator: " = ") {\n                            continue\n                        }\n\n                        // 按空格分割，获取服务名和状态\n                        let parts = trimmed.split(separator: ";
", v))\n        }\n\n        guard !stoppedVersions.isEmpty else { return }\n\n        // 在主线程预计算配置路径（ConfigPathProvider 是 @MainActor）\n        var configPathsMap: [String: [String]] = [:]\n        for item in stoppedVersions {\n            configPathsMap[item.id] = getConfigPathsForVersion(item.version)\n        }\n\n        // 在后台诊断停止原因，完成后回到主线程更新\n        let brewPath = self.brewPath\n        let errorLogs = collectRecentErrorLogs(for: stoppedVersions.map { $0.id })\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n\n        DispatchQueue.global(qos: .utility).async { [weak self] in\n            var results: [String: StopReason] = [:]\n            for item in stoppedVersions {\n                let paths = configPathsMap[item.id] ?? []\n                results[item.id] = Self.diagnoseStopReasonSync(\n                    for: item.version,\n                    brewPath: brewPath,\n                    homebrewPrefix: homebrewPrefix,\n                    configPaths: paths,\n                    hasRecentError: errorLogs.contains(item.id)\n                )\n            }\n            DispatchQueue.main.async {\n                self?.applyStopReasons(results)\n            }\n        }\n    }\n\n    /// 收集有最近错误日志的软件ID\n    private func collectRecentErrorLogs(for ids: [String]) -> Set<String> {\n        var result: Set<String> = []\n        let fiveMinutesAgo = Date().addingTimeInterval(-300)\n        for id in ids {\n            if ErrorLogManager.shared.hasLogs(for: id) {\n                let logs = ErrorLogManager.shared.getLogs(for: id, limit: 1)\n                if let lastLog = logs.first, lastLog.timestamp > fiveMinutesAgo {\n                    result.insert(id)\n                }\n            }\n        }\n        return result\n    }\n\n    /// 将诊断结果应用到各软件列表\n    private func applyStopReasons(_ results: [String: StopReason]) {\n        for i in phpVersions.indices {\n            if let reason = results[phpVersions[i].id] {\n                phpVersions[i].stopReason = reason\n            }\n        }\n        for i in webServers.indices {\n            if let reason = results[webServers[i].id] {\n                webServers[i].stopReason = reason\n            }\n        }\n        for i in databases.indices {\n            if let reason = results[databases[i].id] {\n                databases[i].stopReason = reason\n            }\n        }\n        for i in caches.indices {\n            if let reason = results[caches[i].id] {\n                caches[i].stopReason = reason\n            }\n        }\n    }\n\n    /// 诊断软件停止原因（nonisolated，可在后台线程调用）\n    private nonisolated static func diagnoseStopReasonSync(\n        for version: SoftwareVersion,\n        brewPath: String?,\n        homebrewPrefix: String,\n        configPaths: [String],\n        hasRecentError: Bool\n    ) -> StopReason {\n        let name = version.name.lowercased()\n        let brewName = version.brewName ?? version.id\n\n        // 1. 检查端口冲突\n        if let port = version.port, port > 0 {\n            if isPortOccupiedByOther(port: port) {\n                return .portConflict\n            }\n        }\n\n        // 2. 检查配置文件是否存在\n        if !configPaths.isEmpty {\n            let anyExists = configPaths.contains {\n                FileManager.default.fileExists(atPath: $0)\n            }\n            if !anyExists {\n                return .configMissing\n            }\n        }\n\n        // 3. 检查配置文件语法（仅对 nginx/apache）\n        if name.contains(" = ", v))\n        }\n\n        guard !stoppedVersions.isEmpty else { return }\n\n        // 在主线程预计算配置路径（ConfigPathProvider 是 @MainActor）\n        var configPathsMap: [String: [String]] = [:]\n        for item in stoppedVersions {\n            configPathsMap[item.id] = getConfigPathsForVersion(item.version)\n        }\n\n        // 在后台诊断停止原因，完成后回到主线程更新\n        let brewPath = self.brewPath\n        let errorLogs = collectRecentErrorLogs(for: stoppedVersions.map { $0.id })\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n\n        DispatchQueue.global(qos: .utility).async { [weak self] in\n            var results: [String: StopReason] = [:]\n            for item in stoppedVersions {\n                let paths = configPathsMap[item.id] ?? []\n                results[item.id] = Self.diagnoseStopReasonSync(\n                    for: item.version,\n                    brewPath: brewPath,\n                    homebrewPrefix: homebrewPrefix,\n                    configPaths: paths,\n                    hasRecentError: errorLogs.contains(item.id)\n                )\n            }\n            DispatchQueue.main.async {\n                self?.applyStopReasons(results)\n            }\n        }\n    }\n\n    /// 收集有最近错误日志的软件ID\n    private func collectRecentErrorLogs(for ids: [String]) -> Set<String> {\n        var result: Set<String> = []\n        let fiveMinutesAgo = Date().addingTimeInterval(-300)\n        for id in ids {\n            if ErrorLogManager.shared.hasLogs(for: id) {\n                let logs = ErrorLogManager.shared.getLogs(for: id, limit: 1)\n                if let lastLog = logs.first, lastLog.timestamp > fiveMinutesAgo {\n                    result.insert(id)\n                }\n            }\n        }\n        return result\n    }\n\n    /// 将诊断结果应用到各软件列表\n    private func applyStopReasons(_ results: [String: StopReason]) {\n        for i in phpVersions.indices {\n            if let reason = results[phpVersions[i].id] {\n                phpVersions[i].stopReason = reason\n            }\n        }\n        for i in webServers.indices {\n            if let reason = results[webServers[i].id] {\n                webServers[i].stopReason = reason\n            }\n        }\n        for i in databases.indices {\n            if let reason = results[databases[i].id] {\n                databases[i].stopReason = reason\n            }\n        }\n        for i in caches.indices {\n            if let reason = results[caches[i].id] {\n                caches[i].stopReason = reason\n            }\n        }\n    }\n\n    /// 诊断软件停止原因（nonisolated，可在后台线程调用）\n    private nonisolated static func diagnoseStopReasonSync(\n        for version: SoftwareVersion,\n        brewPath: String?,\n        homebrewPrefix: String,\n        configPaths: [String],\n        hasRecentError: Bool\n    ) -> StopReason {\n        let name = version.name.lowercased()\n        let brewName = version.brewName ?? version.id\n\n        // 1. 检查端口冲突\n        if let port = version.port, port > 0 {\n            if isPortOccupiedByOther(port: port) {\n                return .portConflict\n            }\n        }\n\n        // 2. 检查配置文件是否存在\n        if !configPaths.isEmpty {\n            let anyExists = configPaths.contains {\n                FileManager.default.fileExists(atPath: $0)\n            }\n            if !anyExists {\n                return .configMissing\n            }\n        }\n\n        // 3. 检查配置文件语法（仅对 nginx/apache）\n        if name.contains(";
", selection: $batchPHPVersion) {\n                        ForEach(availablePHPVersions, id: \\.self) { version in\n                            Text(version).tag(version)\n                        }\n                    }\n                    .frame(width: 120)\n                }\n            }\n            .padding(16)\n\n            Divider()\n\n            // 底部按钮\n            HStack {\n                Button(" = ", selection: $batchPHPVersion) {\n                        ForEach(availablePHPVersions, id: \\.self) { version in\n                            Text(version).tag(version)\n                        }\n                    }\n                    .frame(width: 120)\n                }\n            }\n            .padding(16)\n\n            Divider()\n\n            // 底部按钮\n            HStack {\n                Button(";
")\n                                    .foregroundColor(config.corsHeaders.contains(header) ? .blue : .secondary)\n                                    .font(.system(size: 14))\n                            }\n                            .buttonStyle(.plain)\n                            \n                            Text(header)\n                                .font(.system(size: 13))\n                                .foregroundColor(.primary)\n                        }\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                    }\n                }\n            }\n            \n            // 缓存时间\n            VStack(alignment: .leading, spacing: 8) {\n                HStack(spacing: 4) {\n                    Text(LocalizedStringKey(" = ")\n                                    .foregroundColor(config.corsHeaders.contains(header) ? .blue : .secondary)\n                                    .font(.system(size: 14))\n                            }\n                            .buttonStyle(.plain)\n                            \n                            Text(header)\n                                .font(.system(size: 13))\n                                .foregroundColor(.primary)\n                        }\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                    }\n                }\n            }\n            \n            // 缓存时间\n            VStack(alignment: .leading, spacing: 8) {\n                HStack(spacing: 4) {\n                    Text(LocalizedStringKey(";
"您选择的PHP \\(selectedPHPVersion) 当前已停止运行。\\n\\n使用已停止的PHP版本可能导致网站无法正常运行PHP程序。\\n\\n建议先启动该PHP版本，或选择其他运行中的版本。" = "您选择的PHP \\(selectedPHPVersion) 当前已停止运行。\\n\\n使用已停止的PHP版本可能导致网站无法正常运行PHP程序。\\n\\n建议先启动该PHP版本，或选择其他运行中的版本。";
" {\n                tools[i].isRunning = tools[i].installed\n            } else {\n                tools[i].isRunning = running.contains(brewName)\n            }\n            tools[i].stopReason = nil\n        }\n\n        // 收集需要诊断的已停止服务，在后台线程诊断\n        var stoppedVersions: [(id: String, category: String, version: SoftwareVersion)] = []\n        for v in phpVersions where v.installed && !v.isRunning {\n            stoppedVersions.append((v.id, " = " {\n                tools[i].isRunning = tools[i].installed\n            } else {\n                tools[i].isRunning = running.contains(brewName)\n            }\n            tools[i].stopReason = nil\n        }\n\n        // 收集需要诊断的已停止服务，在后台线程诊断\n        var stoppedVersions: [(id: String, category: String, version: SoftwareVersion)] = []\n        for v in phpVersions where v.installed && !v.isRunning {\n            stoppedVersions.append((v.id, ";
"\n        }\n\n        // 1. Unlink 所有已安装的 PHP 版本\n        let installedPhps = phpVersions.filter { $0.installed }\n        for php in installedPhps {\n             let brewName = php.brewName ?? php.id\n             _ = await runBrewCommand(brewPath, arguments: [" = "\n        }\n\n        // 1. Unlink 所有已安装的 PHP 版本\n        let installedPhps = phpVersions.filter { $0.installed }\n        for php in installedPhps {\n             let brewName = php.brewName ?? php.id\n             _ = await runBrewCommand(brewPath, arguments: [";
"⚠️ 修复站点 \\(site.name) 失败: \\(error.localizedDescription)" = "⚠️ 修复站点 \\(site.name) 失败: \\(error.localizedDescription)";
"    # 原始条件: \\(condition)\\n" = "    # 原始条件: \\(condition)\\n";
"更新于: \\(formatDate(site.directorySizeUpdatedAt!))" = "更新于: \\(formatDate(site.directorySizeUpdatedAt!))";
" }\n\n    /// Apache 日志目录\n    var apacheLogsPath: String { " = " }\n\n    /// Apache 日志目录\n    var apacheLogsPath: String { ";
"\n        # PureDB 文件认证\n        PureDB                      \\(configDir)/pureftpd.pdb\n\n        " = "\n        # Autenticación de archivo PureDB\n        PureDB                      \\(configDir)/pureftpd.pdb\n\n        ";
"\n    @State private var showBatchSettings = false  // 显示批量设置弹窗\n    @State private var currentImportingId: UUID?  // 当前正在导入的站点ID\n    @State private var importProgress = " = "\n    @State private var showBatchSettings = false  // Mostrar ventana emergente de configuración por lotes\n    @State private var currentImportingId: UUID?  // ID del sitio que se está importando actualmente\n    @State private var importProgress = ";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 表单内容\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // 数据库名称\n                FormField(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // Contenido del formulario\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // Nombre de la base de datos\n                FormField(";
"\n        if let port = readPHPFPMPortFromFile(wwwConfPath) {\n            return (port, nil)\n        }\n\n        // 默认端口\n        return (" = "\n        if let port = readPHPFPMPortFromFile(wwwConfPath) {\n            return (port, nil)\n        }\n\n        // Puerto predeterminado\n        return (";
"))\n                    .font(.system(size: 13))\n            }\n            .toggleStyle(.switch)\n            .controlSize(.small)\n\n            // 更改日志路径按钮\n            MacButton(icon: " = "))\n                    .font(.system(size: 13))\n            }\n            .toggleStyle(.switch)\n            .controlSize(.small)\n\n            // Botón para cambiar la ruta del registro\n            MacButton(icon: ";
"\n        conversionWarnings.removeAll()\n    }\n\n    // 复制转换后的规则\n    private func copyConvertedRules() {\n        let pasteboard = NSPasteboard.general\n        pasteboard.clearContents()\n        pasteboard.setString(convertedRules, forType: .string)\n    }\n}\n\n// 规则差异对比视图\nstruct RulesDiffSheet: View {\n    let source: String\n    let converted: String\n    @Environment(\\.dismiss) var dismiss\n\n    var body: some View {\n        Group {\n            if BuildInfo.uiVersion == " = "\n        conversionWarnings.removeAll()\n    }\n\n    // Copiar reglas convertidas\n    private func copyConvertedRules() {\n        let pasteboard = NSPasteboard.general\n        pasteboard.clearContents()\n        pasteboard.setString(convertedRules, forType: .string)\n    }\n}\n\n// Vista de comparación de diferencias entre reglas\nstruct RulesDiffSheet: View {\n    let source: String\n    let converted: String\n    @Environment(\\.dismiss) var dismiss\n\n    var body: some View {\n        Group {\n            if BuildInfo.uiVersion == ";
")\n    }\n\n    // 获取单位后缀\n    private func getUnitSuffix(for key: String) -> String {\n        let definition = PerformanceConfigItem.definitions.first { $0.key == key }\n        guard let unit = definition?.unit else { return " = ")\n    }\n\n    // Obtener sufijo de unidad\n    private func getUnitSuffix(for key: String) -> String {\n        let definition = PerformanceConfigItem.definitions.first { $0.key == key }\n        guard let unit = definition?.unit else { return ";
")\n                    .font(.system(size: 14, weight: .medium))\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    private var contentView: some View {\n        VStack(alignment: .leading, spacing: 16) {\n            // 数据库信息\n            HStack {\n                Text(" = ")\n                    .font(.system(size: 14, weight: .medium))\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    private var contentView: some View {\n        VStack(alignment: .leading, spacing: 16) {\n            // Información de la base de datos\n            HStack {\n                Text(";
")\n        try newContent.write(toFile: path, atomically: true, encoding: .utf8)\n    }\n\n    /// 为指定 MySQL 版本创建目录\n    func initializeMySQLVersion(_ version: String) throws {\n        guard let config = config else {\n            throw WorkspaceError.notConfigured\n        }\n\n        let fm = FileManager.default\n        let dirs = WorkspaceDirectories.getMySQLDirectories(config: config, version: version)\n\n        for dir in dirs {\n            if !fm.fileExists(atPath: dir) {\n                try fm.createDirectory(atPath: dir, withIntermediateDirectories: true)\n            }\n        }\n    }\n\n    /// 为指定 PHP 版本创建目录\n    func initializePHPVersion(_ version: String) throws {\n        guard let config = config else {\n            throw WorkspaceError.notConfigured\n        }\n\n        let fm = FileManager.default\n        let dirs = WorkspaceDirectories.getPHPDirectories(config: config, version: version)\n\n        for dir in dirs {\n            if !fm.fileExists(atPath: dir) {\n                try fm.createDirectory(atPath: dir, withIntermediateDirectories: true)\n            }\n        }\n    }\n\n    // MARK: - 路径获取 (兼容旧路径)\n\n    /// 获取 MySQL 配置文件路径\n    func getMySQLConfigPath(version: String) -> String {\n        if let config = config, config.isInitialized {\n            return config.mysqlConfigFile(version: version)\n        }\n        // 回退到 Homebrew 默认路径\n        return " = ")\n        try newContent.write(toFile: path, atomically: true, encoding: .utf8)\n    }\n\n    /// Crear directorios para una versión específica de MySQL\n    func initializeMySQLVersion(_ version: String) throws {\n        guard let config = config else {\n            throw WorkspaceError.notConfigured\n        }\n\n        let fm = FileManager.default\n        let dirs = WorkspaceDirectories.getMySQLDirectories(config: config, version: version)\n\n        for dir in dirs {\n            if !fm.fileExists(atPath: dir) {\n                try fm.createDirectory(atPath: dir, withIntermediateDirectories: true)\n            }\n        }\n    }\n\n    /// Crear directorios para una versión específica de PHP\n    func initializePHPVersion(_ version: String) throws {\n        guard let config = config else {\n            throw WorkspaceError.notConfigured\n        }\n\n        let fm = FileManager.default\n        let dirs = WorkspaceDirectories.getPHPDirectories(config: config, version: version)\n\n        for dir in dirs {\n            if !fm.fileExists(atPath: dir) {\n                try fm.createDirectory(atPath: dir, withIntermediateDirectories: true)\n            }\n        }\n    }\n\n    // MARK: - Obtención de rutas (compatibilidad con rutas antiguas)\n\n    /// Obtener la ruta del archivo de configuración de MySQL\n    func getMySQLConfigPath(version: String) -> String {\n        if let config = config, config.isInitialized {\n            return config.mysqlConfigFile(version: version)\n        }\n        // Volver a la ruta predeterminada de Homebrew\n        return ";
")\n    }\n\n    private var sslStatusColor: Color {\n        if let cert = siteCertificate {\n            return cert.statusColor\n        }\n        return .orange  // 未部署\n    }\n\n    // 操作按钮\n    private var actionButtons: some View {\n        HStack(alignment: .center, spacing: 6) {\n            if site.isDeleted {\n                // 回收站模式：只显示恢复和彻底删除\n                Button(" = ")\n    }\n\n    private var sslStatusColor: Color {\n        if let cert = siteCertificate {\n            return cert.statusColor\n        }\n        return .orange  // No implementado\n    }\n\n    // Botones de acción\n    private var actionButtons: some View {\n        HStack(alignment: .center, spacing: 6) {\n            if site.isDeleted {\n                // Modo papelera: mostrar solo restaurar y eliminar permanentemente\n                Button(";
"\n            return false\n        }\n\n        let targetPath = phpMyAdminSystemPath\n\n        // 如果目标已存在，先备份\n        if FileManager.default.fileExists(atPath: targetPath) {\n            let backupPath = " = "\n            return false\n        }\n\n        let targetPath = phpMyAdminSystemPath\n\n        // Si el destino ya existe, primero hacer una copia de seguridad\n        if FileManager.default.fileExists(atPath: targetPath) {\n            let backupPath = ";
"\n    }\n\n    /// 获取网站根目录\n    func getWwwPath() -> String {\n        if let config = config, config.isInitialized {\n            return config.wwwPath\n        }\n        let home = FileManager.default.homeDirectoryForCurrentUser.path\n        return " = "\n    }\n\n    /// Obtener la ruta del directorio raíz del sitio web\n    func getWwwPath() -> String {\n        if let config = config, config.isInitialized {\n            return config.wwwPath\n        }\n        let home = FileManager.default.homeDirectoryForCurrentUser.path\n        return ";
"\n\n    // 日志配置\n    var logConfig: LogConfig = LogConfig()\n}\n\nstruct Site: Identifiable, Codable, Hashable {\n    var id: UUID\n    var name: String\n    var domains: [String]\n    var documentRoot: String  // 网站目录（PHP可访问的根目录）\n    var publicDirectory: String = " = "\n\n    // Configuración de registros\n    var logConfig: LogConfig = LogConfig()\n}\n\nstruct Site: Identifiable, Codable, Hashable {\n    var id: UUID\n    var name: String\n    var domains: [String]\n    var documentRoot: String  // Directorio del sitio (directorio raíz accesible mediante PHP)\n    var publicDirectory: String = ";
")\n                                .font(.caption)\n                                .foregroundColor(.secondary)\n                        }\n                        Text(LocalizedStringKey(software.statusText))\n                            .font(.caption)\n                            .foregroundColor(statusColor)\n                    }\n                }\n            }\n\n            Spacer()\n\n            // 进度或操作按钮\n            if isLoading {\n                loadingView\n            } else if software.installed {\n                installedActions\n            } else {\n                Button(" = ")\n                                .font(.caption)\n                                .foregroundColor(.secondary)\n                        }\n                        Text(LocalizedStringKey(software.statusText))\n                            .font(.caption)\n                            .foregroundColor(statusColor)\n                    }\n                }\n            }\n\n            Spacer()\n\n            // Progreso u operaciones\n            if isLoading {\n                loadingView\n            } else if software.installed {\n                installedActions\n            } else {\n                Button(";
"\n    }\n\n    // MARK: - 读取访问日志\n    func readAccessLog(for site: Site, limit: Int = 1000) async throws -> [LogEntry] {\n        let logPath = getAccessLogPath(for: site)\n\n        guard FileManager.default.fileExists(atPath: logPath) else {\n            return []\n        }\n\n        return try await withCheckedThrowingContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                do {\n                    let entries = try self.parseAccessLog(path: logPath, limit: limit)\n                    continuation.resume(returning: entries)\n                } catch {\n                    continuation.resume(throwing: error)\n                }\n            }\n        }\n    }\n\n    // MARK: - 读取错误日志\n    func readErrorLog(for site: Site, limit: Int = 1000) async throws -> [SiteErrorLogEntry] {\n        let logPath = getErrorLogPath(for: site)\n\n        guard FileManager.default.fileExists(atPath: logPath) else {\n            return []\n        }\n\n        return try await withCheckedThrowingContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                do {\n                    let entries = try self.parseErrorLog(path: logPath, limit: limit)\n                    continuation.resume(returning: entries)\n                } catch {\n                    continuation.resume(throwing: error)\n                }\n            }\n        }\n    }\n\n    // MARK: - 解析访问日志\n    private func parseAccessLog(path: String, limit: Int) throws -> [LogEntry] {\n        // 使用tail命令读取最后N行，提高性能\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = "\n    }\n\n    // MARK: - Leer registro de accesos\n    func readAccessLog(for site: Site, limit: Int = 1000) async throws -> [LogEntry] {\n        let logPath = getAccessLogPath(for: site)\n\n        guard FileManager.default.fileExists(atPath: logPath) else {\n            return []\n        }\n\n        return try await withCheckedThrowingContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                do {\n                    let entries = try self.parseAccessLog(path: logPath, limit: limit)\n                    continuation.resume(returning: entries)\n                } catch {\n                    continuation.resume(throwing: error)\n                }\n            }\n        }\n    }\n\n    // MARK: - Leer registro de errores\n    func readErrorLog(for site: Site, limit: Int = 1000) async throws -> [SiteErrorLogEntry] {\n        let logPath = getErrorLogPath(for: site)\n\n        guard FileManager.default.fileExists(atPath: logPath) else {\n            return []\n        }\n\n        return try await withCheckedThrowingContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                do {\n                    let entries = try self.parseErrorLog(path: logPath, limit: limit)\n                    continuation.resume(returning: entries)\n                } catch {\n                    continuation.resume(throwing: error)\n                }\n            }\n        }\n    }\n\n    // MARK: - Analizar registro de accesos\n    private func parseAccessLog(path: String, limit: Int) throws -> [LogEntry] {\n        // Usar el comando tail para leer las últimas N líneas, mejorando el rendimiento\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: ";
")\n        }\n\n        // 2. 创建验证文件\n        let verificationFilePath = " = ")\n        }\n\n        // 2. Crear archivo de verificación\n        let verificationFilePath = ";
"站点配置已导出到: \\(url.path)" = "La configuración del sitio se ha exportado a: \\(url.path)";
"\n        let regex = try? NSRegularExpression(pattern: pattern)\n        let range = NSRange(location: 0, length: username.utf16.count)\n        return regex?.firstMatch(in: username, range: range) != nil\n    }\n\n    // 提交表单\n    private func submitForm() {\n        // 验证密码是否匹配\n        guard password == confirmPassword else {\n            errorMessage = " = "\n        let regex = try? NSRegularExpression(pattern: pattern)\n        let range = NSRange(location: 0, length: username.utf16.count)\n        return regex?.firstMatch(in: username, range: range) != nil\n    }\n\n    // Enviar formulario\n    private func submitForm() {\n        // Validar que las contraseñas coincidan\n        guard password == confirmPassword else {\n            errorMessage = ";
")\n            .map { $0.trimmingCharacters(in: .whitespaces) }\n            .filter { !$0.isEmpty }\n        return !domainList.isEmpty && !siteDirectory.isEmpty && !runningDirectory.isEmpty\n    }\n    \n    // 生成随机密码\n    private func generateRandomPassword(length: Int = 16) -> String {\n        let characters = " = "))\\nVálido durante: \\(validityDays) días";
"))\\n有效期: \\(validityDays) 天" = ")\n            .map { $0.trimmingCharacters(in: .whitespaces) }\n            .filter { !$0.isEmpty }\n        return !domainList.isEmpty && !siteDirectory.isEmpty && !runningDirectory.isEmpty\n    }\n    \n    // Generar contraseña aleatoria\n    private func generateRandomPassword(length: Int = 16) -> String {\n        let characters = ";
")\n                .font(.system(size: 14))\n                .foregroundColor(.secondary)\n        }\n    }\n\n    // 展开后的内容视图\n    private var expandedContentView: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            Divider()\n                .padding(.vertical, 4)\n\n            // 完整错误信息\n            VStack(alignment: .leading, spacing: 4) {\n                Text(" = ")\n                .font(.system(size: 14))\n                .foregroundColor(.secondary)\n        }\n    }\n\n    // 展开后的内容视图\n    private var expandedContentView: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            Divider()\n                .padding(.vertical, 4)\n\n            // 完整错误信息\n            VStack(alignment: .leading, spacing: 4) {\n                Text(";
"))\n                        .font(.caption)\n                        .foregroundColor(.secondary)\n                }\n\n                Spacer()\n\n                // 统计信息\n                HStack(spacing: 8) {\n                    if report.successCount > 0 {\n                        Label(" = "))\n                        .font(.caption)\n                        .foregroundColor(.secondary)\n                }\n\n                Spacer()\n\n                // 统计信息\n                HStack(spacing: 8) {\n                    if report.successCount > 0 {\n                        Label(";
")\n                            .font(.headline)\n\n                        VStack(spacing: 0) {\n                            // 表头\n                            HStack {\n                                Text(" = ")\n                            .font(.headline)\n\n                        VStack(spacing: 0) {\n                            // 表头\n                            HStack {\n                                Text(";
"正在重启 \\(service.name)..." = "正在重启 \\(service.name)...";
"\n        }\n\n        // 写入配置文件\n        try config.write(toFile: configFile, atomically: true, encoding: .utf8)\n    }\n\n    /// 创建 PureFTP MySQL 配置文件模板\n    private func createPureFTPMySQLConfig(configDir: String) async throws {\n        let mysqlConfigFile = " = "\n        }\n\n        // 写入配置文件\n        try config.write(toFile: configFile, atomically: true, encoding: .utf8)\n    }\n\n    /// 创建 PureFTP MySQL 配置文件模板\n    private func createPureFTPMySQLConfig(configDir: String) async throws {\n        let mysqlConfigFile = ";
"  // 当前扫描的目录\n    @State private var customNginxPath: String? = nil  // 自定义 Nginx 扫描目录\n    @State private var customApachePath: String? = nil  // 自定义 Apache 扫描目录\n\n    // 获取已安装的Web服务器列表\n    private var installedWebServers: [WebServerType] {\n        var servers: [WebServerType] = []\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if name == " = "  // 当前扫描的目录\n    @State private var customNginxPath: String? = nil  // 自定义 Nginx 扫描目录\n    @State private var customApachePath: String? = nil  // 自定义 Apache 扫描目录\n\n    // 获取已安装的Web服务器列表\n    private var installedWebServers: [WebServerType] {\n        var servers: [WebServerType] = []\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if name == ";
"已选择 \\(selectedCandidates.count) 个站点" = "已选择 \\(selectedCandidates.count) 个站点";
", maxSplits: 1)\n                if parts.count == 2 {\n                    let k = parts[0].trimmingCharacters(in: .whitespaces)\n                    if k == key {\n                        var value = parts[1].trimmingCharacters(in: .whitespaces)\n                        // 移除单位后缀\n                        value = value.replacingOccurrences(of: " = ", maxSplits: 1)\n                if parts.count == 2 {\n                    let k = parts[0].trimmingCharacters(in: .whitespaces)\n                    if k == key {\n                        var value = parts[1].trimmingCharacters(in: .whitespaces)\n                        // 移除单位后缀\n                        value = value.replacingOccurrences(of: ";
"\n\n                // 监听输出\n                pipe.fileHandleForReading.readabilityHandler = { fileHandle in\n                    let data = fileHandle.availableData\n                    guard !data.isEmpty, let output = String(data: data, encoding: .utf8) else { return }\n\n                    outputBuffer += output\n                    let lines = output.components(separatedBy: .newlines)\n\n                    for line in lines {\n                        let trimmed = line.trimmingCharacters(in: .whitespaces)\n                        guard !trimmed.isEmpty else { continue }\n\n                        // 解析输出并更新进度\n                        self.parseOutputAndUpdateProgress(trimmed, extensionName: extensionName, phpVersion: phpVersion)\n                    }\n                }\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    // 停止监听\n                    pipe.fileHandleForReading.readabilityHandler = nil\n\n                    let success = process.terminationStatus == 0\n                    continuation.resume(returning: success)\n                } catch {\n                    pipe.fileHandleForReading.readabilityHandler = nil\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // MARK: - 解析输出并更新进度\n\n    private func parseOutputAndUpdateProgress(_ output: String, extensionName: String, phpVersion: String) {\n        let lowercased = output.lowercased()\n\n        var phase = " = "\n\n                // 监听输出\n                pipe.fileHandleForReading.readabilityHandler = { fileHandle in\n                    let data = fileHandle.availableData\n                    guard !data.isEmpty, let output = String(data: data, encoding: .utf8) else { return }\n\n                    outputBuffer += output\n                    let lines = output.components(separatedBy: .newlines)\n\n                    for line in lines {\n                        let trimmed = line.trimmingCharacters(in: .whitespaces)\n                        guard !trimmed.isEmpty else { continue }\n\n                        // 解析输出并更新进度\n                        self.parseOutputAndUpdateProgress(trimmed, extensionName: extensionName, phpVersion: phpVersion)\n                    }\n                }\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    // 停止监听\n                    pipe.fileHandleForReading.readabilityHandler = nil\n\n                    let success = process.terminationStatus == 0\n                    continuation.resume(returning: success)\n                } catch {\n                    pipe.fileHandleForReading.readabilityHandler = nil\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // MARK: - 解析输出并更新进度\n\n    private func parseOutputAndUpdateProgress(_ output: String, extensionName: String, phpVersion: String) {\n        let lowercased = output.lowercased()\n\n        var phase = ";
"配置文件格式无效: \\(message)" = "配置文件格式无效: \\(message)";
"加载站点数据失败: \\(error.localizedDescription)" = "加载站点数据失败: \\(error.localizedDescription)";
"确定要删除数据库 \\(name) 吗？此操作不可恢复！" = "确定要删除数据库 \\(name) 吗？此操作不可恢复！";
").tag(site as Site?)\n                    }\n                }\n                .pickerStyle(.menu)\n                .onChange(of: selectedSite) { _ in\n                    // 重置验证状态\n                    verificationStatus = .notStarted\n                }\n            }\n        }\n    }\n\n    // MARK: - 域名验证部分\n    private var domainVerificationSection: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            Text(LocalizedStringKey(" = ").tag(site as Site?)\n                    }\n                }\n                .pickerStyle(.menu)\n                .onChange(of: selectedSite) { _ in\n                    // 重置验证状态\n                    verificationStatus = .notStarted\n                }\n            }\n        }\n    }\n\n    // MARK: - 域名验证部分\n    private var domainVerificationSection: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            Text(LocalizedStringKey(";
") else { return nil }\n\n        let brewName = software.brewName ?? software.id\n        let phpVersion = AppSettings.extractPHPVersion(from: brewName)\n        let defaultPort = AppSettings.defaultPHPPort\n\n        // 检查是否有其他 PHP 版本正在运行并占用端口\n        if let conflict = checkRunningPortConflict(port: defaultPort, excluding: software.id) {\n            let suggestedPort = findNextAvailablePHPPort(excluding: software.id)\n            return PHPPortConflictInfo(\n                phpVersion: phpVersion,\n                conflictPort: defaultPort,\n                conflictingService: conflict,\n                suggestedPort: suggestedPort\n            )\n        }\n        return nil\n    }\n\n    /// 查找下一个可用的 PHP 端口\n    private func findNextAvailablePHPPort(excluding: String) -> Int {\n        var usedPorts: Set<Int> = []\n        for php in phpVersions where php.id != excluding && php.isRunning {\n            if let port = php.port {\n                usedPorts.insert(port)\n            }\n        }\n        // 从 AppSettings 获取已配置的端口\n        let settings = AppSettings.shared\n        for (_, port) in settings.phpPorts {\n            usedPorts.insert(port)\n        }\n\n        var port = AppSettings.defaultPHPPort\n        while usedPorts.contains(port) {\n            port += 1\n        }\n        return port\n    }\n\n    /// 停止指定的 PHP 服务\n    func stopPHPService(named serviceName: String) async -> Bool {\n        guard let brewPath = brewPath else { return false }\n\n        // 查找对应的软件\n        if let php = phpVersions.first(where: { $0.name == serviceName }) {\n            let brewName = php.brewName ?? php.id\n            return await runBrewCommand(brewPath, arguments: [" = ") else { return nil }\n\n        let brewName = software.brewName ?? software.id\n        let phpVersion = AppSettings.extractPHPVersion(from: brewName)\n        let defaultPort = AppSettings.defaultPHPPort\n\n        // 检查是否有其他 PHP 版本正在运行并占用端口\n        if let conflict = checkRunningPortConflict(port: defaultPort, excluding: software.id) {\n            let suggestedPort = findNextAvailablePHPPort(excluding: software.id)\n            return PHPPortConflictInfo(\n                phpVersion: phpVersion,\n                conflictPort: defaultPort,\n                conflictingService: conflict,\n                suggestedPort: suggestedPort\n            )\n        }\n        return nil\n    }\n\n    /// 查找下一个可用的 PHP 端口\n    private func findNextAvailablePHPPort(excluding: String) -> Int {\n        var usedPorts: Set<Int> = []\n        for php in phpVersions where php.id != excluding && php.isRunning {\n            if let port = php.port {\n                usedPorts.insert(port)\n            }\n        }\n        // 从 AppSettings 获取已配置的端口\n        let settings = AppSettings.shared\n        for (_, port) in settings.phpPorts {\n            usedPorts.insert(port)\n        }\n\n        var port = AppSettings.defaultPHPPort\n        while usedPorts.contains(port) {\n            port += 1\n        }\n        return port\n    }\n\n    /// 停止指定的 PHP 服务\n    func stopPHPService(named serviceName: String) async -> Bool {\n        guard let brewPath = brewPath else { return false }\n\n        // 查找对应的软件\n        if let php = phpVersions.first(where: { $0.name == serviceName }) {\n            let brewName = php.brewName ?? php.id\n            return await runBrewCommand(brewPath, arguments: [";
")\n                            .font(.system(size: 10))\n                            .foregroundColor(.blue)\n                        Text(dir.label)\n                            .font(.system(size: 11, weight: .medium))\n                            .foregroundColor(.primary)\n                            .frame(width: 60, alignment: .leading)\n                        Text(dir.path)\n                            .font(.system(size: 10, design: .monospaced))\n                            .foregroundColor(.secondary)\n                            .lineLimit(1)\n\n                        Spacer()\n\n                        // 选择自定义目录按钮\n                        Button(action: {\n                            let serverType: WebServerType = dir.label.contains(" = ")\n                            .font(.system(size: 10))\n                            .foregroundColor(.blue)\n                        Text(dir.label)\n                            .font(.system(size: 11, weight: .medium))\n                            .foregroundColor(.primary)\n                            .frame(width: 60, alignment: .leading)\n                        Text(dir.path)\n                            .font(.system(size: 10, design: .monospaced))\n                            .foregroundColor(.secondary)\n                            .lineLimit(1)\n\n                        Spacer()\n\n                        // 选择自定义目录按钮\n                        Button(action: {\n                            let serverType: WebServerType = dir.label.contains(";
"无法\\(operation)站点：请先在「服务管理」中启动 \\(stoppedServices.joined(separator: " = "无法\\(operation)站点：请先在「服务管理」中启动 \\(stoppedServices.joined(separator: ";
"端口 \\(newPort) 已被占用" = "端口 \\(newPort) 已被占用";
"执行失败: \\(error.localizedDescription)" = "执行失败: \\(error.localizedDescription)";
", savedApachePort, 1),\n                    // 高优先级 - 数据库\n                    (" = ", savedApachePort, 1),\n                    // 高优先级 - 数据库\n                    (";
"✓ 检测到版本: \\(version)" = "✓ 检测到版本: \\(version)";
"找到 \\(candidates.count) 个候选站点" = "Se encontraron \\(candidates.count) sitios candidatos";
"\n            counter += 1\n        }\n\n        // 创建新站点\n        let newSite = Site(\n            name: newName,\n            domains: site.domains.map { " = "\n            counter += 1\n        }\n\n        // Crear un nuevo sitio\n        let newSite = Site(\n            name: newName,\n            domains: site.domains.map { ";
"\n        }\n    }\n}\n\n/// 配置解析结果\nstruct ParsedConfigLine {\n    let lineNumber: Int          // 行号\n    let key: String              // 配置键\n    let value: String            // 配置值\n    let isEnabled: Bool          // 是否启用（非注释状态）\n    let originalLine: String     // 原始行内容\n    let section: String?         // 所属节（如 [mysqld]）\n}\n\n/// 配置文件解析器\n@MainActor\nclass ConfigParser {\n\n    /// 解析配置文件\n    /// - Parameters:\n    ///   - filePath: 配置文件路径\n    ///   - fileType: 配置文件类型\n    /// - Returns: 解析结果数组\n    static func parseConfigFile(at filePath: String, type fileType: ConfigFileType) throws -> [ParsedConfigLine] {\n        // 读取文件内容\n        guard let content = try? String(contentsOfFile: filePath, encoding: .utf8) else {\n            throw ConfigParserError.fileNotFound(filePath)\n        }\n\n        return parseConfigContent(content, type: fileType)\n    }\n\n    /// 解析配置文件内容\n    /// - Parameters:\n    ///   - content: 配置文件内容\n    ///   - fileType: 配置文件类型\n    /// - Returns: 解析结果数组\n    static func parseConfigContent(_ content: String, type fileType: ConfigFileType) -> [ParsedConfigLine] {\n        var results: [ParsedConfigLine] = []\n        let lines = content.components(separatedBy: .newlines)\n        var currentSection: String? = nil\n\n        for (index, line) in lines.enumerated() {\n            let trimmedLine = line.trimmingCharacters(in: .whitespaces)\n\n            // 跳过空行\n            if trimmedLine.isEmpty {\n                continue\n            }\n\n            // 检测节标题 [section]\n            if trimmedLine.hasPrefix(" = "\n        }\n    }\n}\n\n/// Resultado del análisis de la configuración\nstruct ParsedConfigLine {\n    let lineNumber: Int          // Número de línea\n    let key: String              // Clave de configuración\n    let value: String            // Valor de configuración\n    let isEnabled: Bool          // ¿Está habilitado? (no es una línea comentada)\n    let originalLine: String     // Contenido original de la línea\n    let section: String?         // Sección a la que pertenece (por ejemplo, [mysqld])\n}\n\n/// Analizador de archivos de configuración\n@MainActor\nclass ConfigParser {\n\n    /// Analizar un archivo de configuración\n    /// - Parámetros:\n    ///   - filePath: Ruta del archivo de configuración\n    ///   - fileType: Tipo de archivo de configuración\n    /// - Devuelve: Matriz de resultados del análisis\n    static func parseConfigFile(at filePath: String, type fileType: ConfigFileType) throws -> [ParsedConfigLine] {\n        // Leer el contenido del archivo\n        guard let content = try? String(contentsOfFile: filePath, encoding: .utf8) else {\n            throw ConfigParserError.fileNotFound(filePath)\n        }\n\n        return parseConfigContent(content, type: fileType)\n    }\n\n    /// Analizar el contenido de un archivo de configuración\n    /// - Parámetros:\n    ///   - content: Contenido del archivo de configuración\n    ///   - fileType: Tipo de archivo de configuración\n    /// - Devuelve: Matriz de resultados del análisis\n    static func parseConfigContent(_ content: String, type fileType: ConfigFileType) -> [ParsedConfigLine] {\n        var results: [ParsedConfigLine] = []\n        let lines = content.components(separatedBy: .newlines)\n        var currentSection: String? = nil\n\n        for (index, line) in lines.enumerated() {\n            let trimmedLine = line.trimmingCharacters(in: .whitespaces)\n\n            // Omitir líneas en blanco\n            if trimmedLine.isEmpty {\n                continue\n            }\n\n            // Detectar encabezados de sección [sección]\n            if trimmedLine.hasPrefix(";
"当前版本暂不支持自动创建 FTP 账号，请先创建站点后手动配置。" = "La versión actual no admite la creación automática de cuentas FTP. Por favor, cree primero el sitio y luego configure manualmente.";
"✗ \\(domain) 未配置" = "✗ \\(domain) no está configurado";
"\n        if let image = NSImage(contentsOfFile: imagePath) {\n            return image\n        }\n        // 备用：直接从 bundle 根目录加载\n        if let url = Bundle.module.url(forResource: name, withExtension: " = "\n        if let image = NSImage(contentsOfFile: imagePath) {\n            return image\n        }\n        // Alternativa: cargar directamente desde el directorio raíz del bundle\n        if let url = Bundle.module.url(forResource: name, withExtension: ";
"),\n           let image = NSImage(contentsOf: resourceURL) {\n            return image\n        }\n\n        // 回退逻辑：尝试从用户的 Documents 目录加载\n        let documentDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first\n        if let customURL = documentDir?.appendingPathComponent(" = "),\n           let image = NSImage(contentsOf: resourceURL) {\n            return image\n        }\n\n        // Estrategia de respaldo: intentar cargar desde el directorio Documents del usuario\n        let documentDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first\n        if let customURL = documentDir?.appendingPathComponent(";
")\n                    .font(.system(size: 20))\n                    .foregroundColor(config.alreadyExists ? .gray : (isSelected ? .blue : .secondary))\n            }\n            .buttonStyle(.plain)\n            .disabled(config.alreadyExists)\n\n            // 配置信息\n            VStack(alignment: .leading, spacing: 8) {\n                // 第一行：域名和服务器类型\n                HStack(spacing: 8) {\n                    // 域名\n                    Text(config.parsed.domains.joined(separator: " = ")\n                    .font(.system(size: 20))\n                    .foregroundColor(config.alreadyExists ? .gray : (isSelected ? .blue : .secondary))\n            }\n            .buttonStyle(.plain)\n            .disabled(config.alreadyExists)\n\n            // Información de configuración\n            VStack(alignment: .leading, spacing: 8) {\n                // Primera línea: dominio y tipo de servidor\n                HStack(spacing: 8) {\n                    // Dominio\n                    Text(config.parsed.domains.joined(separator: ";
", text: $editValue)\n                    .macInput()\n                    .font(.system(size: 14))\n                    .frame(width: 120)\n                    .disabled(!isEnabled)\n                    .opacity(isEnabled ? 1.0 : 0.6)\n                    .onSubmit {\n                        onValueChange(editValue)\n                    }\n                if let unit = item.unit {\n                    Text(unit)\n                        .font(.system(size: 13))\n                        .foregroundColor(isEnabled ? .secondary : .secondary.opacity(AppTokens.Opacity.disabled))\n                }\n            }\n            .frame(width: 160, alignment: .leading)\n\n            // 建议值（可点击应用）- 用户自定义参数无建议值\n            if item.isUserDefined {\n                Text(LocalizedStringKey(" = ", text: $editValue)\n                    .macInput()\n                    .font(.system(size: 14))\n                    .frame(width: 120)\n                    .disabled(!isEnabled)\n                    .opacity(isEnabled ? 1.0 : 0.6)\n                    .onSubmit {\n                        onValueChange(editValue)\n                    }\n                if let unit = item.unit {\n                    Text(unit)\n                        .font(.system(size: 13))\n                        .foregroundColor(isEnabled ? .secondary : .secondary.opacity(AppTokens.Opacity.disabled))\n                }\n            }\n            .frame(width: 160, alignment: .leading)\n\n            // Valor sugerido (clicable para aplicarlo) — los parámetros definidos por el usuario no tienen valores sugeridos\n            if item.isUserDefined {\n                Text(LocalizedStringKey(";
"  // 当前阶段：下载中、编译中、安装中等\n    var detail: String = " = "  // Etapa actual: descargando, compilando, instalando, etc.\n    var detail: String = ";
"             // 系统路径\n        ]\n\n        for path in possiblePaths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n\n        // 尝试使用 which 命令查找\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = "             // Rutas del sistema\n        ]\n\n        for path in possiblePaths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n\n        // Intentar usar el comando 'which' para buscar\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: ";
"\n        ]\n\n        let lowercaseDomain = domain.lowercased()\n        return localPatterns.contains { lowercaseDomain.contains($0) }\n    }\n\n    // MARK: - 域名可达性检测\n    /// 检测域名是否可以访问到当前站点\n    /// - Parameters:\n    ///   - domain: 要检测的域名\n    ///   - documentRoot: 站点根目录\n    /// - Returns: 验证状态\n    func verifyDomainReachability(domain: String, documentRoot: String) async -> DomainVerificationStatus {\n        // 1. 检查是否为本地域名\n        if isLocalDomain(domain) {\n            return .unreachable(reason: " = "\n        ]\n\n        let lowercaseDomain = domain.lowercased()\n        return localPatterns.contains { lowercaseDomain.contains($0) }\n    }\n\n    // MARK: - Detección de accesibilidad del dominio\n    /// Verificar si un dominio puede acceder al sitio actual\n    /// - Parámetros:\n    ///   - domain: Dominio a verificar\n    ///   - documentRoot: Directorio raíz del sitio\n    /// - Devuelve: Estado de verificación\n    func verifyDomainReachability(domain: String, documentRoot: String) async -> DomainVerificationStatus {\n        // 1. Comprobar si es un dominio local\n        if isLocalDomain(domain) {\n            return .unreachable(reason: ";
"\n\n    // 从外部传入的参数\n    var detectedRootPassword: String? = nil  // 自动填充的当前密码（本地）\n    var isLocal: Bool = true  // 是否是本地数据库\n    var host: String = " = "\n\n    // Parámetros recibidos externamente\n    var detectedRootPassword: String? = nil  // Contraseña actual rellenada automáticamente (local)\n    var isLocal: Bool = true  // ¿Es una base de datos local?\n    var host: String = ";
")\n        }\n    }\n\n    // Apache 端口配置\n    @Published var apachePort: Int {\n        didSet {\n            UserDefaults.standard.set(apachePort, forKey: " = ")\n        didSet {\n            UserDefaults.standard.set(apachePort, forKey: ";
")\n                        .font(.system(size: 12))\n                        .foregroundColor(.secondary)\n                }\n            }\n\n            Spacer()\n\n            // 查看日志按钮\n            MacButton(icon: " = ")\n                        .font(.system(size: 12))\n                        .foregroundColor(.secondary)\n                }\n            }\n\n            Spacer()\n\n            // Botón para ver registros\n            MacButton(icon: ";
")\n                            .font(.system(size: 10))\n                    }\n                }\n                .menuStyle(.borderlessButton)\n                .frame(width: 120)\n                .padding(.horizontal, 12)\n                .padding(.vertical, 6)\n            .glass(.input)\n\n                // 高级设置下拉菜单\n                Menu {\n                    Button(" = ")\n                            .font(.system(size: 10))\n                    }\n                }\n                .menuStyle(.borderlessButton)\n                .frame(width: 120)\n                .padding(.horizontal, 12)\n                .padding(.vertical, 6)\n            .glass(.input)\n\n                // Menú desplegable de configuración avanzada\n                Menu {\n                    Button(";
"\n        }\n\n        // 备份旧配置文件（如果存在）\n        if fileManager.fileExists(atPath: configFilePath) {\n            do {\n                _ = try ConfigBackupManager.shared.createSiteBackup(\n                    siteId: site.id,\n                    siteName: site.name,\n                    serviceName: " = "\n        }\n\n        // Respaldo del archivo de configuración anterior (si existe)\n        if fileManager.fileExists(atPath: configFilePath) {\n            do {\n                _ = try ConfigBackupManager.shared.createSiteBackup(\n                    siteId: site.id,\n                    siteName: site.name,\n                    serviceName: ";
"\n    }\n\n    /// 解析行数据\n    private func parseRow(_ packet: Data, columnCount: Int) -> [String?] {\n        var row: [String?] = []\n        var offset = 0\n\n        for _ in 0..<columnCount {\n            if offset >= packet.count {\n                row.append(nil)\n                continue\n            }\n\n            if packet[offset] == 0xFB {\n                row.append(nil)\n                offset += 1\n            } else {\n                let (value, len) = readLengthEncodedString(packet, offset: offset)\n                row.append(value)\n                offset += len\n            }\n        }\n\n        return row\n    }\n\n    // MARK: - 网络 I/O\n\n    /// 发送数据包\n    private func sendPacket(_ data: Data) throws {\n        guard let output = outputStream else {\n            throw MySQLError.connectionFailed(" = "\n    }\n\n    /// Analizar una fila de datos\n    private func parseRow(_ packet: Data, columnCount: Int) -> [String?] {\n        var row: [String?] = []\n        var offset = 0\n\n        for _ in 0..<columnCount {\n            if offset >= packet.count {\n                row.append(nil)\n                continue\n            }\n\n            if packet[offset] == 0xFB {\n                row.append(nil)\n                offset += 1\n            } else {\n                let (value, len) = readLengthEncodedString(packet, offset: offset)\n                row.append(value)\n                offset += len\n            }\n        }\n\n        return row\n    }\n\n    // MARK: - Entrada/salida de red\n\n    /// Enviar un paquete\n    private func sendPacket(_ data: Data) throws {\n        guard let output = outputStream else {\n            throw MySQLError.connectionFailed(";
" ? Color.white.opacity(0.2) : Color.gray.opacity(AppTokens.Opacity.hover), lineWidth: 1)\n                )\n\n                // 附加功能\n                VStack(alignment: .leading, spacing: 12) {\n                    Text(LocalizedStringKey(" = " ? Color.white.opacity(0.2) : Color.gray.opacity(AppTokens.Opacity.hover), lineWidth: 1)\n                )\n\n                // Funcionalidades adicionales\n                VStack(alignment: .leading, spacing: 12) {\n                    Text(LocalizedStringKey(";
", port: 3307, isRunning: false),\n    ]\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏（包含数据库选择下拉菜单）\n            databaseTitleBar\n\n            // Tab 标签栏（已打开的数据库）\n            if !databaseManager.openedDatabaseTabs.isEmpty {\n                databaseTabBar\n            }\n\n            // 主内容区域\n            VStack(spacing: 0) {\n                // 数据库信息卡片\n                databaseInfoCard\n\n                Divider()\n\n                // 顶部工具栏\n                topToolbar\n\n                Divider()\n\n                // 表格内容\n                tableContent\n\n                Divider()\n\n                // 底部分页\n                bottomPagination\n            }\n            .modifier(V2ConditionalCardModifier())\n            .zIndex(1) // Lower z-index than tabs\n        }\n        .padding(.horizontal, 20) // Add some padding around the whole view\n        .padding(.bottom, 20)\n        .sheetWithLocale(isPresented: $showRemoteDatabaseSheet) {\n            RemoteDatabaseSheet()\n                .environmentObject(databaseManager)\n        }\n        .sheetWithLocale(isPresented: $showAddDatabaseSheet) {\n            AddDatabaseSheet()\n                .environmentObject(databaseManager)\n        }\n        .sheetWithLocale(isPresented: $showRootPasswordSheet) {\n            if activeConnectionInfo.isLocal {\n                // 本地数据库：传递检测到的密码\n                RootPasswordSheet(detectedRootPassword: rootPassword)\n                    .environmentObject(databaseManager)\n            } else {\n                // 远程数据库：传递保存的密码和连接信息\n                let remotePassword = getRemoteDatabaseInfo()?.password ?? " = ", port: 3307, isRunning: false),\n    ]\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Barra de título (incluye menú desplegable de selección de bases de datos)\n            databaseTitleBar\n\n            // Pestañas de la barra de pestañas (bases de datos abiertas)\n            if !databaseManager.openedDatabaseTabs.isEmpty {\n                databaseTabBar\n            }\n\n            // Área principal de contenido\n            VStack(spacing: 0) {\n                // Tarjeta de información de la base de datos\n                databaseInfoCard\n\n                Divider()\n\n                // Barra de herramientas superior\n                topToolbar\n\n                Divider()\n\n                // Contenido de la tabla\n                tableContent\n\n                Divider()\n\n                // Paginación inferior\n                bottomPagination\n            }\n            .modifier(V2ConditionalCardModifier())\n            .zIndex(1) // Índice z menor que las pestañas\n        }\n        .padding(.horizontal, 20) // Añadir algo de relleno alrededor de toda la vista\n        .padding(.bottom, 20)\n        .sheetWithLocale(isPresented: $showRemoteDatabaseSheet) {\n            RemoteDatabaseSheet()\n                .environmentObject(databaseManager)\n        }\n        .sheetWithLocale(isPresented: $showAddDatabaseSheet) {\n            AddDatabaseSheet()\n                .environmentObject(databaseManager)\n        }\n        .sheetWithLocale(isPresented: $showRootPasswordSheet) {\n            if activeConnectionInfo.isLocal {\n                // Base de datos local: pasar la contraseña detectada\n                RootPasswordSheet(detectedRootPassword: rootPassword)\n                    .environmentObject(databaseManager)\n            } else {\n                // Base de datos remota: pasar la contraseña guardada y la información de conexión\n                let remotePassword = getRemoteDatabaseInfo()?.password ?? ";
"  // 完整路径（网站目录 + 运行目录）\n    @State private var runningDirectory = " = "  // Ruta completa (directorio del sitio web + directorio de ejecución)\n    @State private var runningDirectory = ";
"正在启动 \\(software.name)..." = "Iniciando \\(software.name)...";
"\\(service.name) 重启失败,请检查日志" = "Error al reiniciar \\(service.name); por favor, revise los registros";
" ? Color.white.opacity(0.2) : AppColors.border, lineWidth: 1)\n                )\n\n                // 附加功能\n                VStack(alignment: .leading, spacing: 12) {\n                    Text(LocalizedStringKey(" = " ? Color.white.opacity(0.2) : AppColors.border, lineWidth: 1)\n                )\n\n                // Funcionalidades adicionales\n                VStack(alignment: .leading, spacing: 12) {\n                    Text(LocalizedStringKey(";
"\n        }\n\n        return result\n    }\n\n    // 加载模板\n    private func loadTemplate(_ template: RuleTemplate) {\n        selectedTemplate = template\n        if sourceType == .nginx {\n            sourceRules = template.nginxRules\n        } else {\n            sourceRules = template.apacheRules\n        }\n        // 清空转换结果\n        convertedRules = " = "\n        }\n\n        return result\n    }\n\n    // Cargar plantilla\n    private func loadTemplate(_ template: RuleTemplate) {\n        selectedTemplate = template\n        if sourceType == .nginx {\n            sourceRules = template.nginxRules\n        } else {\n            sourceRules = template.apacheRules\n        }\n        // Limpiar resultados de conversión\n        convertedRules = ";
"\n                            let regex = try? NSRegularExpression(pattern: pattern)\n                            let range = NSRange(output.startIndex..., in: output)\n                            let hasMatch = regex?.firstMatch(in: output, range: range) != nil\n                            continuation.resume(returning: hasMatch)\n                        } else {\n                            continuation.resume(returning: false)\n                        }\n                    }\n                } catch {\n                    timeoutWorkItem.cancel()\n                    if !hasResumed {\n                        hasResumed = true\n                        continuation.resume(returning: false)\n                    }\n                }\n            }\n        }\n    }\n\n    // MARK: - PHP 运行检查\n\n    private func checkPHP(for site: Site) async -> HealthCheckResult {\n        var details: [String] = []\n\n        // 构建文档根目录路径\n        let documentRoot = site.documentRoot\n        let publicDir = site.publicDirectory.isEmpty ? " = "\n                            let regex = try? NSRegularExpression(pattern: pattern)\n                            let range = NSRange(output.startIndex..., in: output)\n                            let hasMatch = regex?.firstMatch(in: output, range: range) != nil\n                            continuation.resume(returning: hasMatch)\n                        } else {\n                            continuation.resume(returning: false)\n                        }\n                    }\n                } catch {\n                    timeoutWorkItem.cancel()\n                    if !hasResumed {\n                        hasResumed = true\n                        continuation.resume(returning: false)\n                    }\n                }\n            }\n        }\n    }\n\n    // MARK: - Comprobación de ejecución de PHP\n\n    private func checkPHP(for site: Site) async -> HealthCheckResult {\n        var details: [String] = []\n\n        // Construir la ruta del directorio raíz de documentos\n        let documentRoot = site.documentRoot\n        let publicDir = site.publicDirectory.isEmpty ? ";
") {\n                                // 认证插件错误，仍允许保存连接\n                                let tip = " = ") {\n                                // Error del complemento de autenticación; aún se permite guardar la conexión\n                                let tip = ";
")\n                            .font(.system(size: 10))\n                    }\n                    .buttonStyle(.plain)\n                }\n            }\n        }\n    }\n\n    // 备份列\n    private var backupColumn: some View {\n        HStack(spacing: 4) {\n            Button(" = ")\n                            .font(.system(size: 10))\n                    }\n                    .buttonStyle(.plain)\n                }\n            }\n        }\n    }\n\n    // Columna de copias de seguridad\n    private var backupColumn: some View {\n        HStack(spacing: 4) {\n            Button(";
"MySQL 版本: \\(version)" = "Versión de MySQL: \\(version)";
"\n        NSPasteboard.general.clearContents()\n        NSPasteboard.general.setString(info, forType: .string)\n    }\n\n    // 打开 phpMyAdmin（远程服务器）\n    private func openPhpMyAdmin() {\n        // 远程数据库使用服务器自带的 phpMyAdmin\n        if let url = URL(string: " = "\n        NSPasteboard.general.clearContents()\n        NSPasteboard.general.setString(info, forType: .string)\n    }\n\n    // Abrir phpMyAdmin (servidor remoto)\n    private func openPhpMyAdmin() {\n        // Para bases de datos remotas, usar el phpMyAdmin integrado en el servidor\n        if let url = URL(string: ";
")\n                        .foregroundColor(.secondary)\n                        .font(.title2)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding(16)\n\n            Divider()\n\n            // 设置选项\n            VStack(alignment: .leading, spacing: 16) {\n                Text(" = ")\n                        .foregroundColor(.secondary)\n                        .font(.title2)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding(16)\n\n            Divider()\n\n            // Opciones de configuración\n            VStack(alignment: .leading, spacing: 16) {\n                Text(";
")\n        }\n    }\n\n    /// 为 Apache 配置生成缺失的 Nginx 代理配置\n    private func generateMissingNginxProxy(for config: FoundVhostConfig) async throws {\n        guard config.parsed.webServer == .apache else {\n            throw SiteError.operationFailed(" = ")\n        }\n    }\n\n    /// Generar configuraciones de proxy faltantes para Nginx a partir de una configuración de Apache\n    private func generateMissingNginxProxy(for config: FoundVhostConfig) async throws {\n        guard config.parsed.webServer == .apache else {\n            throw SiteError.operationFailed(";
"\n        \n        // 添加管理区域后的内容\n        if !afterManaged.isEmpty {\n            newContent += afterManaged.joined(separator: " = "        \n        // Agregar contenido tras la zona administrada\n        if !afterManaged.isEmpty {\n            newContent += afterManaged.joined(separator: ";
"写入 hosts 原始内容失败：\\(error.localizedDescription)" = "Error al escribir el contenido original de hosts: \\(error.localizedDescription)";
", Color(red: 0.2, green: 0.4, blue: 0.65)) // PostgreSQL蓝色\n        } else if name.contains(" = ", Color(red: 0.2, green: 0.4, blue: 0.65)) // Azul para PostgreSQL\n        } else if name.contains(";
" }\n\n    /// MySQL 配置文件路径\n    func mysqlConfigFile(version: String) -> String {\n        " = " }\n\n    /// Ruta del archivo de configuración de MySQL\n    func mysqlConfigFile(version: String) -> String {\n        ";
") {\n                    // 提取生效日期\n                    let dateStr = line.replacingOccurrences(of: " = ") {\n                    // Extraer la fecha de vigencia\n                    let dateStr = line.replacingOccurrences(of: ";
", 9000, 20),\n                    // 低优先级 - 缓存和工具\n                    (" = ", 9000, 20),\n                    // Baja prioridad - caché y herramientas\n                    (";
")\n                    .foregroundColor(.secondary)\n                    .font(.title2)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(16)\n    }\n\n    // MARK: - Configuration View\n    private var configurationView: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            // 父目录选择\n            HStack(spacing: 8) {\n                Text(" = ")\n                    .foregroundColor(.secondary)\n                    .font(.title2)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(16)\n    }\n\n    // MARK: - Vista de configuración\n    private var configurationView: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            // Selección del directorio padre\n            HStack(spacing: 8) {\n                Text(";
", omittingEmptySubsequences: true)\n\n            guard components.count >= 2 else { return nil }\n\n            let processName = String(components[0])\n            let pid = String(components[1])\n\n            // 获取进程的详细信息\n            let details = getProcessDetails(pid: pid)\n\n            return PortItem(\n                service: serviceName,\n                port: port,\n                status: " = ", omittingEmptySubsequences: true)\n\n            guard components.count >= 2 else { return nil }\n\n            let processName = String(components[0])\n            let pid = String(components[1])\n\n            // Obtener información detallada del proceso\n            let details = getProcessDetails(pid: pid)\n\n            return PortItem(\n                service: serviceName,\n                port: port,\n                status: ";
"  // 数据库主机\n    var port: Int = 3306  // 数据库端口\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 表单内容\n            formContent\n\n            Divider()\n\n            // 底部按钮\n            bottomButtons\n        }\n        .frame(width: 500, height: 380)\n        .onAppear {\n            // 自动填充当前密码\n            if let password = detectedRootPassword {\n                currentPassword = password\n            }\n        }\n        .alert(" = "  // Host de la base de datos\n    var port: Int = 3306  // Puerto de la base de datos\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Barra de título\n            headerBar\n\n            Divider()\n\n            // Contenido del formulario\n            formContent\n\n            Divider()\n\n            // Botones inferiores\n            bottomButtons\n        }\n        .frame(width: 500, height: 380)\n        .onAppear {\n            // Rellenar automáticamente la contraseña actual\n            if let password = detectedRootPassword {\n                currentPassword = password\n            }\n        }\n        .alert(";
").appendingPathComponent(filename),\n           FileManager.default.fileExists(atPath: customURL.path),\n           let image = NSImage(contentsOf: customURL) {\n            return image\n        }\n        \n        return nil\n    }\n}\n\n// 占位的 V2 侧边栏\nstruct SidebarViewV2: View {\n    @Binding var selectedTab: SidebarItem\n    @Binding var selectedTool: ToolShortcut\n    @Environment(\\.colorScheme) var colorScheme\n    \n    var body: some View {\n        VStack(spacing: 0) {\n            // Header\n            HStack(spacing: 12) {\n                Image(nsImage: loadLogoImage() ?? NSImage())\n                    .resizable()\n                    .frame(width: 40, height: 40)\n                    .clipShape(Circle())\n                \n                VStack(alignment: .leading, spacing: 2) {\n                    Text(" = ").appendingPathComponent(filename),\n           FileManager.default.fileExists(atPath: customURL.path),\n           let image = NSImage(contentsOf: customURL) {\n            return image\n        }\n        \n        return nil\n    }\n}\n\n// Barra lateral provisional V2\nstruct SidebarViewV2: View {\n    @Binding var selectedTab: SidebarItem\n    @Binding var selectedTool: ToolShortcut\n    @Environment(\\.colorScheme) var colorScheme\n    \n    var body: some View {\n        VStack(spacing: 0) {\n            // Encabezado\n            HStack(spacing: 12) {\n                Image(nsImage: loadLogoImage() ?? NSImage())\n                    .resizable()\n                    .frame(width: 40, height: 40)\n                    .clipShape(Circle())\n                \n                VStack(alignment: .leading, spacing: 2) {\n                    Text(";
"以下服务当前未运行：\\n\\n\\(serviceList)\\n\\n使用已停止的服务可能导致网站无法正常访问。\\n\\n建议先启动这些服务，或选择其他运行中的服务。" = "Los siguientes servicios no se están ejecutando actualmente:\\n\\n\\(serviceList)\\n\\nEl uso de servicios detenidos podría impedir el acceso normal al sitio web.\\n\\nSe recomienda iniciar primero estos servicios o seleccionar otros que estén en ejecución.";
"接管系统中已安装的 \\(serverType.displayName)，使用 80/443 端口" = "Asumir el control de \\(serverType.displayName) ya instalado en el sistema, utilizando los puertos 80/443";
", maxSplits: 1)\n                        if parts.count >= 2 {\n                            let name = String(parts[0])\n                            let version = String(parts[1]).trimmingCharacters(in: .whitespaces)\n                            packages[name] = version\n                        }\n                    }\n                    AppState.cachedInstalledPackages = packages\n                }\n            }\n        } catch {\n            // 忽略错误\n        }\n    }\n\n    /// 预加载 brew services list 缓存\n    private nonisolated func preloadBrewServicesCache() {\n        guard AppState.cachedBrewServicesOutput == nil else { return }\n        guard let brewPath = resolveBrewPath() else { return }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [" = ", maxSplits: 1)\n                        if parts.count >= 2 {\n                            let name = String(parts[0])\n                            let version = String(parts[1]).trimmingCharacters(in: .whitespaces)\n                            packages[name] = version\n                        }\n                    }\n                    AppState.cachedInstalledPackages = packages\n                }\n            }\n        } catch {\n            // Ignorar el error\n        }\n    }\n\n    /// Precargar la caché de \"brew services list\"\n    private nonisolated func preloadBrewServicesCache() {\n        guard AppState.cachedBrewServicesOutput == nil else { return }\n        guard let brewPath = resolveBrewPath() else { return }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [";
")\n        }\n    }\n\n    // 默认站点是否启用\n    @Published var defaultSiteEnabled: Bool {\n        didSet {\n            UserDefaults.standard.set(defaultSiteEnabled, forKey: " = ")\n        }\n    }\n\n    // ¿Está habilitado el sitio predeterminado?\n    @Published var defaultSiteEnabled: Bool {\n        didSet {\n            UserDefaults.standard.set(defaultSiteEnabled, forKey: ";
")\n                    .font(.system(size: 10))\n            }\n            .buttonStyle(.plain)\n        }\n    }\n\n    // 备份列\n    private var backupColumn: some View {\n        HStack(spacing: 4) {\n            if backupCount > 0 {\n                Button(" = ")\n                    .font(.system(size: 10))\n            }\n            .buttonStyle(.plain)\n        }\n    }\n\n    // Columna de copias de seguridad\n    private var backupColumn: some View {\n        HStack(spacing: 4) {\n            if backupCount > 0 {\n                Button(";
"\n        try setupConfigSymlink(\n            source: brewWwwConfPath,\n            workspace: workspaceWwwPath,\n            fileManager: fm\n        )\n\n        // 处理 php.ini\n        let brewIniPath = " = "\n        try setupConfigSymlink(\n            source: brewWwwConfPath,\n            workspace: workspaceWwwPath,\n            fileManager: fm\n        )\n\n        // Procesar php.ini\n        let brewIniPath = ";
", text: $organization)\n                        .macInput()\n                }\n\n                // 有效期\n                FormField(" = ", text: $organization)\n                        .macInput()\n                }\n\n                // Vigencia\n                FormField(";
"✅ Nginx 反向代理配置文件已生成: \\(configFilePath)" = "✅ El archivo de configuración del proxy inverso de Nginx se ha generado correctamente: \\(configFilePath)";
" : database.location)\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 90, alignment: .leading)\n\n            // 备注\n            Text(database.note.isEmpty ? " = " : database.location)\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 90, alignment: .leading)\n\n            // Notas\n            Text(database.note.isEmpty ? ";
").last {\n                            return String(port)\n                        }\n                    } else if value.allSatisfy({ $0.isNumber }) {\n                        // 格式: 9000\n                        return value\n                    }\n                }\n            }\n        }\n\n        return nil\n    }\n\n    /// 从文件中读取端口配置\n    private func readPortFromFile(_ filePath: String, key: String) -> String? {\n        guard FileManager.default.fileExists(atPath: filePath),\n              let content = try? String(contentsOfFile: filePath, encoding: .utf8) else {\n            return nil\n        }\n\n        let lines = content.components(separatedBy: .newlines)\n        return extractValue(for: key, from: lines)\n    }\n\n    /// 验证端口\n    private func validatePort() {\n        // 清空之前的验证消息\n        portValidationMessage = " = ").last {\n                            return String(port)\n                        }\n                    } else if value.allSatisfy({ $0.isNumber }) {\n                        // Formato: 9000\n                        return value\n                    }\n                }\n            }\n        }\n\n        return nil\n    }\n\n    /// Leer la configuración del puerto desde un archivo\n    private func readPortFromFile(_ filePath: String, key: String) -> String? {\n        guard FileManager.default.fileExists(atPath: filePath),\n              let content = try? String(contentsOfFile: filePath, encoding: .utf8) else {\n            return nil\n        }\n\n        let lines = content.components(separatedBy: .newlines)\n        return extractValue(for: key, from: lines)\n    }\n\n    /// Validar el puerto\n    private func validatePort() {\n        // Limpiar mensajes de validación anteriores\n        portValidationMessage = ";
").appendingPathComponent(filename),\n           FileManager.default.fileExists(atPath: customURL.path),\n           let image = NSImage(contentsOf: customURL) {\n            return image\n        }\n\n        return nil\n    }\n}\n\n// MARK: - 首次启动工作空间欢迎界面\nstruct WorkspaceWelcomeSheet: View {\n    @Environment(\\.dismiss) var dismiss\n    @StateObject private var workspaceManager = WorkspaceManager.shared\n    @State private var showWorkspaceSetup = false\n    @State private var isApplyingDefault = false\n    @State private var errorMessage: String?\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 内容区域\n            VStack(spacing: 24) {\n                Spacer()\n\n                // 图标\n                Image(systemName: " = ").appendingPathComponent(filename),\n           FileManager.default.fileExists(atPath: customURL.path),\n           let image = NSImage(contentsOf: customURL) {\n            return image\n        }\n\n        return nil\n    }\n}\n\n// MARK: - Interfaz de bienvenida para el espacio de trabajo en el primer inicio\nstruct WorkspaceWelcomeSheet: View {\n    @Environment(\\.dismiss) var dismiss\n    @StateObject private var workspaceManager = WorkspaceManager.shared\n    @State private var showWorkspaceSetup = false\n    @State private var isApplyingDefault = false\n    @State private var errorMessage: String?\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Área de contenido\n            VStack(spacing: 24) {\n                Spacer()\n\n                // Icono\n                Image(systemName: ";
",\n                iconColor: .purple,\n                iconBgColor: .purple.opacity(AppTokens.Opacity.light)\n            )\n        }\n    }\n\n    // 访问量图表\n    private var trafficChart: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            Text(" = ",\n                iconColor: .purple,\n                iconBgColor: .purple.opacity(AppTokens.Opacity.light)\n            )\n        }\n    }\n\n    // Gráfico de tráfico\n    private var trafficChart: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            Text(";
" {\n                inMysqldSection = true\n                mysqldSectionEndIndex = i\n                continue\n            }\n\n            // 检测其他段开始\n            if trimmed.hasPrefix(" = " {\n                inMysqldSection = true\n                mysqldSectionEndIndex = i\n                continue\n            }\n\n            // Detectar el inicio de otras secciones\n            if trimmed.hasPrefix(";
"\n\n        DispatchQueue.global(qos: .userInitiated).async {\n            let content = self.readLogFile(path: service.logPath)\n\n            DispatchQueue.main.async {\n                self.logContent = content\n                self.isLoading = false\n            }\n        }\n    }\n\n    /// 读取日志文件\n    private func readLogFile(path: String) -> String {\n        // 处理通配符路径（如 MySQL 的 *.err）\n        if path.contains(" = "\n\n        DispatchQueue.global(qos: .userInitiated).async {\n            let content = self.readLogFile(path: service.logPath)\n\n            DispatchQueue.main.async {\n                self.logContent = content\n                self.isLoading = false\n            }\n        }\n    }\n\n    /// Leer el archivo de registro\n    private func readLogFile(path: String) -> String {\n        // Manejar rutas con comodines (por ejemplo, *.err de MySQL)\n        if path.contains(";
"\n                let newPort = db.serverPort ?? 3306\n                let newName = db.name\n\n                // 检查关键信息是否有变化\n                if tab.host != newHost || tab.port != newPort || tab.name != newName {\n                    // 信息变更，更新标签\n                    // 重新生成 ID\n                    let newTabId = " = "\n                let newPort = db.serverPort ?? 3306\n                let newName = db.name\n\n                // Verificar si hay cambios en la información clave\n                if tab.host != newHost || tab.port != newPort || tab.name != newName {\n                    // Información modificada; actualizar la pestaña\n                    // Generar nuevamente el ID\n                    let newTabId = ";
"\n            while offset < packet.count && packet[offset] != 0 {\n                pluginName.append(Character(UnicodeScalar(packet[offset])))\n                offset += 1\n            }\n            authPluginName = pluginName\n        }\n    }\n\n    /// 发送认证响应\n    private func sendAuthResponse() throws {\n        var packet = Data()\n\n        // 客户端能力标志\n        let clientFlags: UInt32 = MySQLConstants.clientLongPassword |\n            MySQLConstants.clientFoundRows |\n            MySQLConstants.clientLongFlag |\n            MySQLConstants.clientProtocol41 |\n            MySQLConstants.clientSecureConnection |\n            MySQLConstants.clientPluginAuth\n\n        packet.append(contentsOf: withUnsafeBytes(of: clientFlags.littleEndian) { Array($0) })\n\n        // 最大包大小\n        packet.append(contentsOf: withUnsafeBytes(of: MySQLConstants.maxPacketSize.littleEndian) { Array($0) })\n\n        // 字符集 (utf8mb4 = 45)\n        packet.append(45)\n\n        // 保留字节 (23 bytes)\n        packet.append(contentsOf: [UInt8](repeating: 0, count: 23))\n\n        // 用户名 (null-terminated)\n        packet.append(contentsOf: username.utf8)\n        packet.append(0)\n\n        // 密码哈希\n        let authResponse = generateAuthResponse()\n        packet.append(UInt8(authResponse.count))\n        packet.append(authResponse)\n\n        // 认证插件名称\n        packet.append(contentsOf: authPluginName.utf8)\n        packet.append(0)\n\n        try sendPacket(packet)\n    }\n\n    /// 生成认证响应\n    private func generateAuthResponse() -> Data {\n        if password.isEmpty {\n            return Data()\n        }\n\n        if authPluginName == " = "\n            while offset < packet.count && packet[offset] != 0 {\n                pluginName.append(Character(UnicodeScalar(packet[offset])))\n                offset += 1\n            }\n            authPluginName = pluginName\n        }\n    }\n\n    /// Enviar respuesta de autenticación\n    private func sendAuthResponse() throws {\n        var packet = Data()\n\n        // Banderas de capacidades del cliente\n        let clientFlags: UInt32 = MySQLConstants.clientLongPassword |\n            MySQLConstants.clientFoundRows |\n            MySQLConstants.clientLongFlag |\n            MySQLConstants.clientProtocol41 |\n            MySQLConstants.clientSecureConnection |\n            MySQLConstants.clientPluginAuth\n\n        packet.append(contentsOf: withUnsafeBytes(of: clientFlags.littleEndian) { Array($0) })\n\n        // Tamaño máximo del paquete\n        packet.append(contentsOf: withUnsafeBytes(of: MySQLConstants.maxPacketSize.littleEndian) { Array($0) })\n\n        // Conjunto de caracteres (utf8mb4 = 45)\n        packet.append(45)\n\n        // Bytes reservados (23 bytes)\n        packet.append(contentsOf: [UInt8](repeating: 0, count: 23))\n\n        // Nombre de usuario (terminado en nulo)\n        packet.append(contentsOf: username.utf8)\n        packet.append(0)\n\n        // Hash de la contraseña\n        let authResponse = generateAuthResponse()\n        packet.append(UInt8(authResponse.count))\n        packet.append(authResponse)\n\n        // Nombre del complemento de autenticación\n        packet.append(contentsOf: authPluginName.utf8)\n        packet.append(0)\n\n        try sendPacket(packet)\n    }\n\n    /// Generar la respuesta de autenticación\n    private func generateAuthResponse() -> Data {\n        if password.isEmpty {\n            return Data()\n        }\n\n        if authPluginName == ";
") {\n                    currentPage = min(totalPages, currentPage + 1)\n                }\n                .disabled(currentPage >= totalPages)\n            } else {\n                // 上一页按钮\n                Button(action: { currentPage = max(1, currentPage - 1) }) {\n                    Image(systemName: " = ") {\n                    currentPage = min(totalPages, currentPage + 1)\n                }\n                .disabled(currentPage >= totalPages)\n            } else {\n                // Botón de página anterior\n                Button(action: { currentPage = max(1, currentPage - 1) }) {\n                    Image(systemName: ";
")\n                    .font(.system(size: 16, weight: .semibold))\n                Spacer()\n\n                // 搜索框\n                HStack {\n                    Image(systemName: " = ")\n                    .font(.system(size: 16, weight: .semibold))\n                Spacer()\n\n                // Campo de búsqueda\n                HStack {\n                    Image(systemName: ";
"\n    }\n}\n\n// MARK: - 默认文档视图\nstruct DefaultDocumentView: View {\n    let site: Site\n    @Binding var config: SiteAdvancedConfig\n    @EnvironmentObject var siteManager: SiteManager\n    \n    var body: some View {\n        VStack(alignment: .leading, spacing: 24) {\n            Text(LocalizedStringKey(" = "\n    }\n}\n\n// MARK: - Vista de documento predeterminada\nstruct DefaultDocumentView: View {\n    let site: Site\n    @Binding var config: SiteAdvancedConfig\n    @EnvironmentObject var siteManager: SiteManager\n    \n    var body: some View {\n        VStack(alignment: .leading, spacing: 24) {\n            Text(LocalizedStringKey(";
"\n\n        // 构建详细信息文本\n        var detailsText = " = "\n\n        // Construir el texto de detalles\n        var detailsText = ";
")\n                            .font(.system(size: 10))\n                    }\n                }\n                .menuStyle(.borderlessButton)\n                .frame(width: 100)\n                .padding(.horizontal, 12)\n                .padding(.vertical, 6)\n            .glass(.input)\n\n                Spacer()\n\n                // 搜索框\n                HStack {\n                    Image(systemName: " = ")\n                            .font(.system(size: 10))\n                    }\n                }\n                .menuStyle(.borderlessButton)\n                .frame(width: 100)\n                .padding(.horizontal, 12)\n                .padding(.vertical, 6)\n            .glass(.input)\n\n                Spacer()\n\n                // Campo de búsqueda\n                HStack {\n                    Image(systemName: ";
")\n                                .font(.caption)\n                                .foregroundColor(.secondary)\n                        }\n                    }\n                    .padding()\n                    .background(Color.orange.opacity(0.1))\n                    .cornerRadius(6)\n                    .frame(width: 200)\n                }\n            }\n            .padding()\n\n            Divider().background(Color.gray.opacity(0.2))\n\n            // 右侧：转换后的规则\n            VStack(alignment: .leading, spacing: 12) {\n                HStack {\n                    Text(" = ")\n                                .font(.caption)\n                                .foregroundColor(.secondary)\n                        }\n                    }\n                    .padding()\n                    .background(Color.orange.opacity(0.1))\n                    .cornerRadius(6)\n                    .frame(width: 200)\n                }\n            }\n            .padding()\n\n            Divider().background(Color.gray.opacity(0.2))\n\n            // Lado derecho: reglas transformadas\n            VStack(alignment: .leading, spacing: 12) {\n                HStack {\n                    Text(";
" {\n                    subdirs.append(item)\n                }\n            }\n        }\n        \n        availableSubdirs = subdirs.sorted()\n    }\n\n    // 选择目录\n    private func selectDirectory() {\n        let panel = NSOpenPanel()\n        panel.canChooseFiles = false\n        panel.canChooseDirectories = true\n        panel.allowsMultipleSelection = false\n        let defaultPath = appSettings.defaultSiteRoot.isEmpty ? " = " {\n                    subdirs.append(item)\n                }\n            }\n        }\n        \n        availableSubdirs = subdirs.sorted()\n    }\n\n    // Seleccionar directorio\n    private func selectDirectory() {\n        let panel = NSOpenPanel()\n        panel.canChooseFiles = false\n        panel.canChooseDirectories = true\n        panel.allowsMultipleSelection = false\n        let defaultPath = appSettings.defaultSiteRoot.isEmpty ? ";
"\n        }\n        return packageId\n    }\n\n    // MARK: - 安装软件\n\n    /// Nginx 专用安装方法，支持不同安装模式\n    func installNginx(mode: NginxInstallMode, port: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(" = "\n        }\n        return packageId\n    }\n\n    // MARK: - Instalación de software\n\n    /// Método específico para instalar Nginx, compatible con distintos modos de instalación\n    func installNginx(mode: NginxInstallMode, port: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(";
"], isCommon: true),\n        // MySQL 9.x 使用 innodb_redo_log_capacity 替代 innodb_log_file_size\n        " = "], isCommon: true),\n        // MySQL 9.x utiliza innodb_redo_log_capacity en lugar de innodb_log_file_size\n        ";
"\n            port = String(database.version.port)\n        }\n    }\n\n    // 测试连接\n    private func testConnection() {\n        guard let portInt = Int(port), portInt > 0 && portInt < 65536 else {\n            connectionTestResult = (false, " = "\n            port = String(database.version.port)\n        }\n    }\n\n    // Probar la conexión\n    private func testConnection() {\n        guard let portInt = Int(port), portInt > 0 && portInt < 65536 else {\n            connectionTestResult = (false, ";
")\n        }\n    }\n\n    // 执行 MySQL 导入\n    private func performMySQLImport(databaseName: String, from filePath: String, progressCallback: @escaping (Double) -> Void) async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                // 开始导入前设置进度\n                DispatchQueue.main.async { progressCallback(10) }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqlPath)\n                process.arguments = [" = ")\n        }\n    }\n\n    // Ejecutar importación de MySQL\n    private func performMySQLImport(databaseName: String, from filePath: String, progressCallback: @escaping (Double) -> Void) async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                // Establecer progreso antes de iniciar la importación\n                DispatchQueue.main.async { progressCallback(10) }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqlPath)\n                process.arguments = [";
"显示 \\(displayPorts.count) 个结果" = "Mostrando \\(displayPorts.count) resultados";
"\n    }\n\n    /// 获取 PHP-FPM 配置文件路径（优先返回存在的文件）\n    func getPHPFpmConfigPath(version: String) -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.phpFpmConfigFile(version: version)\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        return " = "\n    }\n\n    /// Obtener la ruta del archivo de configuración de PHP-FPM (prioriza el archivo existente)\n    func getPHPFpmConfigPath(version: String) -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.phpFpmConfigFile(version: version)\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        return ";
"\n    }\n\n    // MARK: - 获取已安装扩展\n\n    private func getInstalledExtensions(for phpVersion: String) async -> Set<String> {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let phpPath = self.getPHPPath(for: phpVersion)\n                guard FileManager.default.fileExists(atPath: phpPath) else {\n                    continuation.resume(returning: [])\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: phpPath)\n                process.arguments = [" = "\n    }\n\n    // MARK: - Obtener extensiones instaladas\n\n    private func getInstalledExtensions(for phpVersion: String) async -> Set<String> {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let phpPath = self.getPHPPath(for: phpVersion)\n                guard FileManager.default.fileExists(atPath: phpPath) else {\n                    continuation.resume(returning: [])\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: phpPath)\n                process.arguments = [";
")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n            \n            Divider()\n            \n            // 内容\n            ScrollView {\n                VStack(alignment: .leading, spacing: 20) {\n                    // 站点信息\n                    GroupBox(" = ")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n            \n            Divider()\n            \n            // Contenido\n            ScrollView {\n                VStack(alignment: .leading, spacing: 20) {\n                    // Información del sitio\n                    GroupBox(";
"  // 网站目录（站点根目录）\n    @State private var documentRoot = " = "  // Directorio del sitio (directorio raíz del sitio)\n    @State private var documentRoot = ";
", serviceName: serviceName) { success in\n            DispatchQueue.main.async {\n                self.services[index].state = success ? .stopped : .running\n                // 只在失败时显示提示\n                if !success {\n                    self.showServiceAlert(\n                        title: " = ", serviceName: serviceName) { success in\n            DispatchQueue.main.async {\n                self.services[index].state = success ? .stopped : .running\n                // Mostrar advertencia solo si falla\n                if !success {\n                    self.showServiceAlert(\n                        title: ";
"无法创建验证文件：\\(error.localizedDescription)" = "No se pudo crear el archivo de verificación: \\(error.localizedDescription)";
" }\n    }\n\n    /// phpMyAdmin 安装路径（优先使用系统路径，否则使用内置路径）\n    private var phpMyAdminPath: String {\n        // 优先检查系统安装路径\n        if FileManager.default.fileExists(atPath: phpMyAdminSystemPath) {\n            return phpMyAdminSystemPath\n        }\n        // 检查备用系统路径\n        let altPath = " = " }\n    }\n\n    /// Ruta de instalación de phpMyAdmin (prioriza la ruta del sistema, de lo contrario usa la ruta integrada)\n    private var phpMyAdminPath: String {\n        // Verificar primero la ruta de instalación del sistema\n        if FileManager.default.fileExists(atPath: phpMyAdminSystemPath) {\n            return phpMyAdminSystemPath\n        }\n        // Verificar ruta alternativa del sistema\n        let altPath = ";
"\n        }\n\n        return nil\n    }\n}\n\n/// 端口同步规则\nstruct PortSyncRule {\n    let targetSoftware: String           // 目标软件名称\n    let configLocations: [ConfigFileLocation]  // 需要更新的配置文件位置\n    let patterns: [PortReferencePattern]       // 端口引用模式\n    let description: String                    // 规则描述\n}\n\n/// 端口同步配置管理器\nclass PortSyncConfigManager {\n    static let shared = PortSyncConfigManager()\n\n    private init() {}\n\n    /// 获取软件端口修改时需要同步的规则\n    func getSyncRules(for software: String) -> [PortSyncRule] {\n        switch software.lowercased() {\n        case " = "\n        }\n\n        return nil\n    }\n}\n\n/// Regla de sincronización de puertos\nstruct PortSyncRule {\n    let targetSoftware: String           // Nombre del software objetivo\n    let configLocations: [ConfigFileLocation]  // Ubicaciones de los archivos de configuración que deben actualizarse\n    let patterns: [PortReferencePattern]       // Patrones de referencia de puerto\n    let description: String                    // Descripción de la regla\n}\n\n/// Administrador de configuración de sincronización de puertos\nclass PortSyncConfigManager {\n    static let shared = PortSyncConfigManager()\n\n    private init() {}\n\n    /// Obtener las reglas de sincronización necesarias al modificar el puerto de un software\n    func getSyncRules(for software: String) -> [PortSyncRule] {\n        switch software.lowercased() {\n        case ";
"，按空白分割后会拆成多个 token\n                // 这里从尾部寻找包含 " = " y se dividirá en varios tokens tras separar por espacios en blanco\n                // Aquí buscamos desde el final una cadena que contenga ";
"  // Linux\n        ]\n\n        for path in possiblePaths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n\n        // 尝试使用 which 命令查找\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = "  // Linux\n        ]\n\n        for path in possiblePaths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n\n        // Intentar buscar mediante el comando \"which\"\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: ";
"\n                }\n\n                // 申请证书\n                let (certPath, keyPath) = try await letsEncryptService.issueCertificate(\n                    domain: site.primaryDomain,\n                    email: email,\n                    webroot: site.documentRoot,\n                    outputDir: sslDir\n                )\n\n                // 解析证书信息\n                let certInfo = CertificateManager.shared.parseCertificateInfo(certPath: certPath)\n\n                // 创建证书记录\n                let certificate = Certificate(\n                    domains: [site.primaryDomain],\n                    certificateType: .letsEncrypt,\n                    certificatePath: certPath,\n                    privateKeyPath: keyPath,\n                    issuer: certInfo?.issuer ?? " = "\n                }\n\n                // Solicitar certificado\n                let (certPath, keyPath) = try await letsEncryptService.issueCertificate(\n                    domain: site.primaryDomain,\n                    email: email,\n                    webroot: site.documentRoot,\n                    outputDir: sslDir\n                )\n\n                // Analizar información del certificado\n                let certInfo = CertificateManager.shared.parseCertificateInfo(certPath: certPath)\n\n                // Crear registro del certificado\n                let certificate = Certificate(\n                    domains: [site.primaryDomain],\n                    certificateType: .letsEncrypt,\n                    certificatePath: certPath,\n                    privateKeyPath: keyPath,\n                    issuer: certInfo?.issuer ?? ";
" }\n\n    /// Apache 站点配置目录\n    var apacheVhostsPath: String { " = " }\n\n    /// Directorio de configuración de sitios Apache\n    var apacheVhostsPath: String { ";
"\n        }\n        return line\n    }\n\n    // 在未管理区域中直接替换单条记录，以保持其未管理状态\n    private func updateUnmanagedEntryInRawHosts(oldEntry: HostEntry, newEntry: HostEntry) throws {\n        let content = try String(contentsOfFile: hostsPath, encoding: .utf8)\n        let lines = content.components(separatedBy: .newlines)\n        var inManagedSection = false\n        var replaced = false\n        var newLines: [String] = []\n\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed == markerStart {\n                inManagedSection = true\n                newLines.append(line)\n                continue\n            }\n            if trimmed == markerEnd {\n                inManagedSection = false\n                newLines.append(line)\n                continue\n            }\n\n            if !inManagedSection,\n               !replaced,\n               let parsed = parseHostLine(line),\n               parsed.ip == oldEntry.ip,\n               parsed.hostname == oldEntry.hostname,\n               (parsed.comment ?? " = "\n        }\n        return line\n    }\n\n    // Reemplazar directamente una entrada individual en el archivo hosts sin gestión, para mantener su estado sin gestión\n    private func updateUnmanagedEntryInRawHosts(oldEntry: HostEntry, newEntry: HostEntry) throws {\n        let content = try String(contentsOfFile: hostsPath, encoding: .utf8)\n        let lines = content.components(separatedBy: .newlines)\n        var inManagedSection = false\n        var replaced = false\n        var newLines: [String] = []\n\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed == markerStart {\n                inManagedSection = true\n                newLines.append(line)\n                continue\n            }\n            if trimmed == markerEnd {\n                inManagedSection = false\n                newLines.append(line)\n                continue\n            }\n\n            if !inManagedSection,\n               !replaced,\n               let parsed = parseHostLine(line),\n               parsed.ip == oldEntry.ip,\n               parsed.hostname == oldEntry.hostname,\n               (parsed.comment ?? ";
"你是一个经验丰富的运维工程师和程序员。请分析下面这段软件日志（可能是 Nginx, PHP, MySQL 错误日志或系统日志），解释它代表什么错误或行为，用中文以大白话说明，并提供可能的修复建议或排查思路。" = "Eres un experimentado ingeniero de operaciones y programador. Por favor, analiza el siguiente registro de software (puede ser un registro de errores de Nginx, PHP, MySQL o un registro del sistema), explica qué error o comportamiento representa, descríbelo en español coloquial y proporciona posibles soluciones o pasos para su diagnóstico.";
") ? String(siteDirectory.dropLast()) : siteDirectory\n        let parentDir = (siteDir as NSString).deletingLastPathComponent\n        \n        // 如果父目录为空（根目录），跳过检查\n        guard !parentDir.isEmpty && parentDir != " = ") ? String(siteDirectory.dropLast()) : siteDirectory\n        let parentDir = (siteDir as NSString).deletingLastPathComponent\n        \n        // Si el directorio padre está vacío (directorio raíz), omitir la verificación\n        guard !parentDir.isEmpty && parentDir != ";
")\n            )\n            loadSoftwareVersions()\n            throw error\n        }\n\n        loadSoftwareVersions()\n    }\n\n    /// 修改 Nginx 配置文件中的监听端口（根据工作空间状态选择配置文件）\n    private func configureNginxPort(port: Int) async throws {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        var modified = false\n\n        // 检查工作空间是否已初始化\n        if let config = WorkspaceManager.shared.config, config.isInitialized {\n            // 工作空间模式：只修改工作空间的配置文件\n            let wsNginxConf = config.nginxConfigFile\n            if FileManager.default.fileExists(atPath: wsNginxConf) {\n                try updateNginxListenPort(at: wsNginxConf, port: port)\n                modified = true\n            }\n\n            // 检查工作空间 servers 目录中的 default.conf\n            let wsServersDir = " = ")\n            )\n            loadSoftwareVersions()\n            throw error\n        }\n\n        loadSoftwareVersions()\n    }\n\n    /// Modificar el puerto de escucha en el archivo de configuración de Nginx (seleccionar el archivo según el estado del espacio de trabajo)\n    private func configureNginxPort(port: Int) async throws {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        var modified = false\n\n        // Verificar si el espacio de trabajo ya está inicializado\n        if let config = WorkspaceManager.shared.config, config.isInitialized {\n            // Modo espacio de trabajo: modificar únicamente el archivo de configuración del espacio de trabajo\n            let wsNginxConf = config.nginxConfigFile\n            if FileManager.default.fileExists(atPath: wsNginxConf) {\n                try updateNginxListenPort(at: wsNginxConf, port: port)\n                modified = true\n            }\n\n            // Verificar el archivo default.conf en el directorio servers del espacio de trabajo\n            let wsServersDir = ";
"   - 端口: \\(parsed.ports.map { String($0) }.joined(separator: " = "   - Puerto: \\(parsed.ports.map { String($0) }.joined(separator: ";
",\n            webServer: site.webServer,\n            phpVersion: site.phpVersion,\n            sslStatus: .notConfigured, // 复制的站点默认不启用SSL\n            status: .stopped,\n            isEnabled: false,\n            hasBackup: false,\n            backupCount: 0,\n            dailyTraffic: " = ",\n            webServer: site.webServer,\n            phpVersion: site.phpVersion,\n            sslStatus: .notConfigured, // Los sitios copiados no tienen SSL habilitado por defecto\n            status: .stopped,\n            isEnabled: false,\n            hasBackup: false,\n            backupCount: 0,\n            dailyTraffic: ";
"HTTP状态码：\\(httpResponse.statusCode)" = "Código de estado HTTP: \\(httpResponse.statusCode)";
")\n                    .font(.system(size: 12, design: .monospaced))\n                    .padding(12)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n                    .background(Color.gray.opacity(AppTokens.Opacity.light))\n                    .cornerRadius(8)\n            }\n            .padding(20)\n            .background(Color.blue.opacity(AppTokens.Opacity.ultraLight))\n            .cornerRadius(12)\n\n            // 根据安装状态显示不同的按钮\n            if case .installing = installer.installationStatus {\n                VStack(spacing: 12) {\n                    ProgressView()\n                        .scaleEffect(1.2)\n                    Text(installer.installationProgress)\n                        .font(.system(size: 13))\n                        .foregroundColor(.secondary)\n                }\n            } else if case .completed = installer.installationStatus {\n                VStack(spacing: 12) {\n                    Image(systemName: " = ")\n                    .font(.system(size: 12, design: .monospaced))\n                    .padding(12)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n                    .background(Color.gray.opacity(AppTokens.Opacity.light))\n                    .cornerRadius(8)\n            }\n            .padding(20)\n            .background(Color.blue.opacity(AppTokens.Opacity.ultraLight))\n            .cornerRadius(12)\n\n            // Mostrar botones diferentes según el estado de instalación\n            if case .installing = installer.installationStatus {\n                VStack(spacing: 12) {\n                    ProgressView()\n                        .scaleEffect(1.2)\n                    Text(installer.installationProgress)\n                        .font(.system(size: 13))\n                        .foregroundColor(.secondary)\n                }\n            } else if case .completed = installer.installationStatus {\n                VStack(spacing: 12) {\n                    Image(systemName: ";
"停用站点前自动备份" = "Realizar copia de seguridad automática antes de desactivar el sitio";
"\\(address) (本机)" = "\\(address) (local)";
"\n                result.append(updatedConfig)\n            }\n            // 如果有 Apache 后端，不添加 Nginx 代理配置（已经在 Apache 配置中体现了）\n        }\n\n        // 检查直接的 Nginx 配置（非代理）是否与 Apache 配置冲突\n        for nginxConfig in nginxDirectConfigs {\n            var updatedConfig = nginxConfig\n\n            // 查找是否有相同域名的 Apache 配置\n            let conflictingApache = apacheConfigs.first { apacheConfig in\n                Set(apacheConfig.parsed.domains).intersection(Set(nginxConfig.parsed.domains)).count > 0\n            }\n\n            if let conflictApache = conflictingApache {\n                // 场景3：同时存在完整的Nginx配置和Apache配置（冲突）\n                updatedConfig.completenessStatus = .conflictingConfigs\n                updatedConfig.relatedConfigPath = conflictApache.filePath\n            }\n\n            result.append(updatedConfig)\n        }\n\n        return result\n    }\n\n    /// 修复配置完整性（自动生成缺失的配置文件）\n    func repairConfigCompleteness(_ config: FoundVhostConfig) async throws {\n        switch config.completenessStatus {\n        case .complete:\n            // 配置完整，无需修复\n            return\n\n        case .needsProxyConfig:\n            // Apache 配置存在，但缺少 Nginx 代理配置\n            try await generateMissingNginxProxy(for: config)\n\n        case .missingBackendConfig:\n            // Nginx 代理配置存在，但缺少 Apache 后端配置\n            // 尝试根据域名信息搜索工作空间中的对应目录\n            try await generateMissingApacheBackend(for: config)\n\n        case .conflictingConfigs:\n            // 配置冲突，需要用户手动选择，不提供自动修复\n            throw SiteError.operationFailed(" = "\n                result.append(updatedConfig)\n            }\n            // Si hay un backend Apache, no agregar la configuración de proxy de Nginx (ya está reflejada en la configuración de Apache)\n        }\n\n        // Verificar si existe un conflicto entre la configuración directa de Nginx (sin proxy) y la configuración de Apache\n        for nginxConfig in nginxDirectConfigs {\n            var updatedConfig = nginxConfig\n\n            // Buscar si existe una configuración de Apache con el mismo dominio\n            let conflictingApache = apacheConfigs.first { apacheConfig in\n                Set(apacheConfig.parsed.domains).intersection(Set(nginxConfig.parsed.domains)).count > 0\n            }\n\n            if let conflictApache = conflictingApache {\n                // Escenario 3: existen simultáneamente una configuración completa de Nginx y una de Apache (conflicto)\n                updatedConfig.completenessStatus = .conflictingConfigs\n                updatedConfig.relatedConfigPath = conflictApache.filePath\n            }\n\n            result.append(updatedConfig)\n        }\n\n        return result\n    }\n\n    /// Reparar la integridad de la configuración (generar automáticamente los archivos de configuración faltantes)\n    func repairConfigCompleteness(_ config: FoundVhostConfig) async throws {\n        switch config.completenessStatus {\n        case .complete:\n            // La configuración es completa, no se requiere reparación\n            return\n\n        case .needsProxyConfig:\n            // Existe la configuración de Apache, pero falta la configuración de proxy de Nginx\n            try await generateMissingNginxProxy(for: config)\n\n        case .missingBackendConfig:\n            // Existe la configuración de proxy de Nginx, pero falta la configuración de backend de Apache\n            // Intentar buscar el directorio correspondiente en el espacio de trabajo según la información del dominio\n            try await generateMissingApacheBackend(for: config)\n\n        case .conflictingConfigs:\n            // Conflicto de configuraciones; se requiere intervención manual del usuario, no se ofrece reparación automática\n            throw SiteError.operationFailed(";
"\n        }\n        let listenPort = mode == .takeOver ? 80 : port\n\n        // 读取现有配置\n        guard var config = try? String(contentsOfFile: configPath, encoding: .utf8) else {\n            return false\n        }\n\n        // 修改监听端口\n        config = config.replacingOccurrences(\n            of: " = "\n        }\n        let listenPort = mode == .takeOver ? 80 : port\n\n        // Leer la configuración existente\n        guard var config = try? String(contentsOfFile: configPath, encoding: .utf8) else {\n            return false\n        }\n\n        // Modificar el puerto de escucha\n        config = config.replacingOccurrences(\n            of: ";
")\n        } else {\n            directoryPermissionStatus = validation\n        }\n    }\n    \n    // 更新可用的子目录列表\n    private func updateAvailableSubdirs() {\n        guard !siteDirectory.isEmpty else {\n            availableSubdirs = []\n            return\n        }\n        \n        let siteDir = siteDirectory.hasSuffix(" = ")\n        } else {\n            directoryPermissionStatus = validation\n        }\n    }\n    \n    // Actualizar la lista de subdirectorios disponibles\n    private func updateAvailableSubdirs() {\n        guard !siteDirectory.isEmpty else {\n            availableSubdirs = []\n            return\n        }\n        \n        let siteDir = siteDirectory.hasSuffix(";
")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            // 差异内容\n            HStack(spacing: 0) {\n                // 源规则\n                VStack(alignment: .leading, spacing: 8) {\n                    Text(" = ")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            // Contenido de diferencias\n            HStack(spacing: 0) {\n                // Reglas de origen\n                VStack(alignment: .leading, spacing: 8) {\n                    Text(";
" : database.note)\n                .font(.system(size: 11))\n                .foregroundColor(.secondary)\n                .lineLimit(1)\n                .frame(width: 100, alignment: .leading)\n\n            Spacer()\n\n            // 操作按钮\n            actionColumn\n                .frame(width: 180, alignment: .trailing)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 8)\n        .background(isHovered ? Color.gray.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n        .onHover { hovering in isHovered = hovering }\n        .sheetWithLocale(isPresented: $showPermissionsSheet) {\n            RemoteDatabasePermissionsSheet(\n                databaseName: database.name,\n                username: database.username.isEmpty ? database.name : database.username,\n                connector: connector\n            )\n        }\n        .sheetWithLocale(isPresented: $showChangePasswordSheet) {\n            RemoteChangePasswordSheet(\n                databaseName: database.name,\n                username: database.username.isEmpty ? database.name : database.username,\n                connector: connector\n            )\n        }\n        .sheetWithLocale(isPresented: $showImportSheet) {\n            RemoteImportDatabaseSheet(\n                databaseName: database.name,\n                connector: connector\n            )\n        }\n    }\n\n    // 密码列\n    private var passwordColumn: some View {\n        Group {\n            if database.userPassword.isEmpty {\n                // 远程已存在的数据库，无法获取密码\n                Text(" = " : database.note)\n                .font(.system(size: 11))\n                .foregroundColor(.secondary)\n                .lineLimit(1)\n                .frame(width: 100, alignment: .leading)\n\n            Spacer()\n\n            // Botones de acción\n            actionColumn\n                .frame(width: 180, alignment: .trailing)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 8)\n        .background(isHovered ? Color.gray.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n        .onHover { hovering in isHovered = hovering }\n        .sheetWithLocale(isPresented: $showPermissionsSheet) {\n            RemoteDatabasePermissionsSheet(\n                databaseName: database.name,\n                username: database.username.isEmpty ? database.name : database.username,\n                connector: connector\n            )\n        }\n        .sheetWithLocale(isPresented: $showChangePasswordSheet) {\n            RemoteChangePasswordSheet(\n                databaseName: database.name,\n                username: database.username.isEmpty ? database.name : database.username,\n                connector: connector\n            )\n        }\n        .sheetWithLocale(isPresented: $showImportSheet) {\n            RemoteImportDatabaseSheet(\n                databaseName: database.name,\n                connector: connector\n            )\n        }\n    }\n\n    // Columna de contraseña\n    private var passwordColumn: some View {\n        Group {\n            if database.userPassword.isEmpty {\n                // Base de datos remota ya existente, no se puede recuperar la contraseña\n                Text(";
"\n        }\n\n        if let url = URL(string: urlString) {\n            NSWorkspace.shared.open(url)\n        }\n    }\n\n    /// 在 Finder 中打开默认站点目录\n    func openDefaultSiteInFinder() {\n        if let path = getActiveDefaultSitePath() {\n            // 检查目录是否存在\n            if FileManager.default.fileExists(atPath: path) {\n                NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: path)\n            } else {\n                lastError = " = "\n        }\n\n        if let url = URL(string: urlString) {\n            NSWorkspace.shared.open(url)\n        }\n    }\n\n    /// Abrir el directorio predeterminado del sitio en Finder\n    func openDefaultSiteInFinder() {\n        if let path = getActiveDefaultSitePath() {\n            // Verificar si el directorio existe\n            if FileManager.default.fileExists(atPath: path) {\n                NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: path)\n            } else {\n                lastError = ";
"生效时间: \\(formatDate(certificate.validFrom))" = "Fecha de vigencia: \\(formatDate(certificate.validFrom))";
"]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            let completed = waitForProcessWithTimeout(process, timeout: 5.0)\n            if completed {\n                let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                AppState.cachedBrewServicesOutput = String(data: data, encoding: .utf8)\n            }\n        } catch {\n            // 忽略错误\n        }\n    }\n\n    /// 解析并缓存 Homebrew 可执行文件路径\n    private nonisolated func resolveBrewPath() -> String? {\n        // 已经解析过，直接复用\n        if let path = AppState.cachedBrewPath {\n            return path\n        }\n\n        let brewPaths = [" = "]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            let completed = waitForProcessWithTimeout(process, timeout: 5.0)\n            if completed {\n                let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                AppState.cachedBrewServicesOutput = String(data: data, encoding: .utf8)\n            }\n        } catch {\n            // Ignorar el error\n        }\n    }\n\n    /// Analizar y almacenar en caché la ruta ejecutable de Homebrew\n    private nonisolated func resolveBrewPath() -> String? {\n        // Ya resuelto, reutilizar directamente\n        if let path = AppState.cachedBrewPath {\n            return path\n        }\n\n        let brewPaths = [";
" {\n                ContentViewV2()\n                    .environmentObject(appState)\n                    .environmentObject(softwareManager)\n                    .environmentObject(siteManager)\n                    .environment(\\.locale, Locale(identifier: appLanguageRaw))\n            } else {\n                ContentView()\n                    .environmentObject(appState)\n                    .environmentObject(softwareManager)\n                    .environmentObject(siteManager)\n                    .environment(\\.locale, Locale(identifier: appLanguageRaw))\n            }\n        }\n        .windowStyle(.hiddenTitleBar) // 隐藏原生标题栏\n        .defaultSize(width: BuildInfo.uiVersion == " = " {\n                ContentViewV2()\n                    .environmentObject(appState)\n                    .environmentObject(softwareManager)\n                    .environmentObject(siteManager)\n                    .environment(\\.locale, Locale(identifier: appLanguageRaw))\n            } else {\n                ContentView()\n                    .environmentObject(appState)\n                    .environmentObject(softwareManager)\n                    .environmentObject(siteManager)\n                    .environment(\\.locale, Locale(identifier: appLanguageRaw))\n            }\n        }\n        .windowStyle(.hiddenTitleBar) // Ocultar la barra de título nativa\n        .defaultSize(width: BuildInfo.uiVersion == ";
")\n    }()\n\n    private init() {\n        loadToken()\n        Task {\n            await detectLocalIPAddresses()\n        }\n    }\n\n    // MARK: - IP 地址检测\n\n    /// 检测本机所有有效的 IP 地址\n    func detectLocalIPAddresses() async {\n        isDetectingIPs = true\n\n        let addresses = await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                var result: [LocalIPAddress] = []\n\n                // 始终添加 127.0.0.1\n                result.append(LocalIPAddress(\n                    address: " = ")\n    }()\n\n    private init() {\n        loadToken()\n        Task {\n            await detectLocalIPAddresses()\n        }\n    }\n\n    // MARK: - Detección de direcciones IP\n\n    /// Detecta todas las direcciones IP válidas del equipo local\n    func detectLocalIPAddresses() async {\n        isDetectingIPs = true\n\n        let addresses = await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                var result: [LocalIPAddress] = []\n\n                // Siempre agregar 127.0.0.1\n                result.append(LocalIPAddress(\n                    address: ";
") {\n                errorLines.append(line.trimmingCharacters(in: .whitespaces))\n            }\n        }\n\n        if errorLines.isEmpty {\n            // 返回最后几行作为错误信息\n            let lastLines = lines.suffix(5).filter { !$0.isEmpty }\n            return lastLines.joined(separator: " = ") {\n                errorLines.append(line.trimmingCharacters(in: .whitespaces))\n            }\n        }\n\n        if errorLines.isEmpty {\n            // Devolver las últimas líneas como mensaje de error\n            let lastLines = lines.suffix(5).filter { !$0.isEmpty }\n            return lastLines.joined(separator: ";
", false, false)\n        }\n\n        // appState 还在加载中，使用端口检测作为备用方案\n        // 检查 MySQL 客户端是否存在\n        let mysqlExists = MySQLClientPaths.findMysql() != nil\n        if mysqlExists {\n            // MySQL 客户端存在，使用端口检测判断运行状态\n            let isRunning = actualMySQLPort != nil\n            return (" = ", false, false)\n        }\n\n        // El appState aún se está cargando; usar la detección de puertos como solución alternativa\n        // Verificar si el cliente MySQL existe\n        let mysqlExists = MySQLClientPaths.findMysql() != nil\n        if mysqlExists {\n            // El cliente MySQL existe; usar la detección de puertos para determinar su estado de ejecución\n            let isRunning = actualMySQLPort != nil\n            return (";
", packageName]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            // 使用带超时的等待\n            let completed = waitForProcessWithTimeout(process, timeout: 5.0)\n            if !completed {\n                process.terminate()\n                return " = ", packageName]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            // Usar espera con tiempo de espera\n            let completed = waitForProcessWithTimeout(process, timeout: 5.0)\n            if !completed {\n                process.terminate()\n                return ";
")\n                                .font(.caption)\n                                .foregroundColor(.secondary)\n                        }\n                    }\n                    .padding()\n                    .background(Color.orange.opacity(AppTokens.Opacity.light))\n                    .cornerRadius(6)\n                    .frame(width: 200)\n                }\n            }\n            .padding()\n\n            Divider()\n\n            // 右侧：转换后的规则\n            VStack(alignment: .leading, spacing: 12) {\n                HStack {\n                    Text(" = ")\n                                .font(.caption)\n                                .foregroundColor(.secondary)\n                        }\n                    }\n                    .padding()\n                    .background(Color.orange.opacity(AppTokens.Opacity.light))\n                    .cornerRadius(6)\n                    .frame(width: 200)\n                }\n            }\n            .padding()\n\n            Divider()\n\n            // Panel derecho: reglas convertidas\n            VStack(alignment: .leading, spacing: 12) {\n                HStack {\n                    Text(";
") {\n            return [21]\n        }\n        return []\n    }\n\n    /// 生成端口冲突的建议\n    private func generatePortConflictSuggestion(\n        port: Int,\n        processName: String?,\n        software: SoftwareVersion\n    ) -> String {\n        let process = processName ?? " = ") {\n            return [21]\n        }\n        return []\n    }\n\n    /// Generar una sugerencia para conflictos de puertos\n    private func generatePortConflictSuggestion(\n        port: Int,\n        processName: String?,\n        software: SoftwareVersion\n    ) -> String {\n        let process = processName ?? ";
"❌ 目录不存在: \\(path)" = "❌ El directorio no existe: \\(path)";
" ? Color.clear : Color.clear)\n            .contentShape(Rectangle())\n        }\n        .menuStyle(.borderlessButton)\n        .buttonStyle(.plain)\n    }\n\n    // Tab 标签栏\n    private var databaseTabBar: some View {\n        ScrollView(.horizontal, showsIndicators: false) {\n            HStack(alignment: .bottom, spacing: 4) {\n                // 全部数据库标签\n                DatabaseTabItem(\n                    title: " = " ? Color.clear : Color.clear)\n            .contentShape(Rectangle())\n        }\n        .menuStyle(.borderlessButton)\n        .buttonStyle(.plain)\n    }\n\n    // Barra de pestañas\n    private var databaseTabBar: some View {\n        ScrollView(.horizontal, showsIndicators: false) {\n            HStack(alignment: .bottom, spacing: 4) {\n                // Pestaña de todas las bases de datos\n                DatabaseTabItem(\n                    title: ";
"\n        default:\n            return serviceName.lowercased()\n        }\n    }\n\n    /// 执行 brew services 命令\n    private func executeBrewCommand(_ action: String, serviceName: String, completion: @escaping (Bool) -> Void) {\n        DispatchQueue.global(qos: .userInitiated).async {\n            let process = Process()\n            let brewPaths = [" = "\n        default:\n            return serviceName.lowercased()\n        }\n    }\n\n    /// Ejecutar el comando brew services\n    private func executeBrewCommand(_ action: String, serviceName: String, completion: @escaping (Bool) -> Void) {\n        DispatchQueue.global(qos: .userInitiated).async {\n            let process = Process()\n            let brewPaths = [";
"\n                # HTTP VirtualHost\n                # Apache 后端端口：\\(httpPort)\n                <VirtualHost *:\\(httpPort)>\n                    ServerName \\(domains.first ?? " = "\n                # HTTP VirtualHost\n                # Puerto de backend de Apache: \\(httpPort)\n                <VirtualHost *:\\(httpPort)>\n                    ServerName \\(domains.first ?? ";
"  // 网站目录（站点根目录）\n    @State private var runningDirectory = " = "  // Directorio del sitio web (directorio raíz del sitio)\n    @State private var runningDirectory = ";
",\n                version: detectedMySQLVersion,\n                state: mysqlServiceInfo.isRunning ? .running : .stopped,\n                port: actualMySQLPort ?? 3306\n            ))\n        }\n        .sheetWithLocale(isPresented: $showMySQLPerformanceSheet) {\n            MySQLPerformanceSheet(mysqlVersion: detectedMySQLVersion)\n        }\n        // 密码检测失败浮动提示\n        .overlay(alignment: .top) {\n            if showPasswordTip {\n                PasswordDetectionTipView(\n                    isPresented: $showPasswordTip,\n                    onResetPassword: { showRootPasswordSheet = true }\n                )\n                .transition(.move(edge: .top).combined(with: .opacity))\n                .animation(.easeInOut(duration: 0.3), value: showPasswordTip)\n                .padding(.top, 60)\n            }\n        }\n        .onAppear {\n            detectMySQLPort()\n            detectRootPassword()\n            fetchLocalDatabases()\n            detectMySQLVersion()\n            // 默认打开本地 MySQL 连接标签页\n            openDatabaseTab(name: " = ",\n                version: detectedMySQLVersion,\n                state: mysqlServiceInfo.isRunning ? .running : .stopped,\n                port: actualMySQLPort ?? 3306\n            ))\n        }\n        .sheetWithLocale(isPresented: $showMySQLPerformanceSheet) {\n            MySQLPerformanceSheet(mysqlVersion: detectedMySQLVersion)\n        }\n        // Sugerencia flotante para fallo en la detección de contraseña\n        .overlay(alignment: .top) {\n            if showPasswordTip {\n                PasswordDetectionTipView(\n                    isPresented: $showPasswordTip,\n                    onResetPassword: { showRootPasswordSheet = true }\n                )\n                .transition(.move(edge: .top).combined(with: .opacity))\n                .animation(.easeInOut(duration: 0.3), value: showPasswordTip)\n                .padding(.top, 60)\n            }\n        }\n        .onAppear {\n            detectMySQLPort()\n            detectRootPassword()\n            fetchLocalDatabases()\n            detectMySQLVersion()\n            // Abrir por defecto la pestaña de conexión local a MySQL\n            openDatabaseTab(name: ";
"上次修改: \\(formatDate(lastModified))" = "Última modificación: \\(formatDate(lastModified))";
"无法添加 \\(phpTap)，请检查网络连接" = "No se puede añadir \\(phpTap); compruebe su conexión a Internet";
"\n                ]\n\n                // 如果已经检测到 root 密码，附加密码参数\n                if let pwd = self.rootPassword, !pwd.isEmpty {\n                    arguments.insert(" = "\n                ]\n\n                // Si ya se ha detectado la contraseña root, insertar el parámetro de contraseña\n                if let pwd = self.rootPassword, !pwd.isEmpty {\n                    arguments.insert(";
"\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 内容区域\n            contentArea\n\n            Divider()\n\n            // 底部按钮\n            bottomButtons\n        }\n        .frame(minWidth: 700, minHeight: 500)\n        .alert(" = "\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Barra de título\n            headerBar\n\n            Divider()\n\n            // Área de contenido\n            contentArea\n\n            Divider()\n\n            // Botones inferiores\n            bottomButtons\n        }\n        .frame(minWidth: 700, minHeight: 500)\n        .alert(";
"\\(service.name) 启动失败,请检查日志" = "Error al iniciar \\(service.name); revise los registros";
"\n    @State private var showNewPassword = false  // 是否显示新密码明文\n    @State private var showCurrentPassword = false  // 是否显示当前密码明文（远程模式）\n    @State private var forgotPassword = false   // 忘记密码模式（仅本地可用）\n    @State private var isSubmitting = false\n    @State private var showError = false\n    @State private var errorMessage = " = "\n    @State private var showNewPassword = false  // Indica si se muestra la nueva contraseña en texto claro\n    @State private var showCurrentPassword = false  // Indica si se muestra la contraseña actual en texto claro (modo remoto)\n    @State private var forgotPassword = false   // Modo «contraseña olvidada» (solo disponible localmente)\n    @State private var isSubmitting = false\n    @State private var showError = false\n    @State private var errorMessage = ";
"• 强力修复：根据站点信息重新生成配置文件，并补齐缺失的 hosts 记录" = "• Corrección potente: regenerar el archivo de configuración según la información del sitio y completar los registros faltantes en hosts";
"  // 关联的用户名（通常与数据库名相同）\n    var userPassword: String = " = "  // Nombre de usuario asociado (normalmente igual que el nombre de la base de datos)\n    var userPassword: String = ";
"\n            isPortValid = true\n            return\n        }\n\n        // 异步检查端口是否被占用\n        Task {\n            let portInfo = await PortDetectionService.shared.checkPort(port)\n            await MainActor.run {\n                if !portInfo.isAvailable {\n                    if let processName = portInfo.processName {\n                        portValidationMessage = " = "\n            isPortValid = true\n            return\n        }\n\n        // Comprobar asincrónicamente si el puerto está en uso\n        Task {\n            let portInfo = await PortDetectionService.shared.checkPort(port)\n            await MainActor.run {\n                if !portInfo.isAvailable {\n                    if let processName = portInfo.processName {\n                        portValidationMessage = ";
"\n        }\n    }\n\n    // 延迟加载表单配置（当切换到表单模式时才加载）\n    private func loadFormConfig() {\n        // 如果配置分组已加载，只需要重新解析值（可能配置文件已更新）\n        if configSections.isEmpty {\n            // 首次加载：创建配置分组\n            configSections = ServiceConfigDefinitions.getConfigs(\n                for: service.name,\n                version: service.version\n            )\n        }\n        \n        // 解析配置文件中的值并更新到表单（只有在配置文件内容不为空时）\n        if !configContent.isEmpty {\n            parseConfigValues(from: configContent)\n        }\n    }\n\n    // 从配置文件内容解析值\n    private func parseConfigValues(from content: String) {\n        let lines = content.components(separatedBy: .newlines)\n\n        // 更新已知配置项的值\n        for sectionIndex in configSections.indices {\n            for itemIndex in configSections[sectionIndex].items.indices {\n                let key = configSections[sectionIndex].items[itemIndex].key\n                if let value = extractValue(for: key, from: lines) {\n                    configSections[sectionIndex].items[itemIndex].value = value\n                }\n            }\n        }\n\n        // 同步加载当前端口\n        loadCurrentPort()\n    }\n\n    // MARK: - 端口配置方法\n\n    /// 加载当前端口（同步）\n    private func loadCurrentPort() {\n        let portData = loadPortData()\n        currentPort = portData.port\n        newPort = portData.port\n    }\n\n    /// 加载端口数据（在后台线程执行）\n    private func loadPortData() -> (port: String, error: String?) {\n        switch service.name {\n        case " = "\n        }\n    }\n\n    // Carga diferida de la configuración del formulario (solo se carga al cambiar al modo formulario)\n    private func loadFormConfig() {\n        // Si ya se han cargado los grupos de configuración, basta con volver a analizar los valores (es posible que el archivo de configuración se haya actualizado)\n        if configSections.isEmpty {\n            // Primera carga: crear los grupos de configuración\n            configSections = ServiceConfigDefinitions.getConfigs(\n                for: service.name,\n                version: service.version\n            )\n        }\n        \n        // Analizar los valores del archivo de configuración y actualizarlos en el formulario (solo si el contenido del archivo de configuración no está vacío)\n        if !configContent.isEmpty {\n            parseConfigValues(from: configContent)\n        }\n    }\n\n    // Analizar valores desde el contenido del archivo de configuración\n    private func parseConfigValues(from content: String) {\n        let lines = content.components(separatedBy: .newlines)\n\n        // Actualizar los valores de los elementos de configuración conocidos\n        for sectionIndex in configSections.indices {\n            for itemIndex in configSections[sectionIndex].items.indices {\n                let key = configSections[sectionIndex].items[itemIndex].key\n                if let value = extractValue(for: key, from: lines) {\n                    configSections[sectionIndex].items[itemIndex].value = value\n                }\n            }\n        }\n\n        // Cargar sincrónicamente el puerto actual\n        loadCurrentPort()\n    }\n\n    // MARK: - Métodos de configuración de puertos\n\n    /// Cargar el puerto actual (sincrónico)\n    private func loadCurrentPort() {\n        let portData = loadPortData()\n        currentPort = portData.port\n        newPort = portData.port\n    }\n\n    /// Cargar los datos del puerto (ejecutado en un hilo en segundo plano)\n    private func loadPortData() -> (port: String, error: String?) {\n        switch service.name {\n        case ";
"))\n                    .font(.system(size: 12))\n                    .foregroundColor(.secondary)\n            }\n\n            Spacer()\n\n            // Web服务器标签\n            Text(site.webServer.rawValue)\n                .font(.system(size: 11))\n                .padding(.horizontal, 8)\n                .padding(.vertical, 4)\n                .background(Color.blue.opacity(AppTokens.Opacity.light))\n                .cornerRadius(4)\n\n            // 操作按钮\n            Button(action: onViewHistory) {\n                HStack(spacing: 4) {\n                    Image(systemName: " = "))\n                    .font(.system(size: 12))\n                    .foregroundColor(.secondary)\n            }\n\n            Spacer()\n\n            // Etiqueta del servidor web\n            Text(site.webServer.rawValue)\n                .font(.system(size: 11))\n                .padding(.horizontal, 8)\n                .padding(.vertical, 4)\n                .background(Color.blue.opacity(AppTokens.Opacity.light))\n                .cornerRadius(4)\n\n            // Botón de acciones\n            Button(action: onViewHistory) {\n                HStack(spacing: 4) {\n                    Image(systemName: ";
")\n                            .foregroundColor(.secondary)\n                    }\n                    .buttonStyle(.plain)\n                }\n            }\n\n            // 详细信息（展开时显示）\n            if showDetails && !result.details.isEmpty {\n                VStack(alignment: .leading, spacing: 3) {\n                    ForEach(result.details, id: \\.self) { detail in\n                        Text(detail)\n                            .font(.caption)\n                            .foregroundColor(.secondary)\n                            .padding(.leading, 26)\n                    }\n                }\n            }\n        }\n        .padding(.vertical, 3)\n    }\n\n    // 获取进度信息\n    private func getProgressMessage() -> String {\n        // 根据检查类型返回更具体的进度信息\n        switch result.type {\n        case .hosts:\n            return " = ")\n                            .foregroundColor(.secondary)\n                    }\n                    .buttonStyle(.plain)\n                }\n            }\n\n            // Detalles (se muestran al expandir)\n            if showDetails && !result.details.isEmpty {\n                VStack(alignment: .leading, spacing: 3) {\n                    ForEach(result.details, id: \\.self) { detail in\n                        Text(detail)\n                            .font(.caption)\n                            .foregroundColor(.secondary)\n                            .padding(.leading, 26)\n                    }\n                }\n            }\n        }\n        .padding(.vertical, 3)\n    }\n\n    // Obtener el mensaje de progreso\n    private func getProgressMessage() -> String {\n        // Devolver información de progreso más específica según el tipo de comprobación\n        switch result.type {\n        case .hosts:\n            return ";
"配置迁移失败: \\(error.localizedDescription)" = "Error al migrar la configuración: \\(error.localizedDescription)";
"确定要删除数据库「\\(name)」吗？此操作不可恢复！" = "¿Está seguro de que desea eliminar la base de datos «\\(name)»? Esta acción no se puede deshacer.";
"\n                if let last = stack.last, last.0 == expectedLeft {\n                    stack.removeLast()\n                } else {\n                    // 不对称的右括号\n                    errors.append(NSRange(location: i, length: 1))\n                }\n                \n                formatted.append(" = "\n                if let last = stack.last, last.0 == expectedLeft {\n                    stack.removeLast()\n                } else {\n                    // Paréntesis derecho asimétrico\n                    errors.append(NSRange(location: i, length: 1))\n                }\n                \n                formatted.append(";
")\n    }()\n    \n    // JSON存储管理器（使用actor确保线程安全）\n    private let storage: JSONStorage<Database>\n\n    init() {\n        // 初始化存储管理器\n        storage = JSONStorage<Database>(fileURL: databasesFilePath, backupURL: databasesBackupURL, saveDelay: 0.5)\n        loadDatabases()\n    }\n\n    // 加载数据库列表（异步）\n    func loadDatabases() {\n        Task {\n            do {\n                let loadedDatabases = try await storage.load()\n                await MainActor.run {\n                    databases = loadedDatabases\n                }\n            } catch {\n                print(" = ")\n    }()\n    \n    // Administrador de almacenamiento JSON (usa actor para garantizar seguridad en hilos)\n    private let storage: JSONStorage<Database>\n\n    init() {\n        // Inicializar el administrador de almacenamiento\n        storage = JSONStorage<Database>(fileURL: databasesFilePath, backupURL: databasesBackupURL, saveDelay: 0.5)\n        loadDatabases()\n    }\n\n    // Cargar la lista de bases de datos (asincrónico)\n    func loadDatabases() {\n        Task {\n            do {\n                let loadedDatabases = try await storage.load()\n                await MainActor.run {\n                    databases = loadedDatabases\n                }\n            } catch {\n                print(";
")\n            return\n        }\n        \n        Task {\n            isTestingConnection = true\n            connectionTestResult = nil\n            \n            // 调用DatabaseManager的测试连接方法\n            let result = await databaseManager.testRemoteConnection(\n                serverAddress: serverAddress,\n                port: portInt,\n                username: username,\n                password: password\n            )\n            \n            connectionTestResult = (result.success, result.message ?? (result.success ? " = ")\n            return\n        }\n        \n        Task {\n            isTestingConnection = true\n            connectionTestResult = nil\n            \n            // Llamar al método de prueba de conexión de DatabaseManager\n            let result = await databaseManager.testRemoteConnection(\n                serverAddress: serverAddress,\n                port: portInt,\n                username: username,\n                password: password\n            )\n            \n            connectionTestResult = (result.success, result.message ?? (result.success ? ";
"📁 扫描目录: \\(path)" = "📁 Escaneando directorio: \\(path)";
"\n\n        // 获取该版本支持的扩展\n        let allExtensions = ServiceConfigDefinitions.getExtensions(for: phpVersion)\n\n        // 获取已安装的扩展\n        let installed = await getInstalledExtensions(for: phpVersion)\n        installedExtensionsByVersion[phpVersion] = installed\n\n        // 构建扩展信息列表\n        var extensions: [PHPExtensionInfo] = []\n        for meta in allExtensions {\n            let isInstalled = installed.contains(meta.name.lowercased())\n            let status: ExtensionStatus = isInstalled ? .enabled : .notInstalled\n            extensions.append(PHPExtensionInfo(meta: meta, status: status, isEnabled: isInstalled))\n        }\n\n        extensionsByVersion[phpVersion] = extensions\n        isLoading = false\n        currentOperation = " = "\n\n        // Obtener las extensiones compatibles con esta versión\n        let allExtensions = ServiceConfigDefinitions.getExtensions(for: phpVersion)\n\n        // Obtener las extensiones instaladas\n        let installed = await getInstalledExtensions(for: phpVersion)\n        installedExtensionsByVersion[phpVersion] = installed\n\n        // Construir la lista de información de extensiones\n        var extensions: [PHPExtensionInfo] = []\n        for meta in allExtensions {\n            let isInstalled = installed.contains(meta.name.lowercased())\n            let status: ExtensionStatus = isInstalled ? .enabled : .notInstalled\n            extensions.append(PHPExtensionInfo(meta: meta, status: status, isEnabled: isInstalled))\n        }\n\n        extensionsByVersion[phpVersion] = extensions\n        isLoading = false\n        currentOperation = ";
"]\n        var result = databaseManager.localMySQLDatabases\n\n        // 过滤系统数据库\n        if hideSystemDatabases {\n            result = result.filter { !systemDatabases.contains($0.lowercased()) }\n        }\n\n        // 搜索过滤\n        if !searchText.isEmpty {\n            result = result.filter { $0.localizedCaseInsensitiveContains(searchText) }\n        }\n\n        return result\n    }\n\n    // 底部分页\n    private var bottomPagination: some View {\n        HStack(spacing: 12) {\n            // 批量操作\n            Menu {\n                Button(" = "]\n        var result = databaseManager.localMySQLDatabases\n\n        // Filtrar bases de datos del sistema\n        if hideSystemDatabases {\n            result = result.filter { !systemDatabases.contains($0.lowercased()) }\n        }\n\n        // Filtro de búsqueda\n        if !searchText.isEmpty {\n            result = result.filter { $0.localizedCaseInsensitiveContains(searchText) }\n        }\n\n        return result\n    }\n\n    // Paginación inferior\n    private var bottomPagination: some View {\n        HStack(spacing: 12) {\n            // Operaciones por lotes\n            Menu {\n                Button(";
")\n                            .font(.system(size: 11))\n                            .foregroundColor(.secondary)\n                    }\n\n                    Spacer()\n\n                    // 状态标签\n                    if config.alreadyExists {\n                        HStack(spacing: 4) {\n                            Image(systemName: " = ")\n                            .font(.system(size: 11))\n                            .foregroundColor(.secondary)\n                    }\n\n                    Spacer()\n\n                    // Etiqueta de estado\n                    if config.alreadyExists {\n                        HStack(spacing: 4) {\n                            Image(systemName: ";
")\n                            .foregroundColor(.red)\n                            .font(.system(size: 12))\n                    } else {\n                        ProgressView()\n                            .scaleEffect(0.6)\n                            .frame(width: 12, height: 12)\n                    }\n                    Text(installing.progress.phase.rawValue)\n                        .font(.system(size: 11))\n                        .foregroundColor(statusColor)\n                }\n\n                Spacer()\n\n                // 关闭按钮（失败时显示）\n                if isFailed, let dismiss = onDismiss {\n                    Button(action: dismiss) {\n                        Image(systemName: " = ")\n                            .foregroundColor(.red)\n                            .font(.system(size: 12))\n                    } else {\n                        ProgressView()\n                            .scaleEffect(0.6)\n                            .frame(width: 12, height: 12)\n                    }\n                    Text(installing.progress.phase.rawValue)\n                        .font(.system(size: 11))\n                        .foregroundColor(statusColor)\n                }\n\n                Spacer()\n\n                // Botón de cierre (se muestra solo en caso de fallo)\n                if isFailed, let dismiss = onDismiss {\n                    Button(action: dismiss) {\n                        Image(systemName: ";
"本地域名不支持 Let's Encrypt：\\(domain)" = "Los dominios locales no son compatibles con Let's Encrypt: \\(domain)";
",\n                errorMessage: error,\n                errorDetail: detail\n            )\n        }\n    }\n\n    // 从 brew 输出中提取错误信息\n    private func extractErrorFromOutput(_ output: String) -> String {\n        let lines = output.components(separatedBy: .newlines)\n        var errorLines: [String] = []\n\n        for line in lines {\n            let lower = line.lowercased()\n            if lower.contains(" = ",\n                errorMessage: error,\n                errorDetail: detail\n            )\n        }\n    }\n\n    // Extraer el mensaje de error desde la salida de brew\n    private func extractErrorFromOutput(_ output: String) -> String {\n        let lines = output.components(separatedBy: .newlines)\n        var errorLines: [String] = []\n\n        for line in lines {\n            let lower = line.lowercased()\n            if lower.contains(";
"\n        databases[index].note = note\n\n        // 立即保存\n        try await forceSaveDatabases()\n    }\n\n    // 删除远程数据库连接\n    func deleteRemoteDatabase(id: UUID) async throws {\n        guard let index = databases.firstIndex(where: { $0.id == id && $0.type == .remote }) else {\n            throw DatabaseError.operationFailed(" = "\n        databases[index].note = note\n\n        // Guardar inmediatamente\n        try await forceSaveDatabases()\n    }\n\n    // Eliminar una conexión remota a una base de datos\n    func deleteRemoteDatabase(id: UUID) async throws {\n        guard let index = databases.firstIndex(where: { $0.id == id && $0.type == .remote }) else {\n            throw DatabaseError.operationFailed(";
")\n                        .font(.system(size: 13, weight: .semibold))\n                        .padding(.horizontal)\n                    TextEditor(text: .constant(source))\n                        .font(.system(.body, design: .monospaced))\n                        .macTextEditor()\n                }\n                .frame(maxWidth: .infinity)\n\n                Divider().background(Color.gray.opacity(0.2))\n\n                // 转换后规则\n                VStack(alignment: .leading, spacing: 8) {\n                    Text(" = ")\n                        .font(.system(size: 13, weight: .semibold))\n                        .padding(.horizontal)\n                    TextEditor(text: .constant(source))\n                        .font(.system(.body, design: .monospaced))\n                        .macTextEditor()\n                }\n                .frame(maxWidth: .infinity)\n\n                Divider().background(Color.gray.opacity(0.2))\n\n                // Reglas convertidas\n                VStack(alignment: .leading, spacing: 8) {\n                    Text(";
", sql])\n                process.arguments = arguments\n\n                let errorPipe = Pipe()\n                process.standardError = errorPipe\n                process.standardOutput = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // 强制重置 root 密码（忘记密码时使用）\n    private func forceResetRootPassword() async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let brewPaths = [\n                    " = ", sql])\n                process.arguments = arguments\n\n                let errorPipe = Pipe()\n                process.standardError = errorPipe\n                process.standardOutput = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // Restablecer forzadamente la contraseña de root (para usar cuando se olvida la contraseña)\n    private func forceResetRootPassword() async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let brewPaths = [\n                    ";
"\n\n                    continuation.resume(returning: !output.isEmpty && process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // MARK: - 生成 JSON 报告\n    func generateReport(for site: Site) async throws -> GoAccessReport {\n        // 检查 GoAccess 是否安装\n        guard await isGoAccessInstalled() else {\n            throw GoAccessError.notInstalled\n        }\n\n        // 获取日志文件路径\n        let logPath = LogService.shared.getAccessLogPath(for: site)\n\n        guard FileManager.default.fileExists(atPath: logPath) else {\n            throw GoAccessError.logFileNotFound\n        }\n\n        // 在后台线程执行 GoAccess\n        return try await Task.detached {\n            let jsonOutput = try self.runGoAccess(logPath: logPath)\n            let report = try self.parseJSON(jsonOutput)\n            return report\n        }.value\n    }\n\n    // MARK: - 从自定义路径生成报告\n    func generateReportFromPath(logPath: String) async throws -> GoAccessReport {\n        // 检查 GoAccess 是否安装\n        guard await isGoAccessInstalled() else {\n            throw GoAccessError.notInstalled\n        }\n\n        // 检查日志文件是否存在\n        guard FileManager.default.fileExists(atPath: logPath) else {\n            throw GoAccessError.logFileNotFound\n        }\n\n        // 在后台线程执行 GoAccess\n        return try await Task.detached {\n            let jsonOutput = try self.runGoAccess(logPath: logPath)\n            let report = try self.parseJSON(jsonOutput)\n            return report\n        }.value\n    }\n\n    // MARK: - 获取 GoAccess 路径\n    nonisolated private func getGoAccessPath() -> String? {\n        // 尝试常见的 GoAccess 路径\n        let possiblePaths = [\n            " = "\n\n                    continuation.resume(returning: !output.isEmpty && process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // MARK: - 生成 JSON 报告\n    func generateReport(for site: Site) async throws -> GoAccessReport {\n        // 检查 GoAccess 是否安装\n        guard await isGoAccessInstalled() else {\n            throw GoAccessError.notInstalled\n        }\n\n        // 获取日志文件路径\n        let logPath = LogService.shared.getAccessLogPath(for: site)\n\n        guard FileManager.default.fileExists(atPath: logPath) else {\n            throw GoAccessError.logFileNotFound\n        }\n\n        // 在后台线程执行 GoAccess\n        return try await Task.detached {\n            let jsonOutput = try self.runGoAccess(logPath: logPath)\n            let report = try self.parseJSON(jsonOutput)\n            return report\n        }.value\n    }\n\n    // MARK: - 从自定义路径生成报告\n    func generateReportFromPath(logPath: String) async throws -> GoAccessReport {\n        // 检查 GoAccess 是否安装\n        guard await isGoAccessInstalled() else {\n            throw GoAccessError.notInstalled\n        }\n\n        // 检查日志文件是否存在\n        guard FileManager.default.fileExists(atPath: logPath) else {\n            throw GoAccessError.logFileNotFound\n        }\n\n        // 在后台线程执行 GoAccess\n        return try await Task.detached {\n            let jsonOutput = try self.runGoAccess(logPath: logPath)\n            let report = try self.parseJSON(jsonOutput)\n            return report\n        }.value\n    }\n\n    // MARK: - 获取 GoAccess 路径\n    nonisolated private func getGoAccessPath() -> String? {\n        // 尝试常见的 GoAccess 路径\n        let possiblePaths = [\n            ";
")) && service.installed {\n                return true\n            }\n        }\n        return false\n    }\n\n    // 检查 FTP 是否已安装\n    private var isFTPInstalled: Bool {\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if (name.contains(" = ")) && service.installed {\n                return true\n            }\n        }\n        return false\n    }\n\n    // 检查 FTP 是否已安装\n    private var isFTPInstalled: Bool {\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if (name.contains(";
"#\n                                if let regex = try? NSRegularExpression(pattern: pattern),\n                                   let match = regex.firstMatch(in: line, range: NSRange(line.startIndex..., in: line)),\n                                   match.numberOfRanges > 1,\n                                   let range = Range(match.range(at: 1), in: line),\n                                   let port = Int(String(line[range])) {\n                                    // 排除 33060 等非 HTTP 端口\n                                    if port != 33060 {\n                                        continuation.resume(returning: port)\n                                        return\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    continuation.resume(returning: nil)\n                } catch {\n                    continuation.resume(returning: nil)\n                }\n            }\n        }\n    }\n\n    /// 检测 Apache 运行状态\n    private func detectApacheStatus() async {\n        let result = await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: " = "#\n                                if let regex = try? NSRegularExpression(pattern: pattern),\n                                   let match = regex.firstMatch(in: line, range: NSRange(line.startIndex..., in: line)),\n                                   match.numberOfRanges > 1,\n                                   let range = Range(match.range(at: 1), in: line),\n                                   let port = Int(String(line[range])) {\n                                    // 排除 33060 等非 HTTP 端口\n                                    if port != 33060 {\n                                        continuation.resume(returning: port)\n                                        return\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    continuation.resume(returning: nil)\n                } catch {\n                    continuation.resume(returning: nil)\n                }\n            }\n        }\n    }\n\n    /// 检测 Apache 运行状态\n    private func detectApacheStatus() async {\n        let result = await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: ";
", isOn: $isSelected)\n                .labelsHidden()\n                .frame(width: 30)\n\n            // 数据库名\n            Text(name)\n                .font(.system(size: 12))\n                .frame(width: 120, alignment: .leading)\n\n            // 用户名\n            Text(dbUsername)\n                .font(.system(size: 12))\n                .frame(width: 100, alignment: .leading)\n\n            // 密码\n            passwordColumn\n                .frame(width: 100, alignment: .leading)\n\n            // 备份\n            backupColumn\n                .frame(width: 120, alignment: .leading)\n\n            // 权限（可点击修改）\n            permissionColumn\n                .frame(width: 90, alignment: .leading)\n\n            // 备注\n            Text(isSystemDatabase ? LocalizedStringKey(" = ", isOn: $isSelected)\n                .labelsHidden()\n                .frame(width: 30)\n\n            // 数据库名\n            Text(name)\n                .font(.system(size: 12))\n                .frame(width: 120, alignment: .leading)\n\n            // 用户名\n            Text(dbUsername)\n                .font(.system(size: 12))\n                .frame(width: 100, alignment: .leading)\n\n            // 密码\n            passwordColumn\n                .frame(width: 100, alignment: .leading)\n\n            // 备份\n            backupColumn\n                .frame(width: 120, alignment: .leading)\n\n            // 权限（可点击修改）\n            permissionColumn\n                .frame(width: 90, alignment: .leading)\n\n            // 备注\n            Text(isSystemDatabase ? LocalizedStringKey(";
")\n                    .font(.title2)\n                    .fontWeight(.semibold)\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n            .padding(.horizontal, 20)\n            .padding(.vertical, 16)\n\n            // 主菜单区域：占满侧边栏剩余高度\n            VStack(spacing: 4) {\n                ForEach(SidebarItem.mainItems) { item in\n                    SidebarButton(item: item, isSelected: selection == item) {\n                        selection = item\n                        if item != .tools {\n                            selectedToolShortcut = nil\n                        }\n                    }\n\n                    // 工具箱二级菜单：默认展开，超出高度后只在局部滚动\n                    if item == .tools {\n                        // 工具箱二级菜单占用主菜单的剩余高度，内容超出时局部滚动\n                        ScrollView(.vertical, showsIndicators: true) {\n                            VStack(spacing: 2) {\n                                ForEach(ToolShortcut.allCases, id: \\.self) { tool in\n                                    Button {\n                                        withAnimation(.easeInOut(duration: 0.1)) {\n                                            selection = .tools\n                                            selectedToolShortcut = tool\n                                            selectedTool = tool\n                                        }\n                                    } label: {\n                                        HStack(spacing: 8) {\n                                            Image(systemName: tool.icon)\n                                                .font(.system(size: 11))\n                                                .frame(width: 14)\n                                            Text(LocalizedStringKey(tool.title))\n                                                .font(.system(size: 13, weight: selectedToolShortcut == tool ? .medium : .regular))\n                                                .lineLimit(1)\n                                            Spacer()\n                                        }\n                                        // 扩大二级菜单整行命中区域，避免只能点中文字\n                                        .frame(maxWidth: .infinity, alignment: .leading)\n                                        .padding(.horizontal, 12)\n                                        .padding(.vertical, 8)\n                                        .background(\n                                            RoundedRectangle(cornerRadius: 6)\n                                                .fill(selectedToolShortcut == tool ? Color.orange.opacity(AppTokens.Opacity.hover) : Color.clear)\n                                        )\n                                        .foregroundColor(selectedToolShortcut == tool ? .orange : .secondary)\n                                        .contentShape(Rectangle())\n                                    }\n                                    .buttonStyle(.plain)\n                                    .frame(maxWidth: .infinity, alignment: .leading)\n                                }\n                            }\n                            .padding(.leading, 34)\n                            .padding(.trailing, 8)\n                            .padding(.vertical, 4)\n                        }\n                        .frame(maxHeight: .infinity, alignment: .top)\n                    }\n                }\n            }\n            .padding(.horizontal, 12)\n            .frame(maxHeight: .infinity, alignment: .top)\n\n            // 底部主题与设置切换\n            HStack(spacing: 4) {\n                Button {\n                    appThemeModeRaw = AppThemeMode.light.rawValue\n                } label: {\n                    Text(" = ")\n                    .font(.title2)\n                    .fontWeight(.semibold)\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n            .padding(.horizontal, 20)\n            .padding(.vertical, 16)\n\n            // 主菜单区域：占满侧边栏剩余高度\n            VStack(spacing: 4) {\n                ForEach(SidebarItem.mainItems) { item in\n                    SidebarButton(item: item, isSelected: selection == item) {\n                        selection = item\n                        if item != .tools {\n                            selectedToolShortcut = nil\n                        }\n                    }\n\n                    // 工具箱二级菜单：默认展开，超出高度后只在局部滚动\n                    if item == .tools {\n                        // 工具箱二级菜单占用主菜单的剩余高度，内容超出时局部滚动\n                        ScrollView(.vertical, showsIndicators: true) {\n                            VStack(spacing: 2) {\n                                ForEach(ToolShortcut.allCases, id: \\.self) { tool in\n                                    Button {\n                                        withAnimation(.easeInOut(duration: 0.1)) {\n                                            selection = .tools\n                                            selectedToolShortcut = tool\n                                            selectedTool = tool\n                                        }\n                                    } label: {\n                                        HStack(spacing: 8) {\n                                            Image(systemName: tool.icon)\n                                                .font(.system(size: 11))\n                                                .frame(width: 14)\n                                            Text(LocalizedStringKey(tool.title))\n                                                .font(.system(size: 13, weight: selectedToolShortcut == tool ? .medium : .regular))\n                                                .lineLimit(1)\n                                            Spacer()\n                                        }\n                                        // 扩大二级菜单整行命中区域，避免只能点中文字\n                                        .frame(maxWidth: .infinity, alignment: .leading)\n                                        .padding(.horizontal, 12)\n                                        .padding(.vertical, 8)\n                                        .background(\n                                            RoundedRectangle(cornerRadius: 6)\n                                                .fill(selectedToolShortcut == tool ? Color.orange.opacity(AppTokens.Opacity.hover) : Color.clear)\n                                        )\n                                        .foregroundColor(selectedToolShortcut == tool ? .orange : .secondary)\n                                        .contentShape(Rectangle())\n                                    }\n                                    .buttonStyle(.plain)\n                                    .frame(maxWidth: .infinity, alignment: .leading)\n                                }\n                            }\n                            .padding(.leading, 34)\n                            .padding(.trailing, 8)\n                            .padding(.vertical, 4)\n                        }\n                        .frame(maxHeight: .infinity, alignment: .top)\n                    }\n                }\n            }\n            .padding(.horizontal, 12)\n            .frame(maxHeight: .infinity, alignment: .top)\n\n            // 底部主题与设置切换\n            HStack(spacing: 4) {\n                Button {\n                    appThemeModeRaw = AppThemeMode.light.rawValue\n                } label: {\n                    Text(";
"主配置: \\(brewNginxConf) (普通文件)" = "主配置: \\(brewNginxConf) (普通文件)";
")\n                                .font(.system(size: 11))\n                                .foregroundColor(category == selectedCategory ? .blue : .secondary)\n                                .padding(.horizontal, 8)\n                                .padding(.vertical, 2)\n                                .background(\n                                    Capsule()\n                                        .fill(category == selectedCategory ? Color.white : Color.gray.opacity(AppTokens.Opacity.light))\n                                )\n                        }\n                        .padding(.vertical, 10)\n                        .padding(.horizontal, 24)\n                        .background(category == selectedCategory ? Color.blue.opacity(AppTokens.Opacity.light) : Color.clear)\n                        .contentShape(Rectangle())\n                        .overlay(\n                            // 右侧选中指示条 (Border 样式)\n                            HStack {\n                                Spacer()\n                                if category == selectedCategory {\n                                    Rectangle()\n                                        .fill(Color.blue)\n                                        .frame(width: 3)\n                                        // 确保蓝色条在分割线之上，并且紧贴右边缘\n                                        .offset(x: 1) \n                                }\n                            }\n                        )\n                    }\n                    .buttonStyle(.plain)\n                }\n                Spacer()\n            }\n            .frame(width: 200)\n            .glass(.panel)\n            .overlay(\n                // 分割线，只在非选中项右侧显示（或者完全依靠右侧内容区的背景色差）\n                // 实际上 HTML 原型是整个 Sidebar 右侧有一条淡淡的线，\n                // 但选中项的蓝色条盖住了它，或者选中项本身背景延伸到了边缘。\n                // 简单起见，我们统一画一条线，蓝色条层级更高即可。\n                Rectangle()\n                    .frame(width: 1)\n                    .foregroundColor(Color.gray.opacity(AppTokens.Opacity.light)),\n                alignment: .trailing\n            )\n\n            // 右侧版本列表\n            VStack(spacing: 0) {\n                // Toolbar\n                HStack(spacing: 12) {\n                    HStack {\n                        Image(systemName: " = ")\n                                .font(.system(size: 11))\n                                .foregroundColor(category == selectedCategory ? .blue : .secondary)\n                                .padding(.horizontal, 8)\n                                .padding(.vertical, 2)\n                                .background(\n                                    Capsule()\n                                        .fill(category == selectedCategory ? Color.white : Color.gray.opacity(AppTokens.Opacity.light))\n                                )\n                        }\n                        .padding(.vertical, 10)\n                        .padding(.horizontal, 24)\n                        .background(category == selectedCategory ? Color.blue.opacity(AppTokens.Opacity.light) : Color.clear)\n                        .contentShape(Rectangle())\n                        .overlay(\n                            // 右侧选中指示条 (Border 样式)\n                            HStack {\n                                Spacer()\n                                if category == selectedCategory {\n                                    Rectangle()\n                                        .fill(Color.blue)\n                                        .frame(width: 3)\n                                        // 确保蓝色条在分割线之上，并且紧贴右边缘\n                                        .offset(x: 1) \n                                }\n                            }\n                        )\n                    }\n                    .buttonStyle(.plain)\n                }\n                Spacer()\n            }\n            .frame(width: 200)\n            .glass(.panel)\n            .overlay(\n                // 分割线，只在非选中项右侧显示（或者完全依靠右侧内容区的背景色差）\n                // 实际上 HTML 原型是整个 Sidebar 右侧有一条淡淡的线，\n                // 但选中项的蓝色条盖住了它，或者选中项本身背景延伸到了边缘。\n                // 简单起见，我们统一画一条线，蓝色条层级更高即可。\n                Rectangle()\n                    .frame(width: 1)\n                    .foregroundColor(Color.gray.opacity(AppTokens.Opacity.light)),\n                alignment: .trailing\n            )\n\n            // 右侧版本列表\n            VStack(spacing: 0) {\n                // Toolbar\n                HStack(spacing: 12) {\n                    HStack {\n                        Image(systemName: ";
"default.conf: \\(brewDefaultConf) (普通文件)" = "default.conf: \\(brewDefaultConf) (普通文件)";
"\n        if fm.fileExists(atPath: backupPath) {\n            try fm.removeItem(atPath: backupPath)\n        }\n        try fm.moveItem(atPath: source, toPath: backupPath)\n        try fm.createSymbolicLink(atPath: source, withDestinationPath: workspace)\n    }\n\n    /// 更新 PHP-FPM 配置文件中的端口\n    private func updatePHPFPMConfig(at path: String, port: Int) throws {\n        var content = try String(contentsOfFile: path, encoding: .utf8)\n\n        // 替换 listen 配置\n        // 匹配格式: listen = 127.0.0.1:9000 或 listen = 9000 或 listen = /path/to/socket\n        // 使用单一正则，匹配 " = "\n        if fm.fileExists(atPath: backupPath) {\n            try fm.removeItem(atPath: backupPath)\n        }\n        try fm.moveItem(atPath: source, toPath: backupPath)\n        try fm.createSymbolicLink(atPath: source, withDestinationPath: workspace)\n    }\n\n    /// 更新 PHP-FPM 配置文件中的端口\n    private func updatePHPFPMConfig(at path: String, port: Int) throws {\n        var content = try String(contentsOfFile: path, encoding: .utf8)\n\n        // 替换 listen 配置\n        // 匹配格式: listen = 127.0.0.1:9000 或 listen = 9000 或 listen = /path/to/socket\n        // 使用单一正则，匹配 ";
"执行终止命令失败: \\(error.localizedDescription)" = "执行终止命令失败: \\(error.localizedDescription)";
" }\n\n    /// 数据目录\n    var dataPath: String { " = " }\n\n    /// 数据目录\n    var dataPath: String { ";
"\n    }\n\n    /// 获取 MySQL 数据目录（优先返回存在的目录）\n    func getMySQLDataPath(version: String? = nil) -> String {\n        let ver = version ?? " = "\n    }\n\n    /// 获取 MySQL 数据目录（优先返回存在的目录）\n    func getMySQLDataPath(version: String? = nil) -> String {\n        let ver = version ?? ";
"\n            isSaving = false\n            return\n        }\n\n        DispatchQueue.global(qos: .userInitiated).async {\n            do {\n                var content = try String(contentsOfFile: configPath, encoding: .utf8)\n\n                // 更新配置值\n                for (key, value) in configValues {\n                    content = updateConfigValue(in: content, key: key, value: value)\n                }\n\n                try content.write(toFile: configPath, atomically: true, encoding: .utf8)\n\n                DispatchQueue.main.async {\n                    self.successMessage = " = "\n            isSaving = false\n            return\n        }\n\n        DispatchQueue.global(qos: .userInitiated).async {\n            do {\n                var content = try String(contentsOfFile: configPath, encoding: .utf8)\n\n                // 更新配置值\n                for (key, value) in configValues {\n                    content = updateConfigValue(in: content, key: key, value: value)\n                }\n\n                try content.write(toFile: configPath, atomically: true, encoding: .utf8)\n\n                DispatchQueue.main.async {\n                    self.successMessage = ";
"配置 \\(service.name)" = "配置 \\(service.name)";
")\n                    .font(.system(size: 48))\n                    .foregroundColor(.blue)\n                    .padding(.top, 20)\n\n                // 说明文字\n                VStack(spacing: 12) {\n                    Text(" = ")\n                    .font(.system(size: 48))\n                    .foregroundColor(.blue)\n                    .padding(.top, 20)\n\n                // 说明文字\n                VStack(spacing: 12) {\n                    Text(";
"\n            )\n        }\n        try content.write(toFile: path, atomically: true, encoding: .utf8)\n    }\n\n    // MARK: - Apache 端口修改\n\n    func changeApachePort(_ software: SoftwareVersion, newPort: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(" = "\n            )\n        }\n        try content.write(toFile: path, atomically: true, encoding: .utf8)\n    }\n\n    // MARK: - Apache 端口修改\n\n    func changeApachePort(_ software: SoftwareVersion, newPort: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(";
"\\(service.name) 不支持重新加载配置，将使用重启代替" = "\\(service.name) 不支持重新加载配置，将使用重启代替";
"\n    }\n\n    /// 获取 Redis 配置搜索路径列表\n    func getRedisConfigSearchPaths() -> [String] {\n        var paths: [String] = []\n\n        if let config = workspaceManager.config, config.isInitialized {\n            paths.append(config.redisConfigFile)\n        }\n\n        paths.append(" = "\n    }\n\n    /// 获取 Redis 配置搜索路径列表\n    func getRedisConfigSearchPaths() -> [String] {\n        var paths: [String] = []\n\n        if let config = workspaceManager.config, config.isInitialized {\n            paths.append(config.redisConfigFile)\n        }\n\n        paths.append(";
"brew 输出:\\n\\(result.output)" = "brew 输出:\\n\\(result.output)";
"   - 文档根目录: \\(parsed.documentRoot)" = "   - 文档根目录: \\(parsed.documentRoot)";
") {\n                return service.isRunning\n            }\n        }\n        return false\n    }\n\n    // 根据运行状态自动选择默认 Web 服务器\n    private func selectDefaultWebServer() {\n        let nginxRunning = isWebServerRunning(.nginx)\n        let apacheRunning = isWebServerRunning(.apache)\n\n        if nginxRunning && apacheRunning {\n            // 两者都运行，默认选择 Nginx\n            selectedWebServer = .nginx\n        } else if nginxRunning {\n            // 只有 Nginx 运行\n            selectedWebServer = .nginx\n        } else if apacheRunning {\n            // 只有 Apache 运行\n            selectedWebServer = .apache\n        }\n        // 如果都没运行，保持默认值 .nginx\n    }\n\n    // 检查 MySQL 是否已安装\n    private var isMySQLInstalled: Bool {\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if (name.contains(" = ") {\n                return service.isRunning\n            }\n        }\n        return false\n    }\n\n    // Seleccionar automáticamente el servidor web predeterminado según su estado de ejecución\n    private func selectDefaultWebServer() {\n        let nginxRunning = isWebServerRunning(.nginx)\n        let apacheRunning = isWebServerRunning(.apache)\n\n        if nginxRunning && apacheRunning {\n            // Ambos están en ejecución: seleccionar Nginx como predeterminado\n            selectedWebServer = .nginx\n        } else if nginxRunning {\n            // Solo Nginx está en ejecución\n            selectedWebServer = .nginx\n        } else if apacheRunning {\n            // Solo Apache está en ejecución\n            selectedWebServer = .apache\n        }\n        // Si ninguno está en ejecución, se mantiene el valor predeterminado (.nginx)\n    }\n\n    // Verificar si MySQL está instalado\n    private var isMySQLInstalled: Bool {\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if (name.contains(";
"安装 phpMyAdmin 失败: \\(error.localizedDescription)" = "Error al instalar phpMyAdmin: \\(error.localizedDescription)";
" : appSettings.defaultSiteRoot)\n\n        if panel.runModal() == .OK {\n            if let url = panel.url {\n                appSettings.defaultSiteRoot = url.path\n                appSettings.siteRootManuallySet = true\n            }\n        }\n    }\n\n    // MARK: - 数据目录行\n    private var dataDirectoryRow: some View {\n        LabeledContent(" = " : appSettings.defaultSiteRoot)\n\n        if panel.runModal() == .OK {\n            if let url = panel.url {\n                appSettings.defaultSiteRoot = url.path\n                appSettings.siteRootManuallySet = true\n            }\n        }\n    }\n\n    // MARK: - Fila del directorio de datos\n    private var dataDirectoryRow: some View {\n        LabeledContent(";
")\n                                .font(.system(size: 11))\n                        }\n                        .padding(.horizontal, 6)\n                        .padding(.vertical, 3)\n                        .background(Color.green.opacity(AppTokens.Opacity.hover))\n                        .foregroundColor(.green)\n                        .cornerRadius(4)\n                    }\n\n                    // 端口标签\n                    if !config.parsed.ports.isEmpty {\n                        Text(" = ")\n                                .font(.system(size: 11))\n                        }\n                        .padding(.horizontal, 6)\n                        .padding(.vertical, 3)\n                        .background(Color.green.opacity(AppTokens.Opacity.hover))\n                        .foregroundColor(.green)\n                        .cornerRadius(4)\n                    }\n\n                    // Etiqueta de puertos\n                    if !config.parsed.ports.isEmpty {\n                        Text(";
" }\n\n    // MARK: - 初始化\n\n    init(rootPath: String) {\n        self.rootPath = rootPath\n        self.isInitialized = false\n        self.createdAt = Date()\n        self.updatedAt = Date()\n    }\n}\n\n// MARK: - 目录结构定义\n\n/// 需要创建的目录列表\nenum WorkspaceDirectories {\n    /// 获取所有需要创建的基础目录\n    static func getBaseDirectories(config: WorkspaceConfig) -> [String] {\n        [\n            config.confPath,\n            config.dataPath,\n            config.logsPath,\n            config.wwwPath,\n            config.sslPath,\n            // MySQL\n            " = " }\n\n    // MARK: - Inicialización\n\n    init(rootPath: String) {\n        self.rootPath = rootPath\n        self.isInitialized = false\n        self.createdAt = Date()\n        self.updatedAt = Date()\n    }\n}\n\n// MARK: - Definición de la estructura de directorios\n\n/// Lista de directorios que deben crearse\nenum WorkspaceDirectories {\n    /// Obtener todos los directorios base necesarios para su creación\n    static func getBaseDirectories(config: WorkspaceConfig) -> [String] {\n        [\n            config.confPath,\n            config.dataPath,\n            config.logsPath,\n            config.wwwPath,\n            config.sslPath,\n            // MySQL\n            ";
"\n\n    // 状态\n    @State private var isCreating = false\n    @State private var errorMessage: String?\n\n    // 字符集选项\n    private let charsetOptions = [" = "\n\n    // Estado\n    @State private var isCreating = false\n    @State private var errorMessage: String?\n\n    // Opciones de juego de caracteres\n    private let charsetOptions = [";
") {\n            // MySQL 错误日志路径（按优先级尝试）\n            // 先尝试直接扫描目录找到 .err 文件\n            let mysqlDataDir = " = ") {\n            // Ruta del registro de errores de MySQL (intentos por orden de prioridad)\n            // Primero intenta escanear directamente el directorio para encontrar el archivo .err\n            let mysqlDataDir = ";
"\n                        conversionWarnings.removeAll()\n                    }\n                    .buttonStyle(.bordered)\n                    .controlSize(.small)\n                }\n            }\n            .frame(maxWidth: .infinity)\n            .padding()\n\n            Divider()\n\n            // 中间：转换按钮和警告\n            VStack(spacing: 16) {\n                Button(action: { convertRules() }) {\n                    HStack {\n                        Image(systemName: " = "\n                        conversionWarnings.removeAll()\n                    }\n                    .buttonStyle(.bordered)\n                    .controlSize(.small)\n                }\n            }\n            .frame(maxWidth: .infinity)\n            .padding()\n\n            Divider()\n\n            // Centro: botón de conversión y advertencias\n            VStack(spacing: 16) {\n                Button(action: { convertRules() }) {\n                    HStack {\n                        Image(systemName: ";
"\n\n    // 端口配置相关\n    @State private var currentPort: String = " = "\n\n    // Configuración de puertos\n    @State private var currentPort: String = ";
"✗ 证书文件不存在: \\(certPath)" = "✗ El archivo de certificado no existe: \\(certPath)";
" }\n\n    // MARK: - Apache 路径\n\n    /// Apache 配置目录\n    var apacheConfPath: String { " = " }\n\n    // MARK: - Rutas de Apache\n\n    /// Directorio de configuración de Apache\n    var apacheConfPath: String { ";
")\n        }\n\n        databases.remove(at: index)\n        try await forceSaveDatabases()\n    }\n\n    // 测试远程数据库连接（简化版）\n    func testRemoteConnection(serverAddress: String, port: Int, username: String, password: String) async -> (success: Bool, message: String?, version: String?) {\n        // 使用 MySQLClientPaths 查找可用客户端\n        guard let mysqlPath = MySQLClientPaths.findMysql() else {\n            return (false, " = ")\n        }\n\n        databases.remove(at: index)\n        try await forceSaveDatabases()\n    }\n\n    // Probar la conexión a una base de datos remota (versión simplificada)\n    func testRemoteConnection(serverAddress: String, port: Int, username: String, password: String) async -> (success: Bool, message: String?, version: String?) {\n        // Usar MySQLClientPaths para buscar un cliente disponible\n        guard let mysqlPath = MySQLClientPaths.findMysql() else {\n            return (false, ";
") {\n                // 跳过已在预定义列表中的版本\n                if predefinedBrewNames.contains(packageName) {\n                    continue\n                }\n\n                // 提取版本号\n                let displayName: String\n                if packageName == " = ") {\n                // Saltar las versiones ya presentes en la lista predefinida\n                if predefinedBrewNames.contains(packageName) {\n                    continue\n                }\n\n                // Extraer el número de versión\n                let displayName: String\n                if packageName == ";
"]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            guard let output = String(data: data, encoding: .utf8), !output.isEmpty else {\n                return nil\n            }\n\n            // 解析 lsof 输出\n            let lines = output.components(separatedBy: .newlines)\n            guard lines.count > 1 else { return nil }\n\n            // 第二行包含进程信息\n            let processLine = lines[1]\n            let components = processLine.split(separator: " = "]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            guard let output = String(data: data, encoding: .utf8), !output.isEmpty else {\n                return nil\n            }\n\n            // Analizar la salida de lsof\n            let lines = output.components(separatedBy: .newlines)\n            guard lines.count > 1 else { return nil }\n\n            // La segunda línea contiene información del proceso\n            let processLine = lines[1]\n            let components = processLine.split(separator: ";
" ? Color.clear : Color(NSColor.controlBackgroundColor))\n    }\n\n    // 获取配置文件路径\n    private func getConfigPath() -> String? {\n        var configPaths: [String] = []\n\n        switch service.name {\n        case " = " ? Color.clear : Color(NSColor.controlBackgroundColor))\n    }\n\n    // Obtener la ruta del archivo de configuración\n    private func getConfigPath() -> String? {\n        var configPaths: [String] = []\n\n        switch service.name {\n        case ";
"批量导入: \\(candidate.directoryName)" = "Importación masiva: \\(candidate.directoryName)";
"\n    @State private var hideSystemDatabases = true\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 顶部标签切换\n            tabSelector\n\n            Divider()\n\n            // 内容区域\n            if connector.isLoading {\n                loadingView\n            } else if let error = connector.errorMessage {\n                errorView(error)\n            } else {\n                contentView\n            }\n        }\n        .onAppear {\n            setupConnector()\n        }\n    }\n\n    // 设置连接器\n    private func setupConnector() {\n        connector.configure(\n            host: tab.host,\n            port: tab.port,\n            username: " = "\n    @State private var hideSystemDatabases = true\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Pestañas superiores\n            tabSelector\n\n            Divider()\n\n            // Área de contenido\n            if connector.isLoading {\n                loadingView\n            } else if let error = connector.errorMessage {\n                errorView(error)\n            } else {\n                contentView\n            }\n        }\n        .onAppear {\n            setupConnector()\n        }\n    }\n\n    // Configurar el conector\n    private func setupConnector() {\n        connector.configure(\n            host: tab.host,\n            port: tab.port,\n            username: ";
"] as? Bool {\n                completion(running)\n                return\n            }\n        } catch {\n            // 忽略错误\n        }\n\n        // 备用方案：检查进程是否存在\n        let pgrepProcess = Process()\n        pgrepProcess.executableURL = URL(fileURLWithPath: " = "] as? Bool {\n                completion(running)\n                return\n            }\n        } catch {\n            // Ignorar el error\n        }\n\n        // Solución alternativa: comprobar si el proceso existe\n        let pgrepProcess = Process()\n        pgrepProcess.executableURL = URL(fileURLWithPath: ";
") {\n                return (port, nil)\n            }\n        }\n\n        // 使用 Homebrew 路径\n        let defaultConfPath = " = ") {\n                return (port, nil)\n            }\n        }\n\n        // Usar la ruta de Homebrew\n        let defaultConfPath = ";
"\n\n                return !output.isEmpty && process.terminationStatus == 0\n            } catch {\n                return false\n            }\n        }.value\n    }\n\n    // MARK: - 安装 GoAccess\n    func installGoAccess() async throws {\n        installationStatus = .checking\n        installationProgress = " = "\n\n                return !output.isEmpty && process.terminationStatus == 0\n            } catch {\n                return false\n            }\n        }.value\n    }\n\n    // MARK: - Instalar GoAccess\n    func installGoAccess() async throws {\n        installationStatus = .checking\n        installationProgress = ";
") {\n                let parts = trimmed.dropFirst(4).dropLast().trimmingCharacters(in: .whitespaces)\n                if !parts.isEmpty {\n                    // 展开 html 为实际路径\n                    if parts == " = ") {\n                let parts = trimmed.dropFirst(4).dropLast().trimmingCharacters(in: .whitespaces)\n                if !parts.isEmpty {\n                    // Expandir HTML a la ruta real\n                    if parts == ";
")!]\n        panel.allowsMultipleSelection = false\n        if panel.runModal() == .OK {\n            selectedFile = panel.url\n        }\n    }\n\n    private func performImport() {\n        guard let file = selectedFile else { return }\n        isImporting = true\n        errorMessage = nil\n        Task {\n            let result = await connector.importDatabase(name: databaseName, fromFile: file)\n            await MainActor.run {\n                if result.success {\n                    dismiss()\n                } else {\n                    errorMessage = result.error\n                }\n                isImporting = false\n            }\n        }\n    }\n}\n\n// MARK: - 远程权限设置 Sheet\nstruct RemotePermissionsSheet: View {\n    let databaseName: String\n    let username: String\n    @ObservedObject var connector: MySQLConnector\n    @Environment(\\.dismiss) private var dismiss\n\n    @State private var selectedAccessType: AccessType = .everyone\n    @State private var customIP = " = ")!]\n        panel.allowsMultipleSelection = false\n        if panel.runModal() == .OK {\n            selectedFile = panel.url\n        }\n    }\n\n    private func performImport() {\n        guard let file = selectedFile else { return }\n        isImporting = true\n        errorMessage = nil\n        Task {\n            let result = await connector.importDatabase(name: databaseName, fromFile: file)\n            await MainActor.run {\n                if result.success {\n                    dismiss()\n                } else {\n                    errorMessage = result.error\n                }\n                isImporting = false\n            }\n        }\n    }\n}\n\n// MARK: - Hoja de configuración de permisos remotos\nstruct RemotePermissionsSheet: View {\n    let databaseName: String\n    let username: String\n    @ObservedObject var connector: MySQLConnector\n    @Environment(\\.dismiss) private var dismiss\n\n    @State private var selectedAccessType: AccessType = .everyone\n    @State private var customIP = ";
"剩余天数: \\(remainingDays) 天" = "Días restantes: \\(remainingDays) días";
"\n    }\n\n    /// 获取 Nginx 配置搜索路径列表\n    func getNginxConfigSearchPaths() -> [String] {\n        var paths: [String] = []\n\n        if let config = workspaceManager.config, config.isInitialized {\n            paths.append(config.nginxConfigFile)\n        }\n\n        paths.append(" = "\n    }\n\n    /// Obtener la lista de rutas de búsqueda para la configuración de Nginx\n    func getNginxConfigSearchPaths() -> [String] {\n        var paths: [String] = []\n\n        if let config = workspaceManager.config, config.isInitialized {\n            paths.append(config.nginxConfigFile)\n        }\n\n        paths.append(";
" }\n        }\n    }\n\n    /// 获取所有有效证书\n    var validCertificates: [Certificate] {\n        return certificates.filter { $0.status == .valid }\n    }\n\n    /// 获取即将过期的证书\n    var expiringSoonCertificates: [Certificate] {\n        return certificates.filter { $0.status == .expiringSoon }\n    }\n\n    /// 获取已过期的证书\n    var expiredCertificates: [Certificate] {\n        return certificates.filter { $0.status == .expired }\n    }\n\n    // MARK: - 证书解析\n\n    /// 证书信息结构\n    struct CertificateInfo {\n        let domains: [String]\n        let issuer: String\n        let validFrom: Date\n        let validUntil: Date\n    }\n\n    /// 解析证书文件信息\n    func parseCertificateInfo(certPath: String) -> CertificateInfo? {\n        // 使用 openssl 命令读取证书信息\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = " }\n        }\n    }\n\n    /// Obtener todos los certificados válidos\n    var validCertificates: [Certificate] {\n        return certificates.filter { $0.status == .valid }\n    }\n\n    /// Obtener los certificados que expiran pronto\n    var expiringSoonCertificates: [Certificate] {\n        return certificates.filter { $0.status == .expiringSoon }\n    }\n\n    /// Obtener los certificados caducados\n    var expiredCertificates: [Certificate] {\n        return certificates.filter { $0.status == .expired }\n    }\n\n    // MARK: - Análisis de certificados\n\n    /// Estructura de información del certificado\n    struct CertificateInfo {\n        let domains: [String]\n        let issuer: String\n        let validFrom: Date\n        let validUntil: Date\n    }\n\n    /// Analizar la información del archivo de certificado\n    func parseCertificateInfo(certPath: String) -> CertificateInfo? {\n        // Usar el comando openssl para leer la información del certificado\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: ";
", selection: $selectedAlgorithm) {\n                                ForEach(EncryptionAlgorithm.allCases, id: \\.self) { algorithm in\n                                    Text(algorithm.displayName).tag(algorithm)\n                                }\n                            }\n                            .frame(width: 200)\n                        }\n                    }\n\n                    // 操作按钮\n                    HStack(spacing: 12) {\n                        Button(action: encryptText) {\n                            Label(" = ", selection: $selectedAlgorithm) {\n                                ForEach(EncryptionAlgorithm.allCases, id: \\.self) { algorithm in\n                                    Text(algorithm.displayName).tag(algorithm)\n                                }\n                            }\n                            .frame(width: 200)\n                        }\n                    }\n\n                    // Botones de acción\n                    HStack(spacing: 12) {\n                        Button(action: encryptText) {\n                            Label(";
" }\n\n        let brewName = software.brewName ?? software.id\n\n        // 无论是否正在运行，都先停止服务\n        if software.isRunning {\n            _ = await runBrewCommand(brewPath, arguments: [" = " }\n\n        let brewName = software.brewName ?? software.id\n\n        // Detener el servicio primero, independientemente de si está en ejecución o no\n        if software.isRunning {\n            _ = await runBrewCommand(brewPath, arguments: [";
") {\n                                        NSWorkspace.shared.open(url)\n                                    }\n                                }\n                        }\n                        .padding(12)\n                        .background(Color.white.opacity(colorScheme == .dark ? 0.1 : 0.8))\n                        .glass(.panel)\n                        .cornerRadius(12)\n                        .shadow(color: Color.black.opacity(0.15), radius: 10, x: 0, y: 5)\n                        .onHover { hovering in\n                            withAnimation(.easeInOut(duration: 0.2)) {\n                                isHoveringDouyin = hovering\n                            }\n                        }\n                        .padding(.trailing, 20)\n                        .padding(.bottom, 20)\n                        .transition(.move(edge: .bottom).combined(with: .opacity))\n                    }\n                }\n                .zIndex(100)\n            }\n        }\n        // 隐藏系统默认窗口背景\n        .background(Color.clear)\n        .preferredColorScheme(appThemeModeRaw == AppThemeMode.auto.rawValue ? nil : (appThemeModeRaw == AppThemeMode.dark.rawValue ? .dark : .light))\n        .frame(minWidth: 1360, minHeight: 840)\n        .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name(" = ") {\n                                        NSWorkspace.shared.open(url)\n                                    }\n                                }\n                        }\n                        .padding(12)\n                        .background(Color.white.opacity(colorScheme == .dark ? 0.1 : 0.8))\n                        .glass(.panel)\n                        .cornerRadius(12)\n                        .shadow(color: Color.black.opacity(0.15), radius: 10, x: 0, y: 5)\n                        .onHover { hovering in\n                            withAnimation(.easeInOut(duration: 0.2)) {\n                                isHoveringDouyin = hovering\n                            }\n                        }\n                        .padding(.trailing, 20)\n                        .padding(.bottom, 20)\n                        .transition(.move(edge: .bottom).combined(with: .opacity))\n                    }\n                }\n                .zIndex(100)\n            }\n        }\n        // Ocultar el fondo predeterminado de la ventana del sistema\n        .background(Color.clear)\n        .preferredColorScheme(appThemeModeRaw == AppThemeMode.auto.rawValue ? nil : (appThemeModeRaw == AppThemeMode.dark.rawValue ? .dark : .light))\n        .frame(minWidth: 1360, minHeight: 840)\n        .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name(";
"\n            }\n        } else {\n            // 没有运行的 Web 服务器，使用设置中的默认路径\n            let settings = AppSettings.shared\n            let path = settings.defaultSiteRoot.isEmpty ? " = "\n            }\n        } else {\n            // No hay servidores web en ejecución; usar la ruta predeterminada de la configuración\n            let settings = AppSettings.shared\n            let path = settings.defaultSiteRoot.isEmpty ? ";
" : siteDirectory)\n\n        if panel.runModal() == .OK {\n            if let url = panel.url {\n                siteDirectory = url.path\n                updateDocumentRoot()\n                scanSubdirectories()\n            }\n        }\n    }\n\n    // 提交表单\n    private func submitForm() {\n        Task {\n            isSubmitting = true\n            do {\n                // 解析域名列表（按逗号分割）\n                let domainList = domains.split(separator: " = " : siteDirectory)\n\n        if panel.runModal() == .OK {\n            if let url = panel.url {\n                siteDirectory = url.path\n                updateDocumentRoot()\n                scanSubdirectories()\n            }\n        }\n    }\n\n    // Enviar formulario\n    private func submitForm() {\n        Task {\n            isSubmitting = true\n            do {\n                // Analizar la lista de dominios (separados por comas)\n                let domainList = domains.split(separator: ";
"\n\n        // 1. 检查端口冲突\n        if let port = service.port, port > 0 {\n            if isPortOccupied(port) {\n                return .portConflict\n            }\n        }\n\n        // 2. 检查配置文件是否存在\n        if !configPaths.isEmpty {\n            let anyExists = configPaths.contains {\n                FileManager.default.fileExists(atPath: $0)\n            }\n            if !anyExists {\n                return .configMissing\n            }\n        }\n\n        // 3. 检查配置文件语法\n        if name.contains(" = "\n\n        // 1. Verificar conflictos de puertos\n        if let port = service.port, port > 0 {\n            if isPortOccupied(port) {\n                return .portConflict\n            }\n        }\n\n        // 2. Verificar si existe el archivo de configuración\n        if !configPaths.isEmpty {\n            let anyExists = configPaths.contains {\n                FileManager.default.fileExists(atPath: $0)\n            }\n            if !anyExists {\n                return .configMissing\n            }\n        }\n\n        // 3. Verificar la sintaxis del archivo de configuración\n        if name.contains(";
"\n        let defaultPort = useSSL ? 443 : 80\n\n        // 从站点配置的端口列表中选择合适的端口\n        var port: Int? = nil\n        if !site.ports.isEmpty {\n            // 如果有配置端口，使用第一个匹配协议的端口\n            if useSSL {\n                // 查找 HTTPS 端口（443 或其他）\n                port = site.ports.first { $0 == 443 } ?? site.ports.first { $0 > 1024 }\n            } else {\n                // 查找 HTTP 端口（80 或其他）\n                port = site.ports.first { $0 == 80 } ?? site.ports.first\n            }\n        }\n\n        // 构建 URL\n        var urlString: String\n        if let port = port, port != defaultPort {\n            // 非标准端口，需要在 URL 中指定\n            urlString = " = "\n        let defaultPort = useSSL ? 443 : 80\n\n        // Seleccionar un puerto adecuado de la lista de puertos de la configuración del sitio\n        var port: Int? = nil\n        if !site.ports.isEmpty {\n            // Si hay puertos configurados, usar el primer puerto coincidente con el protocolo\n            if useSSL {\n                // Buscar puerto HTTPS (443 u otro)\n                port = site.ports.first { $0 == 443 } ?? site.ports.first { $0 > 1024 }\n            } else {\n                // Buscar puerto HTTP (80 u otro)\n                port = site.ports.first { $0 == 80 } ?? site.ports.first\n            }\n        }\n\n        // Construir la URL\n        var urlString: String\n        if let port = port, port != defaultPort {\n            // Puerto no estándar, debe especificarse en la URL\n            urlString = ";
"配置文件 (\\(configFileName))" = "Archivo de configuración (\\(configFileName))";
"]),\n        ]\n    }\n\n    // MARK: - Nginx 配置字典\n    static let nginx: [String: ConfigMeta] = [\n        // 基础配置\n        " = "]),\n        ]\n    }\n\n    // MARK: - Diccionario de configuración de Nginx\n    static let nginx: [String: ConfigMeta] = [\n        // Configuración básica\n        ";
"\n                RootPasswordSheet(\n                    detectedRootPassword: remotePassword,\n                    isLocal: false,\n                    host: activeConnectionInfo.host,\n                    port: activeConnectionInfo.port\n                )\n                .environmentObject(databaseManager)\n            }\n        }\n        .sheetWithLocale(item: $editingDatabase) { database in\n            EditDatabaseSheet(database: database)\n                .environmentObject(databaseManager)\n        }\n        .sheetWithLocale(isPresented: $showRemoteAddDatabaseSheet) {\n            RemoteAddDatabaseSheet(connector: remoteConnector)\n        }\n        .sheetWithLocale(isPresented: $showMySQLConfigSheet) {\n            // 使用 ServiceConfigSheet 显示 MySQL 配置\n            ServiceConfigSheet(service: ServiceStatus(\n                name: " = "\n                RootPasswordSheet(\n                    detectedRootPassword: remotePassword,\n                    isLocal: false,\n                    host: activeConnectionInfo.host,\n                    port: activeConnectionInfo.port\n                )\n                .environmentObject(databaseManager)\n            }\n        }\n        .sheetWithLocale(item: $editingDatabase) { database in\n            EditDatabaseSheet(database: database)\n                .environmentObject(databaseManager)\n        }\n        .sheetWithLocale(isPresented: $showRemoteAddDatabaseSheet) {\n            RemoteAddDatabaseSheet(connector: remoteConnector)\n        }\n        .sheetWithLocale(isPresented: $showMySQLConfigSheet) {\n            // Usar ServiceConfigSheet para mostrar la configuración de MySQL\n            ServiceConfigSheet(service: ServiceStatus(\n                name: ";
"\n\n                        // 检查是否为目录\n                        var isDirectory: ObjCBool = false\n                        guard fileManager.fileExists(atPath: itemPath, isDirectory: &isDirectory),\n                              isDirectory.boolValue else {\n                            continue\n                        }\n\n                        // 跳过隐藏目录\n                        if item.hasPrefix(" = "\n\n                        // Verificar si es un directorio\n                        var isDirectory: ObjCBool = false\n                        guard fileManager.fileExists(atPath: itemPath, isDirectory: &isDirectory),\n                              isDirectory.boolValue else {\n                            continue\n                        }\n\n                        // Saltar directorios ocultos\n                        if item.hasPrefix(";
") {\n                currentSection = String(trimmedLine.dropFirst().dropLast())\n                continue\n            }\n\n            // 解析配置行\n            if let parsed = parseConfigLine(trimmedLine, lineNumber: index + 1, section: currentSection, commentPrefix: fileType.commentPrefix) {\n                results.append(parsed)\n            }\n        }\n\n        return results\n    }\n\n    /// 解析单行配置\n    /// - Parameters:\n    ///   - line: 配置行内容\n    ///   - lineNumber: 行号\n    ///   - section: 所属节\n    ///   - commentPrefix: 注释符\n    /// - Returns: 解析结果，如果不是有效配置行则返回nil\n    private static func parseConfigLine(_ line: String, lineNumber: Int, section: String?, commentPrefix: String) -> ParsedConfigLine? {\n        var workingLine = line\n        var isEnabled = true\n\n        // 检查是否是注释行\n        if workingLine.hasPrefix(commentPrefix) {\n            isEnabled = false\n            // 移除注释符和后续空格\n            workingLine = String(workingLine.dropFirst(commentPrefix.count))\n                .trimmingCharacters(in: .whitespaces)\n        }\n\n        // 查找等号或冒号分隔符\n        guard let separatorRange = workingLine.range(of: " = ") {\n                currentSection = String(trimmedLine.dropFirst().dropLast())\n                continue\n            }\n\n            // Analizar línea de configuración\n            if let parsed = parseConfigLine(trimmedLine, lineNumber: index + 1, section: currentSection, commentPrefix: fileType.commentPrefix) {\n                results.append(parsed)\n            }\n        }\n\n        return results\n    }\n\n    /// Analizar una sola línea de configuración\n    /// - Parámetros:\n    ///   - line: Contenido de la línea de configuración\n    ///   - lineNumber: Número de línea\n    ///   - section: Sección a la que pertenece\n    ///   - commentPrefix: Prefijo de comentario\n    /// - Devuelve: Resultado del análisis; nil si no es una línea de configuración válida\n    private static func parseConfigLine(_ line: String, lineNumber: Int, section: String?, commentPrefix: String) -> ParsedConfigLine? {\n        var workingLine = line\n        var isEnabled = true\n\n        // Verificar si es una línea de comentario\n        if workingLine.hasPrefix(commentPrefix) {\n            isEnabled = false\n            // Eliminar el prefijo de comentario y los espacios posteriores\n            workingLine = String(workingLine.dropFirst(commentPrefix.count))\n                .trimmingCharacters(in: .whitespaces)\n        }\n\n        // Buscar el separador igual (=) o dos puntos (:)\n        guard let separatorRange = workingLine.range(of: ";
"创建目录失败: \\(error.localizedDescription)" = "Error al crear el directorio: \\(error.localizedDescription)";
"\n            // 创建 MySQL 配置文件模板\n            try await createPureFTPMySQLConfig(configDir: configDir)\n        } else {\n            config += " = "\n            // Crear plantilla de archivo de configuración de MySQL para Pure-FTP\n            try await createPureFTPMySQLConfig(configDir: configDir)\n        } else {\n            config += ";
"删除配置文件失败: \\(config.filePath), 错误: \\(error.localizedDescription)" = "Error al eliminar el archivo de configuración: \\(config.filePath), error: \\(error.localizedDescription)";
")\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n            }\n            .frame(minWidth: 100, alignment: .leading)\n\n            // 备注\n            Text(isSystemDatabase ? LocalizedStringKey(" = ")\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n            }\n            .frame(minWidth: 100, alignment: .leading)\n\n            // 备注\n            Text(isSystemDatabase ? LocalizedStringKey(";
"\n                # HTTP 服务器块\n                server {\n                    listen 80;\n                    server_name \\(serverNames);\n                    root \\(publicRoot);\n                    index index.php index.html index.htm;\n\n                    # 访问日志\n                    access_log \\(accessLog);\n                    error_log \\(errorLog);\n\n                    location / {\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\\\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:\\(phpPort);\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # 禁止访问隐藏文件\n                    location ~ /\\\\. {\n                        deny all;\n                    }\n                }\n\n                # HTTPS 服务器块\n                server {\n                    listen 443 ssl;\n                    server_name \\(serverNames);\n                    root \\(publicRoot);\n                    index index.php index.html index.htm;\n\n                    # SSL 证书配置\n                    ssl_certificate \\(certPath);\n                    ssl_certificate_key \\(keyPath);\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # 访问日志\n                    access_log \\(accessLog);\n                    error_log \\(errorLog);\n\n                    location / {\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\\\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:\\(phpPort);\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # 禁止访问隐藏文件\n                    location ~ /\\\\. {\n                        deny all;\n                    }\n                }\n                " = "\n                # HTTP 服务器块\n                server {\n                    listen 80;\n                    server_name \\(serverNames);\n                    root \\(publicRoot);\n                    index index.php index.html index.htm;\n\n                    # 访问日志\n                    access_log \\(accessLog);\n                    error_log \\(errorLog);\n\n                    location / {\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\\\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:\\(phpPort);\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # 禁止访问隐藏文件\n                    location ~ /\\\\. {\n                        deny all;\n                    }\n                }\n\n                # HTTPS 服务器块\n                server {\n                    listen 443 ssl;\n                    server_name \\(serverNames);\n                    root \\(publicRoot);\n                    index index.php index.html index.htm;\n\n                    # SSL 证书配置\n                    ssl_certificate \\(certPath);\n                    ssl_certificate_key \\(keyPath);\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # 访问日志\n                    access_log \\(accessLog);\n                    error_log \\(errorLog);\n\n                    location / {\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\\\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:\\(phpPort);\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # 禁止访问隐藏文件\n                    location ~ /\\\\. {\n                        deny all;\n                    }\n                }\n                ";
"\n        }\n\n        // 报告当前扫描的目录\n        await MainActor.run {\n            onProgress?(nginxVhostPath)\n        }\n\n        return await scanConfigDirectory(path: nginxVhostPath, webServer: .nginx)\n    }\n\n    /// 扫描 Apache 配置文件\n    private func scanApacheConfigs(onProgress: ((String) -> Void)? = nil) async -> [FoundVhostConfig] {\n        // 获取 Apache 配置目录路径\n        let workspaceManager = WorkspaceManager.shared\n        let apacheVhostPath: String\n\n        if let config = workspaceManager.config, config.isInitialized {\n            apacheVhostPath = config.apacheVhostsPath\n        } else {\n            let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n            apacheVhostPath = " = "\n        }\n\n        // 报告当前扫描的目录\n        await MainActor.run {\n            onProgress?(nginxVhostPath)\n        }\n\n        return await scanConfigDirectory(path: nginxVhostPath, webServer: .nginx)\n    }\n\n    /// 扫描 Apache 配置文件\n    private func scanApacheConfigs(onProgress: ((String) -> Void)? = nil) async -> [FoundVhostConfig] {\n        // 获取 Apache 配置目录路径\n        let workspaceManager = WorkspaceManager.shared\n        let apacheVhostPath: String\n\n        if let config = workspaceManager.config, config.isInitialized {\n            apacheVhostPath = config.apacheVhostsPath\n        } else {\n            let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n            apacheVhostPath = ";
"\n        let config = generateNginxConfig(port: listenPorts)\n\n        // 写入配置文件\n        let configPath = " = "\n        let config = generateNginxConfig(port: listenPorts)\n\n        // 写入配置文件\n        let configPath = ";
")\n                .font(.caption)\n                .foregroundColor(.secondary)\n                .padding(.top, 8)\n\n            // 操作按钮\n            HStack(spacing: 16) {\n                // 复制到 Navicat\n                Button(action: copyToNavicat) {\n                    VStack(spacing: 8) {\n                        Image(systemName: " = ")\n                .font(.caption)\n                .foregroundColor(.secondary)\n                .padding(.top, 8)\n\n            // 操作按钮\n            HStack(spacing: 16) {\n                // 复制到 Navicat\n                Button(action: copyToNavicat) {\n                    VStack(spacing: 8) {\n                        Image(systemName: ";
")\n\n            // 转换标志\n            var nginxFlags = " = ")\n\n            // 转换标志\n            var nginxFlags = ";
"]\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = Pipe()\n        do {\n            try process.run()\n            process.waitUntilExit()\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            guard let output = String(data: data, encoding: .utf8),\n                  !output.isEmpty else { return false }\n            let lines = output.components(separatedBy: .newlines).filter { !$0.isEmpty }\n            return lines.count > 1\n        } catch {\n            return false\n        }\n    }\n\n    /// 检查配置文件语法\n    private nonisolated static func checkConfigSyntax(bin: String, args: [String]) -> Bool {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: bin)\n        process.arguments = args\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n        do {\n            try process.run()\n            process.waitUntilExit()\n            return process.terminationStatus == 0\n        } catch {\n            return true\n        }\n    }\n\n    /// 获取 brew service 状态\n    private nonisolated static func getBrewServiceStatusSync(_ brewName: String, brewPath: String) -> String {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [" = "]\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = Pipe()\n        do {\n            try process.run()\n            process.waitUntilExit()\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            guard let output = String(data: data, encoding: .utf8),\n                  !output.isEmpty else { return false }\n            let lines = output.components(separatedBy: .newlines).filter { !$0.isEmpty }\n            return lines.count > 1\n        } catch {\n            return false\n        }\n    }\n\n    /// 检查配置文件语法\n    private nonisolated static func checkConfigSyntax(bin: String, args: [String]) -> Bool {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: bin)\n        process.arguments = args\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n        do {\n            try process.run()\n            process.waitUntilExit()\n            return process.terminationStatus == 0\n        } catch {\n            return true\n        }\n    }\n\n    /// 获取 brew service 状态\n    private nonisolated static func getBrewServiceStatusSync(_ brewName: String, brewPath: String) -> String {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [";
", brewName])\n        }\n        return false\n    }\n\n    /// 安装 PHP 并配置指定端口\n    func installPHPWithPort(_ software: SoftwareVersion, port: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(" = ", brewName])\n        }\n        return false\n    }\n\n    /// 安装 PHP 并配置指定端口\n    func installPHPWithPort(_ software: SoftwareVersion, port: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(";
") 配置缺失: \\(path)" = ") 配置缺失: \\(path)";
"📄 找到 \\(files.count) 个文件" = "📄 找到 \\(files.count) 个文件";
"）\n    ///   - defaultPHPVersion: 默认 PHP 版本\n    ///   - defaultWebServer: 默认 Web 服务器\n    /// - Returns: 候选站点列表\n    func scanDirectoriesForBatchImport(\n        parentDirectory: String,\n        documentRootSubPath: String?,\n        defaultPHPVersion: String,\n        defaultWebServer: WebServerType\n    ) async -> [CandidateSite] {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                var candidates: [CandidateSite] = []\n                let fileManager = FileManager.default\n\n                guard fileManager.fileExists(atPath: parentDirectory) else {\n                    continuation.resume(returning: candidates)\n                    return\n                }\n\n                do {\n                    // 获取一层子目录\n                    let contents = try fileManager.contentsOfDirectory(atPath: parentDirectory)\n\n                    for item in contents {\n                        let itemPath = " = "）\n    ///   - defaultPHPVersion: 默认 PHP 版本\n    ///   - defaultWebServer: 默认 Web 服务器\n    /// - Returns: 候选站点列表\n    func scanDirectoriesForBatchImport(\n        parentDirectory: String,\n        documentRootSubPath: String?,\n        defaultPHPVersion: String,\n        defaultWebServer: WebServerType\n    ) async -> [CandidateSite] {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                var candidates: [CandidateSite] = []\n                let fileManager = FileManager.default\n\n                guard fileManager.fileExists(atPath: parentDirectory) else {\n                    continuation.resume(returning: candidates)\n                    return\n                }\n\n                do {\n                    // 获取一层子目录\n                    let contents = try fileManager.contentsOfDirectory(atPath: parentDirectory)\n\n                    for item in contents {\n                        let itemPath = ";
"\\(service.name) 停止失败,请检查日志" = "\\(service.name) 停止失败,请检查日志";
"\n        }\n        \n        // 添加管理区域\n        newContent += " = "\n        }\n        \n        // 添加管理区域\n        newContent += ";
", Color(red: 0.86, green: 0.24, blue: 0.24)) // Redis红色\n        } else if name.contains(" = ", Color(red: 0.86, green: 0.24, blue: 0.24)) // Redis红色\n        } else if name.contains(";
") {\n                isProxy = true\n                // 提取代理目标端口，如: proxy_pass http://127.0.0.1:8080;\n                if let portRange = trimmed.range(of: " = ") {\n                isProxy = true\n                // 提取代理目标端口，如: proxy_pass http://127.0.0.1:8080;\n                if let portRange = trimmed.range(of: ";
" 的 token，兼容 IPv4/IPv6/星号监听格式\n                guard let endpointToken = parts.reversed().first(where: { $0.contains(" = " 的 token，兼容 IPv4/IPv6/星号监听格式\n                guard let endpointToken = parts.reversed().first(where: { $0.contains(";
")\n                }\n                .buttonStyle(.borderedProminent)\n                .tint(.orange)\n\n                // phpMyAdmin 下拉菜单\n                Menu {\n                    Button(" = ")\n                }\n                .buttonStyle(.borderedProminent)\n                .tint(.orange)\n\n                // phpMyAdmin 下拉菜单\n                Menu {\n                    Button(";
"]),\n        // MySQL 8.0/8.4 认证 (MySQL 9.x 已废弃)\n        " = "]),\n        // MySQL 8.0/8.4 认证 (MySQL 9.x 已废弃)\n        ";
" }.count\n        } catch {\n            return 0\n        }\n    }\n\n    /// 获取数据库的备份文件列表\n    func getBackupFiles(for databaseName: String) -> [URL] {\n        let backupDir = getBackupDirectory()\n        let pattern = " = " }.count\n        } catch {\n            return 0\n        }\n    }\n\n    /// 获取数据库的备份文件列表\n    func getBackupFiles(for databaseName: String) -> [URL] {\n        let backupDir = getBackupDirectory()\n        let pattern = ";
"✅ 已备份旧 Nginx 配置: \\(configFilePath)" = "✅ 已备份旧 Nginx 配置: \\(configFilePath)";
") {\n                let parts = trimmed.components(separatedBy: .whitespaces)\n                if parts.count >= 2 {\n                    domains.append(parts[1])\n                }\n            }\n\n            // 解析 ServerAlias\n            if trimmed.hasPrefix(" = ") {\n                let parts = trimmed.components(separatedBy: .whitespaces)\n                if parts.count >= 2 {\n                    domains.append(parts[1])\n                }\n            }\n\n            // Analizar ServerAlias\n            if trimmed.hasPrefix(";
")\n        hasChanges = true\n    }\n\n    // 从配置行中提取值\n    private func extractValue(for key: String, from lines: [String]) -> String? {\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed.hasPrefix(" = ")\n        hasChanges = true\n    }\n\n    // Extraer valor de líneas de configuración\n    private func extractValue(for key: String, from lines: [String]) -> String? {\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed.hasPrefix(";
")\n            }\n            .buttonStyle(.bordered)\n\n            // 设置按钮\n            Button(action: {\n                openSettings()\n            }) {\n                Image(systemName: " = ")\n            }\n            .buttonStyle(.bordered)\n\n            // Botón de configuración\n            Button(action: {\n                openSettings()\n            }) {\n                Image(systemName: ";
") {\n            return readWildcardLogFile(pattern: path)\n        }\n\n        // 检查文件是否存在\n        guard FileManager.default.fileExists(atPath: path) else {\n            return " = ") {\n            return readWildcardLogFile(pattern: path)\n        }\n\n        // Verificar si el archivo existe\n        guard FileManager.default.fileExists(atPath: path) else {\n            return ";
" ? Color.blue.opacity(0.1) : Color.blue.opacity(AppTokens.Opacity.light))\n            .cornerRadius(8)\n            .frame(maxWidth: 600)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .padding(40)\n    }\n\n    // MARK: - 分析中视图\n    private var analyzingView: some View {\n        VStack(spacing: 16) {\n            ProgressView()\n                .scaleEffect(1.5)\n            Text(LocalizedStringKey(" = " ? Color.blue.opacity(0.1) : Color.blue.opacity(AppTokens.Opacity.light))\n            .cornerRadius(8)\n            .frame(maxWidth: 600)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .padding(40)\n    }\n\n    // MARK: - Vista de análisis\n    private var analyzingView: some View {\n        VStack(spacing: 16) {\n            ProgressView()\n                .scaleEffect(1.5)\n            Text(LocalizedStringKey(";
")\n        }\n\n        // 4. 发起HTTP请求\n        do {\n            let (data, response) = try await URLSession.shared.data(from: url)\n\n            // 5. 检查响应\n            guard let httpResponse = response as? HTTPURLResponse else {\n                try? FileManager.default.removeItem(atPath: verificationFilePath)\n                return .failed(error: " = ")\n        }\n\n        // 4. Realizar solicitud HTTP\n        do {\n            let (data, response) = try await URLSession.shared.data(from: url)\n\n            // 5. Verificar respuesta\n            guard let httpResponse = response as? HTTPURLResponse else {\n                try? FileManager.default.removeItem(atPath: verificationFilePath)\n                return .failed(error: ";
"\n            }\n        } else {\n            // 仅 HTTP 代理\n            configContent = " = "\n            }\n        } else {\n            // Solo proxy HTTP\n            configContent = ";
").map { String($0) }\n                guard parts.count >= 2 else { return nil }\n                return MySQLUserInfo(user: parts[0], host: parts[1])\n            }\n        } else {\n            errorMessage = result.error\n        }\n        isLoading = false\n    }\n\n    /// 创建用户\n    func createUser(username: String, host: String, password: String) async -> Bool {\n        let sql = " = ").map { String($0) }\n                guard parts.count >= 2 else { return nil }\n                return MySQLUserInfo(user: parts[0], host: parts[1])\n            }\n        } else {\n            errorMessage = result.error\n        }\n        isLoading = false\n    }\n\n    /// Crear usuario\n    func createUser(username: String, host: String, password: String) async -> Bool {\n        let sql = ";
")\n                                    .font(.system(size: 9, design: .monospaced))\n                                    .foregroundColor(.secondary)\n                                    .lineLimit(1)\n                            }\n                        }\n\n                        Spacer()\n\n                        // 修复按钮\n                        if let onRepair = onRepair {\n                            Button(action: onRepair) {\n                                HStack(spacing: 4) {\n                                    Image(systemName: " = ")\n                                    .font(.system(size: 9, design: .monospaced))\n                                    .foregroundColor(.secondary)\n                                    .lineLimit(1)\n                            }\n                        }\n\n                        Spacer()\n\n                        // Botón de reparación\n                        if let onRepair = onRepair {\n                            Button(action: onRepair) {\n                                HStack(spacing: 4) {\n                                    Image(systemName: ";
")\n                        .font(.system(size: 12))\n                        .foregroundColor(.secondary)\n                }\n            }\n\n            Spacer()\n\n            // 查看日志按钮\n            Button(action: action) {\n                HStack(spacing: 6) {\n                    Image(systemName: " = ")\n                        .font(.system(size: 12))\n                        .foregroundColor(.secondary)\n                }\n            }\n\n            Spacer()\n\n            // Botón para ver registros\n            Button(action: action) {\n                HStack(spacing: 6) {\n                    Image(systemName: ";
",\n            validFrom: certInfo?.validFrom ?? Date(),\n            validUntil: certInfo?.validUntil ?? Calendar.current.date(byAdding: .day, value: 365, to: Date()) ?? Date(),\n            siteId: site.id\n        )\n\n        CertificateManager.shared.addCertificate(certificate)\n\n        sites[index].certificateId = certificate.id\n        sites[index].sslStatus = .enabled\n        try await forceSaveSites()\n\n        if sites[index].isEnabled || sites[index].status == .running {\n            try await applySiteConfiguration(for: sites[index], reloadRunningServices: true)\n        }\n    }\n\n    // 导出站点配置\n    func exportSiteConfig(_ site: Site) -> String {\n        // 生成站点配置JSON\n        let config: [String: Any] = [\n            " = ",\n            validFrom: certInfo?.validFrom ?? Date(),\n            validUntil: certInfo?.validUntil ?? Calendar.current.date(byAdding: .day, value: 365, to: Date()) ?? Date(),\n            siteId: site.id\n        )\n\n        CertificateManager.shared.addCertificate(certificate)\n\n        sites[index].certificateId = certificate.id\n        sites[index].sslStatus = .enabled\n        try await forceSaveSites()\n\n        if sites[index].isEnabled || sites[index].status == .running {\n            try await applySiteConfiguration(for: sites[index], reloadRunningServices: true)\n        }\n    }\n\n    // Exportar configuración del sitio\n    func exportSiteConfig(_ site: Site) -> String {\n        // Generar JSON de configuración del sitio\n        let config: [String: Any] = [\n            ";
"))\n                    .font(.system(size: 12, design: .monospaced))\n                    .padding(12)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n                    .background(Color.gray.opacity(AppTokens.Opacity.light))\n                    .cornerRadius(8)\n            }\n            .padding(20)\n            .background(Color.blue.opacity(AppTokens.Opacity.ultraLight))\n            .cornerRadius(12)\n\n            // 根据安装状态显示不同的按钮\n            if case .installing = installer.installationStatus {\n                VStack(spacing: 12) {\n                    ProgressView()\n                        .scaleEffect(1.2)\n                    Text(installer.installationProgress)\n                        .font(.system(size: 13))\n                        .foregroundColor(.secondary)\n                }\n            } else if case .completed = installer.installationStatus {\n                VStack(spacing: 12) {\n                    Image(systemName: " = "))\n                    .font(.system(size: 12, design: .monospaced))\n                    .padding(12)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n                    .background(Color.gray.opacity(AppTokens.Opacity.light))\n                    .cornerRadius(8)\n            }\n            .padding(20)\n            .background(Color.blue.opacity(AppTokens.Opacity.ultraLight))\n            .cornerRadius(12)\n\n            // Mostrar botones diferentes según estado de instalación\n            if case .installing = installer.installationStatus {\n                VStack(spacing: 12) {\n                    ProgressView()\n                        .scaleEffect(1.2)\n                    Text(installer.installationProgress)\n                        .font(.system(size: 13))\n                        .foregroundColor(.secondary)\n                }\n            } else if case .completed = installer.installationStatus {\n                VStack(spacing: 12) {\n                    Image(systemName: ";
"))\n\n        menu.addItem(NSMenuItem.separator())\n\n        // 服务状态\n        menu.addItem(NSMenuItem(title: " = "))\n\n        menu.addItem(NSMenuItem.separator())\n\n        // Estado del servicio\n        menu.addItem(NSMenuItem(title: ";
")\n                                    .font(.system(size: 16, weight: .semibold))\n\n                                // 复制端口按钮\n                                Button(action: {\n                                    copyPortToClipboard()\n                                }) {\n                                    Image(systemName: " = ")\n                                    .font(.system(size: 16, weight: .semibold))\n\n                                // Botón para copiar puerto\n                                Button(action: {\n                                    copyPortToClipboard()\n                                }) {\n                                    Image(systemName: ";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 表单内容\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // 当前密码区域（本地和远程显示不同）\n                if isLocal {\n                    // 本地数据库：自动填充，只读显示，有忘记密码选项\n                    localCurrentPasswordField\n                } else {\n                    // 远程数据库：必须手动输入当前密码\n                    remoteCurrentPasswordField\n                }\n\n                // 新密码（带显示/隐藏按钮）\n                FormField(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // Contenido del formulario\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // Campo de contraseña actual (diferente para local y remoto)\n                if isLocal {\n                    // Base de datos local: relleno automático, solo lectura, con opción «Olvidé mi contraseña»\n                    localCurrentPasswordField\n                } else {\n                    // Base de datos remota: se debe ingresar manualmente la contraseña actual\n                    remoteCurrentPasswordField\n                }\n\n                // Nueva contraseña (con botón para mostrar/ocultar)\n                FormField(";
")\n            }\n            databases[index].password = password\n        }\n\n        if let note = note {\n            databases[index].note = note\n        }\n\n        saveDatabases()\n    }\n    \n    // 刷新数据库列表（从服务器重新加载）\n    func refreshDatabases() async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        // 获取本地 MySQL 数据库列表\n        let localDbs = await fetchLocalMySQLDatabases()\n        localMySQLDatabases = localDbs\n\n        saveDatabases()\n    }\n\n    // 从本地 MySQL 获取真实数据库列表\n    func fetchLocalMySQLDatabases() async -> [String] {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: [])\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqlPath)\n                process.arguments = [\n                    " = ")\n            }\n            databases[index].password = password\n        }\n\n        if let note = note {\n            databases[index].note = note\n        }\n\n        saveDatabases()\n    }\n    \n    // Actualizar lista de bases de datos (recargar desde el servidor)\n    func refreshDatabases() async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        // Obtener lista de bases de datos MySQL locales\n        let localDbs = await fetchLocalMySQLDatabases()\n        localMySQLDatabases = localDbs\n\n        saveDatabases()\n    }\n\n    // Obtener lista real de bases de datos MySQL locales\n    func fetchLocalMySQLDatabases() async -> [String] {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: [])\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqlPath)\n                process.arguments = [\n                    ";
"\n            }\n        }\n    }\n\n    // 获取已安装的PHP版本列表（包含运行状态）\n    private var installedPHPVersions: [PHPVersionInfo] {\n        return softwareManager.phpVersions\n            .filter { $0.installed }\n            .map { PHPVersionInfo(id: $0.id, version: $0.version, isRunning: $0.isRunning) }\n            .sorted { $0.version > $1.version }  // 降序排列，新版本在前\n    }\n\n    // 检查指定PHP版本是否正在运行\n    private func isPHPVersionRunning(_ version: String) -> Bool {\n        return softwareManager.phpVersions\n            .first { $0.version == version && $0.installed }?\n            .isRunning ?? false\n    }\n\n    // 获取已安装的Web服务器列表（包含运行状态）\n    private var installedWebServers: [WebServerType] {\n        var servers: [WebServerType] = []\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if name == " = "\n            }\n        }\n    }\n\n    // Obtener lista de versiones PHP instaladas (incluyendo estado de ejecución)\n    private var installedPHPVersions: [PHPVersionInfo] {\n        return softwareManager.phpVersions\n            .filter { $0.installed }\n            .map { PHPVersionInfo(id: $0.id, version: $0.version, isRunning: $0.isRunning) }\n            .sorted { $0.version > $1.version }  // Orden descendente: versiones más nuevas primero\n    }\n\n    // Verificar si una versión específica de PHP está en ejecución\n    private func isPHPVersionRunning(_ version: String) -> Bool {\n        return softwareManager.phpVersions\n            .first { $0.version == version && $0.installed }?\n            .isRunning ?? false\n    }\n\n    // Obtener lista de servidores web instalados (incluyendo estado de ejecución)\n    private var installedWebServers: [WebServerType] {\n        var servers: [WebServerType] = []\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if name == ";
"正在停止 \\(software.name)..." = "Deteniendo \\(software.name)...";
")\n                    .font(.system(size: 24))\n                    .foregroundColor(site.webServer == .nginx ? .green : .blue)\n            }\n\n            // 网站信息\n            VStack(alignment: .leading, spacing: 4) {\n                Text(site.primaryDomain)\n                    .font(.system(size: 15, weight: .semibold))\n\n                HStack(spacing: 12) {\n                    Label(site.webServer.rawValue, systemImage: " = ")\n                    .font(.system(size: 24))\n                    .foregroundColor(site.webServer == .nginx ? .green : .blue)\n            }\n\n            // Información del sitio web\n            VStack(alignment: .leading, spacing: 4) {\n                Text(site.primaryDomain)\n                    .font(.system(size: 15, weight: .semibold))\n\n                HStack(spacing: 12) {\n                    Label(site.webServer.rawValue, systemImage: ";
" {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(width: 900, height: 600)\n    }\n    \n    // V1 旧版布局\n    private var v1Body: some View {\n        HStack(spacing: 0) {\n            // 左侧菜单\n            v1LeftMenu\n\n            Divider()\n\n            // 右侧内容\n            rightContent\n        }\n        .background(Color(NSColor.windowBackgroundColor))\n    }\n    \n    // V2 玻璃拟物化布局\n    private var v2Body: some View {\n        HStack(spacing: 0) {\n            // 左侧菜单\n            v2LeftMenu\n                .frame(width: 200)\n\n            // 右侧内容区\n            rightContent\n                .background(Color.white.opacity(0.1))\n        }\n        .glass(.panel)\n        .overlay(\n            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                .stroke(Color.white.opacity(0.2), lineWidth: 1)\n        )\n    }\n\n    // V1 左侧菜单\n    private var v1LeftMenu: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            Text(" = " {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(width: 900, height: 600)\n    }\n    \n    // Diseño antiguo V1\n    private var v1Body: some View {\n        HStack(spacing: 0) {\n            // Menú izquierdo\n            v1LeftMenu\n\n            Divider()\n\n            // Contenido derecho\n            rightContent\n        }\n        .background(Color(NSColor.windowBackgroundColor))\n    }\n    \n    // Diseño acristalado V2\n    private var v2Body: some View {\n        HStack(spacing: 0) {\n            // Menú izquierdo\n            v2LeftMenu\n                .frame(width: 200)\n\n            // Área de contenido derecho\n            rightContent\n                .background(Color.white.opacity(0.1))\n        }\n        .glass(.panel)\n        .overlay(\n            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                .stroke(Color.white.opacity(0.2), lineWidth: 1)\n        )\n    }\n\n    // Menú izquierdo V1\n    private var v1LeftMenu: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            Text(";
", parentPath)\n            }\n        }\n    }\n    \n    // 创建目录（如果不存在）\n    func createDirectoryIfNeeded(_ path: String) throws {\n        let fileManager = FileManager.default\n        var isDirectory: ObjCBool = false\n\n        if !fileManager.fileExists(atPath: path, isDirectory: &isDirectory) {\n            try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)\n        } else if !isDirectory.boolValue {\n            throw NSError(domain: " = ", parentPath)\n            }\n        }\n    }\n    \n    // 创建目录（如果不存在）\n    func createDirectoryIfNeeded(_ path: String) throws {\n        let fileManager = FileManager.default\n        var isDirectory: ObjCBool = false\n\n        if !fileManager.fileExists(atPath: path, isDirectory: &isDirectory) {\n            try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)\n        } else if !isDirectory.boolValue {\n            throw NSError(domain: ";
"))\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqlPath)\n\n                // 构建参数\n                var arguments = [\n                    " = "))\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqlPath)\n\n                // 构建参数\n                var arguments = [\n                    ";
"抖音看配置教程" = "抖音看配置教程";
"\n\n        // 如果启用了SSL且使用端到端SSL模式\n        if site.sslEnabled && !certPath.isEmpty && site.sslMode == .endToEnd {\n            if site.forceHttps {\n                // 强制HTTPS：HTTP自动跳转到HTTPS\n                configContent = " = "\n\n        // 如果启用了SSL且使用端到端SSL模式\n        if site.sslEnabled && !certPath.isEmpty && site.sslMode == .endToEnd {\n            if site.forceHttps {\n                // 强制HTTPS：HTTP自动跳转到HTTPS\n                configContent = ";
"\n    }\n\n    // MARK: - PHP 路径\n\n    /// PHP 配置目录 (按版本)\n    func phpConfPath(version: String) -> String {\n        " = "\n    }\n\n    // MARK: - PHP 路径\n\n    /// PHP 配置目录 (按版本)\n    func phpConfPath(version: String) -> String {\n        ";
")\n        }\n\n        // 刷新数据库列表\n        await fetchDatabases()\n\n        return (true, nil)\n    }\n\n    /// 删除数据库\n    func dropDatabase(name: String) async -> Bool {\n        let sql = " = ")\n        }\n\n        // 刷新数据库列表\n        await fetchDatabases()\n\n        return (true, nil)\n    }\n\n    /// 删除数据库\n    func dropDatabase(name: String) async -> Bool {\n        let sql = ";
") else {\n            return nil\n        }\n\n        let key = String(workingLine[..<separatorRange.lowerBound])\n            .trimmingCharacters(in: .whitespaces)\n        let value = String(workingLine[separatorRange.upperBound...])\n            .trimmingCharacters(in: .whitespaces)\n\n        // 跳过空键名\n        if key.isEmpty {\n            return nil\n        }\n\n        return ParsedConfigLine(\n            lineNumber: lineNumber,\n            key: key,\n            value: value,\n            isEnabled: isEnabled,\n            originalLine: line,\n            section: section\n        )\n    }\n\n    // MARK: - 配置生成\n\n    /// 生成配置文件内容\n    /// - Parameters:\n    ///   - items: 配置项数组\n    ///   - fileType: 配置文件类型\n    ///   - sections: 配置节分组（可选）\n    /// - Returns: 配置文件内容\n    static func generateConfigContent(items: [ConfigItem], type fileType: ConfigFileType, sections: [String: [ConfigItem]]? = nil) -> String {\n        var lines: [String] = []\n        let commentPrefix = fileType.commentPrefix\n\n        if let sections = sections {\n            // 按节分组生成\n            for (sectionName, sectionItems) in sections.sorted(by: { $0.key < $1.key }) {\n                // 添加节标题\n                if !sectionName.isEmpty {\n                    lines.append(" = ") else {\n            return nil\n        }\n\n        let key = String(workingLine[..<separatorRange.lowerBound])\n            .trimmingCharacters(in: .whitespaces)\n        let value = String(workingLine[separatorRange.upperBound...])\n            .trimmingCharacters(in: .whitespaces)\n\n        // 跳过空键名\n        if key.isEmpty {\n            return nil\n        }\n\n        return ParsedConfigLine(\n            lineNumber: lineNumber,\n            key: key,\n            value: value,\n            isEnabled: isEnabled,\n            originalLine: line,\n            section: section\n        )\n    }\n\n    // MARK: - 配置生成\n\n    /// 生成配置文件内容\n    /// - Parameters:\n    ///   - items: 配置项数组\n    ///   - fileType: 配置文件类型\n    ///   - sections: 配置节分组（可选）\n    /// - Returns: 配置文件内容\n    static func generateConfigContent(items: [ConfigItem], type fileType: ConfigFileType, sections: [String: [ConfigItem]]? = nil) -> String {\n        var lines: [String] = []\n        let commentPrefix = fileType.commentPrefix\n\n        if let sections = sections {\n            // 按节分组生成\n            for (sectionName, sectionItems) in sections.sorted(by: { $0.key < $1.key }) {\n                // 添加节标题\n                if !sectionName.isEmpty {\n                    lines.append(";
")\n                }\n            }\n        }\n    }\n\n    // 保存权限\n    private func savePermissions() {\n        Task {\n            isSaving = true\n            let host = selectedAccessType == .customIP ? customIP : selectedAccessType.hostValue\n            let success = await applyHostPermission(host: host)\n            await MainActor.run {\n                isSaving = false\n                if success {\n                    dismiss()\n                } else {\n                    let alert = NSAlert()\n                    alert.messageText = " = ")\n                }\n            }\n        }\n    }\n\n    // 保存权限\n    private func savePermissions() {\n        Task {\n            isSaving = true\n            let host = selectedAccessType == .customIP ? customIP : selectedAccessType.hostValue\n            let success = await applyHostPermission(host: host)\n            await MainActor.run {\n                isSaving = false\n                if success {\n                    dismiss()\n                } else {\n                    let alert = NSAlert()\n                    alert.messageText = ";
")\n            }\n        }\n\n        return result\n    }\n\n    // 仅保存配置文件\n    private func saveConfigOnly() {\n        let path = currentConfigPath.isEmpty ? getConfigPathWithWorkspace() : currentConfigPath\n        guard !path.isEmpty else { return }\n\n        // 如果是表单模式，先同步表单值到配置内容\n        syncFormToContent()\n\n        statusMessages.removeAll()\n        isOperating = true\n        addStatusMessage(" = ")\n            }\n        }\n\n        return result\n    }\n\n    // 仅保存配置文件\n    private func saveConfigOnly() {\n        let path = currentConfigPath.isEmpty ? getConfigPathWithWorkspace() : currentConfigPath\n        guard !path.isEmpty else { return }\n\n        // 如果是表单模式，先同步表单值到配置内容\n        syncFormToContent()\n\n        statusMessages.removeAll()\n        isOperating = true\n        addStatusMessage(";
" + errStr).trimmingCharacters(in: .whitespacesAndNewlines)\n                    continuation.resume(returning: (process.terminationStatus == 0, combined))\n                } catch {\n                    continuation.resume(returning: (false, error.localizedDescription))\n                }\n            }\n        }\n    }\n\n    // 带进度的命令执行\n    private func runBrewCommandWithProgress(_ brewPath: String, arguments: [String], softwareId: String) async -> Bool {\n        await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async { [weak self] in\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: brewPath)\n                process.arguments = arguments\n\n                let outputPipe = Pipe()\n                let errorPipe = Pipe()\n                process.standardOutput = outputPipe\n                process.standardError = errorPipe\n\n                // 实时读取输出\n                outputPipe.fileHandleForReading.readabilityHandler = { [weak self] handle in\n                    let data = handle.availableData\n                    if !data.isEmpty, let output = String(data: data, encoding: .utf8) {\n                        DispatchQueue.main.async {\n                            self?.processOutput(output, for: softwareId)\n                        }\n                    }\n                }\n\n                errorPipe.fileHandleForReading.readabilityHandler = { [weak self] handle in\n                    let data = handle.availableData\n                    if !data.isEmpty, let output = String(data: data, encoding: .utf8) {\n                        DispatchQueue.main.async {\n                            self?.processOutput(output, for: softwareId)\n                        }\n                    }\n                }\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    // 清理 handler\n                    outputPipe.fileHandleForReading.readabilityHandler = nil\n                    errorPipe.fileHandleForReading.readabilityHandler = nil\n\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    outputPipe.fileHandleForReading.readabilityHandler = nil\n                    errorPipe.fileHandleForReading.readabilityHandler = nil\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // 处理输出并更新进度\n    private func processOutput(_ output: String, for softwareId: String) {\n        guard var progress = installProgressMap[softwareId] else { return }\n\n        // 追加输出\n        progress.output += output\n\n        // 解析输出判断阶段\n        let lowercased = output.lowercased()\n\n        if lowercased.contains(" = " + errStr).trimmingCharacters(in: .whitespacesAndNewlines)\n                    continuation.resume(returning: (process.terminationStatus == 0, combined))\n                } catch {\n                    continuation.resume(returning: (false, error.localizedDescription))\n                }\n            }\n        }\n    }\n\n    // 带进度的命令执行\n    private func runBrewCommandWithProgress(_ brewPath: String, arguments: [String], softwareId: String) async -> Bool {\n        await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async { [weak self] in\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: brewPath)\n                process.arguments = arguments\n\n                let outputPipe = Pipe()\n                let errorPipe = Pipe()\n                process.standardOutput = outputPipe\n                process.standardError = errorPipe\n\n                // 实时读取输出\n                outputPipe.fileHandleForReading.readabilityHandler = { [weak self] handle in\n                    let data = handle.availableData\n                    if !data.isEmpty, let output = String(data: data, encoding: .utf8) {\n                        DispatchQueue.main.async {\n                            self?.processOutput(output, for: softwareId)\n                        }\n                    }\n                }\n\n                errorPipe.fileHandleForReading.readabilityHandler = { [weak self] handle in\n                    let data = handle.availableData\n                    if !data.isEmpty, let output = String(data: data, encoding: .utf8) {\n                        DispatchQueue.main.async {\n                            self?.processOutput(output, for: softwareId)\n                        }\n                    }\n                }\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    // 清理 handler\n                    outputPipe.fileHandleForReading.readabilityHandler = nil\n                    errorPipe.fileHandleForReading.readabilityHandler = nil\n\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    outputPipe.fileHandleForReading.readabilityHandler = nil\n                    errorPipe.fileHandleForReading.readabilityHandler = nil\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // 处理输出并更新进度\n    private func processOutput(_ output: String, for softwareId: String) {\n        guard var progress = installProgressMap[softwareId] else { return }\n\n        // 追加输出\n        progress.output += output\n\n        // 解析输出判断阶段\n        let lowercased = output.lowercased()\n\n        if lowercased.contains(";
"]),\n\n        // ===== 安全配置 =====\n        " = "]),\n\n        // ===== 安全配置 =====\n        ";
"确定要删除站点 \\(site.name) 吗？站点将被移到回收站，可以恢复。" = "确定要删除站点 \\(site.name) 吗？站点将被移到回收站，可以恢复。";
":\n                break // 忽略空白字符\n            default:\n                formatted.append(char)\n            }\n        }\n        \n        // 检查栈中剩余的未闭合的左括号\n        for remaining in stack {\n            errors.append(NSRange(location: remaining.1, length: 1))\n        }\n        \n        if !errors.isEmpty {\n            inputSyntaxErrors = errors\n        }\n        \n        outputCode = formatted\n    }\n\n    private func minifyJSON() {\n        guard let data = inputCode.data(using: .utf8) else {\n            errorMessage = " = ":\n                break // 忽略空白字符\n            default:\n                formatted.append(char)\n            }\n        }\n        \n        // 检查栈中剩余的未闭合的左括号\n        for remaining in stack {\n            errors.append(NSRange(location: remaining.1, length: 1))\n        }\n        \n        if !errors.isEmpty {\n            inputSyntaxErrors = errors\n        }\n        \n        outputCode = formatted\n    }\n\n    private func minifyJSON() {\n        guard let data = inputCode.data(using: .utf8) else {\n            errorMessage = ";
")\n                        }\n                    }\n                }\n            }\n\n            // 检查 default.conf\n            if FileManager.default.fileExists(atPath: brewDefaultConf) {\n                if let linkDest = try? FileManager.default.destinationOfSymbolicLink(atPath: brewDefaultConf) {\n                    info.append(" = ")\n                        }\n                    }\n                }\n            }\n\n            // 检查 default.conf\n            if FileManager.default.fileExists(atPath: brewDefaultConf) {\n                if let linkDest = try? FileManager.default.destinationOfSymbolicLink(atPath: brewDefaultConf) {\n                    info.append(";
"\n    @State private var isLoading = true\n    @State private var errorMessage: String?\n    @State private var hasChanges = false\n    @State private var editMode: ConfigEditMode = .form\n    @State private var configSections: [ConfigSection] = []\n    @State private var displayType: ConfigDisplayType = .common\n    @State private var statusMessages: [StatusMessage] = []\n    @State private var isOperating = false\n    @State private var selectedSidebarItem: PHPConfigSidebarItem = .config\n\n    // 备份回滚相关\n    @State private var showBackupSheet = false\n    @State private var configBackups: [ConfigBackup] = []\n\n    // 配置迁移相关\n    @State private var showMigrationAlert = false\n    @State private var pendingMigration: ConfigPathProvider.ConfigMigrationInfo?\n    @State private var currentConfigPath: String = " = "\n    @State private var isLoading = true\n    @State private var errorMessage: String?\n    @State private var hasChanges = false\n    @State private var editMode: ConfigEditMode = .form\n    @State private var configSections: [ConfigSection] = []\n    @State private var displayType: ConfigDisplayType = .common\n    @State private var statusMessages: [StatusMessage] = []\n    @State private var isOperating = false\n    @State private var selectedSidebarItem: PHPConfigSidebarItem = .config\n\n    // 备份回滚相关\n    @State private var showBackupSheet = false\n    @State private var configBackups: [ConfigBackup] = []\n\n    // 配置迁移相关\n    @State private var showMigrationAlert = false\n    @State private var pendingMigration: ConfigPathProvider.ConfigMigrationInfo?\n    @State private var currentConfigPath: String = ";
"确定要删除选中的 \\(selectedEntries.count) 个 hosts 条目吗？" = "确定要删除选中的 \\(selectedEntries.count) 个 hosts 条目吗？";
"错误日志 (最近10行):\\n\\(recentLog)" = "错误日志 (最近10行):\\n\\(recentLog)";
"\n        errorMessage = nil\n\n        // 初始化进度\n        let initialProgress = InstallProgress(\n            phase: .preparing,\n            progress: 0,\n            currentStep: isLegacyVersion ? " = "\n        errorMessage = nil\n\n        // 初始化进度\n        let initialProgress = InstallProgress(\n            phase: .preparing,\n            progress: 0,\n            currentStep: isLegacyVersion ? ";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 表单内容\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // IP 地址\n                FormField(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 表单内容\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // IP 地址\n                FormField(";
" }.count\n\n                // 如果遇到 include servers/*; 就停止处理\n                if line.contains(" = " }.count\n\n                // 如果遇到 include servers/*; 就停止处理\n                if line.contains(";
")\n\n                            // 收集所有配置（包括 Nginx 反向代理配置）\n                            // 稍后会检查配置完整性并过滤\n\n                            // 检查是否已经在站点列表中（使用捕获的站点列表）\n                            let alreadyExists = currentSites.contains { site in\n                                !site.isDeleted && Set(site.domains).intersection(Set(parsed.domains)).count > 0\n                            }\n\n                            configs.append(FoundVhostConfig(\n                                parsed: parsed,\n                                filePath: filePath,\n                                alreadyExists: alreadyExists,\n                                hasConflict: false,\n                                conflictWith: nil,\n                                completenessStatus: .complete,\n                                relatedConfigPath: nil\n                            ))\n                        } else {\n                            self.writeVhostScanLog(" = ")\n\n                            // 收集所有配置（包括 Nginx 反向代理配置）\n                            // 稍后会检查配置完整性并过滤\n\n                            // 检查是否已经在站点列表中（使用捕获的站点列表）\n                            let alreadyExists = currentSites.contains { site in\n                                !site.isDeleted && Set(site.domains).intersection(Set(parsed.domains)).count > 0\n                            }\n\n                            configs.append(FoundVhostConfig(\n                                parsed: parsed,\n                                filePath: filePath,\n                                alreadyExists: alreadyExists,\n                                hasConflict: false,\n                                conflictWith: nil,\n                                completenessStatus: .complete,\n                                relatedConfigPath: nil\n                            ))\n                        } else {\n                            self.writeVhostScanLog(";
" }\n\n    /// PHP 配置文件路径\n    func phpConfigFile(version: String) -> String {\n        " = " }\n\n    /// PHP 配置文件路径\n    func phpConfigFile(version: String) -> String {\n        ";
"\n    }\n\n    /// 获取 MySQL 数据目录\n    func getMySQLDataPath(version: String) -> String {\n        if let config = config, config.isInitialized {\n            return config.mysqlDataPath(version: version)\n        }\n        return " = "\n    }\n\n    /// 获取 MySQL 数据目录\n    func getMySQLDataPath(version: String) -> String {\n        if let config = config, config.isInitialized {\n            return config.mysqlDataPath(version: version)\n        }\n        return ";
"成功删除 \\(successCount) 个配置文件，失败 \\(failCount) 个" = "成功删除 \\(successCount) 个配置文件，失败 \\(failCount) 个";
"找到 \\(foundConfigs.count) 个配置文件" = "找到 \\(foundConfigs.count) 个配置文件";
" : site.note)\n                        .onTapGesture {\n                            editingNote = site.note\n                            isEditingNote = true\n                        }\n                }\n            }\n            .frame(width: 100, alignment: .leading)\n\n            // Web服务器（可下拉更换）\n            HStack(alignment: .center, spacing: 4) {\n                Text(site.webServer == .nginx ? LocalizedStringKey(" = " : site.note)\n                        .onTapGesture {\n                            editingNote = site.note\n                            isEditingNote = true\n                        }\n                }\n            }\n            .frame(width: 100, alignment: .leading)\n\n            // Web服务器（可下拉更换）\n            HStack(alignment: .center, spacing: 4) {\n                Text(site.webServer == .nginx ? LocalizedStringKey(";
"]),\n\n        // ===== InnoDB 配置 =====\n        " = "]),\n\n        // ===== InnoDB 配置 =====\n        ";
"\n                        found = true\n                        break\n                    }\n                }\n            }\n        }\n\n        // 如果没找到，在适当位置添加\n        if !found {\n            // 对于 MySQL，在 [mysqld] 段末尾添加\n            if service.name == " = "\n                        found = true\n                        break\n                    }\n                }\n            }\n        }\n\n        // 如果没找到，在适当位置添加\n        if !found {\n            // 对于 MySQL，在 [mysqld] 段末尾添加\n            if service.name == ";
", maxSplits: 1)\n                if parts.count >= 1 {\n                    let k = parts[0].trimmingCharacters(in: .whitespaces)\n                    if k == key {\n                        // 根据isEnabled状态生成配置行\n                        let configLine = " = ", maxSplits: 1)\n                if parts.count >= 1 {\n                    let k = parts[0].trimmingCharacters(in: .whitespaces)\n                    if k == key {\n                        // 根据isEnabled状态生成配置行\n                        let configLine = ";
"\n        password = String((0..<16).map { _ in chars.randomElement()! })\n    }\n\n    // 创建数据库\n    private func createDatabase() {\n        isCreating = true\n        errorMessage = nil\n\n        Task {\n            let result = await connector.createDatabaseWithUser(\n                databaseName: databaseName,\n                charset: charset,\n                username: username,\n                password: password,\n                accessHost: accessHost\n            )\n\n            await MainActor.run {\n                if result.success {\n                    dismiss()\n                } else {\n                    errorMessage = result.error\n                }\n                isCreating = false\n            }\n        }\n    }\n}\n\n// MARK: - 添加用户 Sheet\nstruct AddUserSheet: View {\n    @ObservedObject var connector: MySQLConnector\n    @Environment(\\.dismiss) private var dismiss\n    @State private var username = " = "\n        password = String((0..<16).map { _ in chars.randomElement()! })\n    }\n\n    // 创建数据库\n    private func createDatabase() {\n        isCreating = true\n        errorMessage = nil\n\n        Task {\n            let result = await connector.createDatabaseWithUser(\n                databaseName: databaseName,\n                charset: charset,\n                username: username,\n                password: password,\n                accessHost: accessHost\n            )\n\n            await MainActor.run {\n                if result.success {\n                    dismiss()\n                } else {\n                    errorMessage = result.error\n                }\n                isCreating = false\n            }\n        }\n    }\n}\n\n// MARK: - 添加用户 Sheet\nstruct AddUserSheet: View {\n    @ObservedObject var connector: MySQLConnector\n    @Environment(\\.dismiss) private var dismiss\n    @State private var username = ";
"Nginx 配置正常" = "Nginx 配置正常";
", nil)\n        }\n\n        return await testConnectionWithClient(\n            clientPath: mysqlPath,\n            serverAddress: serverAddress,\n            port: port,\n            username: username,\n            password: password\n        )\n    }\n\n    // 使用指定客户端测试连接\n    private func testConnectionWithClient(clientPath: String, serverAddress: String, port: Int, username: String, password: String) async -> (success: Bool, message: String?, version: String?) {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: clientPath)\n                let arguments = [\n                    " = ", nil)\n        }\n\n        return await testConnectionWithClient(\n            clientPath: mysqlPath,\n            serverAddress: serverAddress,\n            port: port,\n            username: username,\n            password: password\n        )\n    }\n\n    // 使用指定客户端测试连接\n    private func testConnectionWithClient(clientPath: String, serverAddress: String, port: Int, username: String, password: String) async -> (success: Bool, message: String?, version: String?) {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: clientPath)\n                let arguments = [\n                    ";
"\\(service.name) 重新加载配置失败，请检查日志" = "\\(service.name) 重新加载配置失败，请检查日志";
"数据库 \\(dbName) 已删除" = "数据库 \\(dbName) 已删除";
")\n        }\n    }\n\n    // Nginx 端口配置\n    @Published var nginxPort: Int {\n        didSet {\n            UserDefaults.standard.set(nginxPort, forKey: " = ")\n        }\n    }\n\n    // Nginx 端口配置\n    @Published var nginxPort: Int {\n        didSet {\n            UserDefaults.standard.set(nginxPort, forKey: ";
", 5432, 5),\n                    // 中优先级 - PHP\n                    (" = ", 5432, 5),\n                    // 中优先级 - PHP\n                    (";
" {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(width: 800, height: 600)\n    }\n    \n    // V1 旧版布局\n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            HStack {\n                Text(LocalizedStringKey(" = " {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(width: 800, height: 600)\n    }\n    \n    // V1 旧版布局\n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            HStack {\n                Text(LocalizedStringKey(";
",\n                        isDirectory: true\n                    ),\n                    // Apache 配置目录\n                    ConfigFileLocation(\n                        workspacePath: " = ",\n                        isDirectory: true\n                    ),\n                    // Apache 配置目录\n                    ConfigFileLocation(\n                        workspacePath: ";
"卸载 \\(software.name) 失败" = "卸载 \\(software.name) 失败";
")\n        }\n\n        // 更新内存中的列表\n        entries.append(entry)\n\n        // 保存到文件（需要管理员权限）\n        try await saveHosts()\n        try await loadHostsAsync()\n    }\n\n    // 删除 hosts 条目\n    func deleteEntry(_ entry: HostEntry) async throws {\n        // 从内存中删除\n        entries.removeAll { $0.id == entry.id }\n\n        // 保存到文件（需要管理员权限）\n        try await saveHosts()\n        try await loadHostsAsync()\n    }\n\n    // 批量启用/禁用\n    func toggleEntries(_ entryIds: Set<UUID>, enabled: Bool) async throws {\n        // 更新内存中的状态\n        for id in entryIds {\n            if let index = entries.firstIndex(where: { $0.id == id }) {\n                entries[index].isEnabled = enabled\n            }\n        }\n\n        // 保存到文件（需要管理员权限）\n        try await saveHosts()\n        try await loadHostsAsync()\n    }\n\n    // 编辑 hosts 条目\n    // forceManaged 为 true 时，编辑后条目会纳入管理区域，确保修改可保存\n    func updateEntry(\n        entryId: UUID,\n        ip: String,\n        hostname: String,\n        comment: String?,\n        forceManaged: Bool = true\n    ) async throws {\n        guard let targetIndex = entries.firstIndex(where: { $0.id == entryId }) else {\n            throw HostsError.invalidFormat(" = ")\n        }\n\n        // 更新内存中的列表\n        entries.append(entry)\n\n        // 保存到文件（需要管理员权限）\n        try await saveHosts()\n        try await loadHostsAsync()\n    }\n\n    // 删除 hosts 条目\n    func deleteEntry(_ entry: HostEntry) async throws {\n        // 从内存中删除\n        entries.removeAll { $0.id == entry.id }\n\n        // 保存到文件（需要管理员权限）\n        try await saveHosts()\n        try await loadHostsAsync()\n    }\n\n    // 批量启用/禁用\n    func toggleEntries(_ entryIds: Set<UUID>, enabled: Bool) async throws {\n        // 更新内存中的状态\n        for id in entryIds {\n            if let index = entries.firstIndex(where: { $0.id == id }) {\n                entries[index].isEnabled = enabled\n            }\n        }\n\n        // 保存到文件（需要管理员权限）\n        try await saveHosts()\n        try await loadHostsAsync()\n    }\n\n    // 编辑 hosts 条目\n    // forceManaged 为 true 时，编辑后条目会纳入管理区域，确保修改可保存\n    func updateEntry(\n        entryId: UUID,\n        ip: String,\n        hostname: String,\n        comment: String?,\n        forceManaged: Bool = true\n    ) async throws {\n        guard let targetIndex = entries.firstIndex(where: { $0.id == entryId }) else {\n            throw HostsError.invalidFormat(";
", port.pid]\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            if process.terminationStatus == 0 {\n                // 成功终止，刷新列表\n                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {\n                    refreshPorts()\n                }\n            } else {\n                errorMessage = " = ", port.pid]\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            if process.terminationStatus == 0 {\n                // Finalización exitosa; actualizar la lista\n                DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {\n                    refreshPorts()\n                }\n            } else {\n                errorMessage = ";
"\n                }\n\n                // 使用较低的优先级（120+）\n                additionalVersions.append((displayName, packageName, 3306, 120 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// 动态检测所有已安装的 PostgreSQL 版本（不在预定义列表中的）\n    private nonisolated func detectAdditionalPostgreSQLVersions(\n        predefinedBrewNames: Set<String>\n    ) -> [(name: String, brewName: String, port: Int, priority: Int)] {\n        guard let packages = AppState.cachedInstalledPackages else { return [] }\n\n        var additionalVersions: [(name: String, brewName: String, port: Int, priority: Int)] = []\n\n        for (packageName, _) in packages {\n            // 匹配 postgresql 或 postgresql@x.x 格式\n            if packageName == " = "\n                }\n\n                // Usar una prioridad más baja (120+ )\n                additionalVersions.append((displayName, packageName, 3306, 120 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// Detectar dinámicamente todas las versiones de PostgreSQL instaladas (no incluidas en la lista predefinida)\n    private nonisolated func detectAdditionalPostgreSQLVersions(\n        predefinedBrewNames: Set<String>\n    ) -> [(name: String, brewName: String, port: Int, priority: Int)] {\n        guard let packages = AppState.cachedInstalledPackages else { return [] }\n\n        var additionalVersions: [(name: String, brewName: String, port: Int, priority: Int)] = []\n\n        for (packageName, _) in packages {\n            // Coincidir con formatos como postgresql o postgresql@x.x\n            if packageName == ";
"\n\n    // 获取站点关联的证书\n    private var siteCertificate: Certificate? {\n        if let certId = site.certificateId {\n            return certificateManager.certificates.first { $0.id == certId }\n        }\n        return nil\n    }\n    \n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            HStack {\n                Text(" = "\n\n    // Obtener el certificado asociado al sitio\n    private var siteCertificate: Certificate? {\n        if let certId = site.certificateId {\n            return certificateManager.certificates.first { $0.id == certId }\n        }\n        return nil\n    }\n    \n    var body: some View {\n        VStack(spacing: 0) {\n            // Barra de título\n            HStack {\n                Text(";
"\n\n        let targetPath = FileManager.default.fileExists(atPath: phpPath) ? phpPath : altPath\n\n        guard let linkDest = try? FileManager.default.destinationOfSymbolicLink(atPath: targetPath) else {\n            return nil\n        }\n\n        // 链接目标类似: ../Cellar/php@8.3/8.3.30/bin/php\n        // 提取包名\n        if linkDest.contains(" = "\n\n        let targetPath = FileManager.default.fileExists(atPath: phpPath) ? phpPath : altPath\n\n        guard let linkDest = try? FileManager.default.destinationOfSymbolicLink(atPath: targetPath) else {\n            return nil\n        }\n\n        // El destino del enlace es similar a: ../Cellar/php@8.3/8.3.30/bin/php\n        // Extraer el nombre del paquete\n        if linkDest.contains(";
")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding(20)\n\n            // 差异内容\n            HStack(spacing: 0) {\n                // 源规则\n                VStack(alignment: .leading, spacing: 8) {\n                    Text(" = ")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding(20)\n\n            // Contenido de diferencias\n            HStack(spacing: 0) {\n                // Reglas de origen\n                VStack(alignment: .leading, spacing: 8) {\n                    Text(";
")\n                .font(.system(size: 12))\n                .frame(width: 80, alignment: .leading)\n\n            // 备注（可点击编辑）\n            Group {\n                if isEditingNote {\n                    TextField(" = ")\n                .font(.system(size: 12))\n                .frame(width: 80, alignment: .leading)\n\n            // Nota (editable al hacer clic)\n            Group {\n                if isEditingNote {\n                    TextField(";
", sql]\n                resetProcess.standardOutput = Pipe()\n                resetProcess.standardError = Pipe()\n\n                var resetSuccess = false\n                do {\n                    try resetProcess.run()\n                    resetProcess.waitUntilExit()\n                    resetSuccess = resetProcess.terminationStatus == 0\n                } catch {\n                    resetSuccess = false\n                }\n\n                // 步骤4: 停止安全模式的 MySQL\n                safeProcess.terminate()\n                Thread.sleep(forTimeInterval: 2)\n\n                // 步骤5: 正常启动 MySQL 服务\n                let startProcess = Process()\n                startProcess.executableURL = URL(fileURLWithPath: brewPath)\n                startProcess.arguments = [" = ", sql]\n                resetProcess.standardOutput = Pipe()\n                resetProcess.standardError = Pipe()\n\n                var resetSuccess = false\n                do {\n                    try resetProcess.run()\n                    resetProcess.waitUntilExit()\n                    resetSuccess = resetProcess.terminationStatus == 0\n                } catch {\n                    resetSuccess = false\n                }\n\n                // Paso 4: Detener MySQL en modo seguro\n                safeProcess.terminate()\n                Thread.sleep(forTimeInterval: 2)\n\n                // Paso 5: Iniciar normalmente el servicio MySQL\n                let startProcess = Process()\n                startProcess.executableURL = URL(fileURLWithPath: brewPath)\n                startProcess.arguments = [";
"\n\n        // 优先使用工作空间路径\n        if let config = workspaceManager.config, config.isInitialized {\n            paths.append(config.mysqlConfigFile(version: ver))\n        }\n\n        // Homebrew 默认路径\n        paths.append(" = "\n\n        // Priorizar la ruta del espacio de trabajo\n        if let config = workspaceManager.config, config.isInitialized {\n            paths.append(config.mysqlConfigFile(version: ver))\n        }\n\n        // Ruta predeterminada de Homebrew\n        paths.append(";
")\n        } else if status == 0x04 {\n            // 需要完整认证（需要 SSL 或明文密码）\n            throw MySQLError.authenticationFailed(" = ")\n        } else if status == 0x04 {\n            // Se requiere autenticación completa (SSL o contraseña en texto plano)\n            throw MySQLError.authenticationFailed(";
"AI 分析结果" = "Resultado del análisis de IA";
", actualBrewName],\n            softwareId: software.id\n        )\n\n        if !success {\n            // 失败时保留记录，提取错误信息\n            let errorDetail = extractErrorFromOutput(installProgressMap[software.id]?.output ?? " = ", actualBrewName],\n            softwareId: software.id\n        )\n\n        if !success {\n            // En caso de fallo, conservar el registro y extraer los detalles del error\n            let errorDetail = extractErrorFromOutput(installProgressMap[software.id]?.output ?? ";
")\n                    .foregroundColor(.secondary)\n                    .font(.title2)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(16)\n    }\n\n    private var totalLogCount: Int {\n        if let id = softwareId {\n            return logManager.logCount(for: id)\n        } else if let name = softwareName {\n            return logManager.logCountForSoftware(named: name)\n        }\n        return 0\n    }\n\n    // MARK: - Log List\n    private var logListView: some View {\n        ScrollView {\n            LazyVStack(spacing: 8) {\n                let logs = currentLogs\n\n                if logs.isEmpty {\n                    emptyStateView\n                } else {\n                    ForEach(logs) { log in\n                        ErrorLogRow(\n                            log: log,\n                            isExpanded: expandedLogId == log.id,\n                            onToggle: { toggleExpand(log.id) }\n                        )\n                    }\n\n                    // 加载更多按钮\n                    if !showAll && totalLogCount > initialLimit {\n                        loadMoreButton\n                    }\n                }\n            }\n            .padding(16)\n        }\n    }\n\n    private var currentLogs: [ErrorLogEntry] {\n        if let id = softwareId {\n            return showAll\n                ? logManager.getAllLogs(for: id)\n                : logManager.getLogs(for: id, limit: initialLimit)\n        } else if let name = softwareName {\n            return showAll\n                ? logManager.getAllLogsForSoftware(named: name)\n                : logManager.getLogsForSoftware(named: name, limit: initialLimit)\n        }\n        return []\n    }\n\n    private var emptyStateView: some View {\n        VStack(spacing: 12) {\n            Image(systemName: " = ")\n                    .foregroundColor(.secondary)\n                    .font(.title2)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(16)\n    }\n\n    private var totalLogCount: Int {\n        if let id = softwareId {\n            return logManager.logCount(for: id)\n        } else if let name = softwareName {\n            return logManager.logCountForSoftware(named: name)\n        }\n        return 0\n    }\n\n    // MARK: - Lista de registros\n    private var logListView: some View {\n        ScrollView {\n            LazyVStack(spacing: 8) {\n                let logs = currentLogs\n\n                if logs.isEmpty {\n                    emptyStateView\n                } else {\n                    ForEach(logs) { log in\n                        ErrorLogRow(\n                            log: log,\n                            isExpanded: expandedLogId == log.id,\n                            onToggle: { toggleExpand(log.id) }\n                        )\n                    }\n\n                    // Botón para cargar más\n                    if !showAll && totalLogCount > initialLimit {\n                        loadMoreButton\n                    }\n                }\n            }\n            .padding(16)\n        }\n    }\n\n    private var currentLogs: [ErrorLogEntry] {\n        if let id = softwareId {\n            return showAll\n                ? logManager.getAllLogs(for: id)\n                : logManager.getLogs(for: id, limit: initialLimit)\n        } else if let name = softwareName {\n            return showAll\n                ? logManager.getAllLogsForSoftware(named: name)\n                : logManager.getLogsForSoftware(named: name, limit: initialLimit)\n        }\n        return []\n    }\n\n    private var emptyStateView: some View {\n        VStack(spacing: 12) {\n            Image(systemName: ";
"))\n                        .font(.caption)\n                        .foregroundColor(.secondary)\n                }\n                Spacer()\n            }\n\n            Divider()\n\n            // 待检查项目列表\n            VStack(spacing: 6) {\n                PendingCheckRow(type: .hosts)\n                PendingCheckRow(type: .webServer)\n                PendingCheckRow(type: .ports)\n\n                // PHP 检查（如果需要）\n                if shouldShowPHPCheck(for: site) {\n                    PendingCheckRow(type: .php)\n                }\n\n                // SSL 检查（如果启用）\n                if site.sslStatus == .enabled {\n                    PendingCheckRow(type: .ssl)\n                }\n            }\n        }\n        .padding(10)\n            .glass(.input)\n    }\n\n    private func shouldShowPHPCheck(for site: Site) -> Bool {\n        // 简单判断：如果文档根目录存在，就显示 PHP 检查项\n        return FileManager.default.fileExists(atPath: site.documentRoot)\n    }\n}\n\n// MARK: - Pending Check Row\n\nstruct PendingCheckRow: View {\n    let type: HealthCheckType\n\n    var body: some View {\n        HStack(spacing: 10) {\n            // 待检测图标\n            Image(systemName: " = "))\n                        .font(.caption)\n                        .foregroundColor(.secondary)\n                }\n                Spacer()\n            }\n\n            Divider()\n\n            // Lista de elementos pendientes de comprobación\n            VStack(spacing: 6) {\n                PendingCheckRow(type: .hosts)\n                PendingCheckRow(type: .webServer)\n                PendingCheckRow(type: .ports)\n\n                // Comprobación de PHP (si procede)\n                if shouldShowPHPCheck(for: site) {\n                    PendingCheckRow(type: .php)\n                }\n\n                // Comprobación de SSL (si está habilitada)\n                if site.sslStatus == .enabled {\n                    PendingCheckRow(type: .ssl)\n                }\n            }\n        }\n        .padding(10)\n            .glass(.input)\n    }\n\n    private func shouldShowPHPCheck(for site: Site) -> Bool {\n        // Comprobación sencilla: mostrar la comprobación de PHP si existe el directorio raíz de documentos\n        return FileManager.default.fileExists(atPath: site.documentRoot)\n    }\n}\n\n// MARK: - Fila de comprobación pendiente\n\nstruct PendingCheckRow: View {\n    let type: HealthCheckType\n\n    var body: some View {\n        HStack(spacing: 10) {\n            // Icono de elemento pendiente de comprobación\n            Image(systemName: ";
")\n        var isReading = true\n\n        readQueue.async {\n            while isReading {\n                let availableData = outputHandle.availableData\n                if availableData.isEmpty {\n                    isReading = false\n                    break\n                }\n\n                outputData.append(availableData)\n\n                // 解析输出并更新进度\n                if let output = String(data: outputData, encoding: .utf8) {\n                    self.parseBrewOutput(output)\n                }\n            }\n        }\n\n        process.waitUntilExit()\n\n        guard process.terminationStatus == 0 else {\n            let output = String(data: outputData, encoding: .utf8) ?? " = ")\n        var isReading = true\n\n        readQueue.async {\n            while isReading {\n                let availableData = outputHandle.availableData\n                if availableData.isEmpty {\n                    isReading = false\n                    break\n                }\n\n                outputData.append(availableData)\n\n                // Analizar la salida y actualizar el progreso\n                if let output = String(data: outputData, encoding: .utf8) {\n                    self.parseBrewOutput(output)\n                }\n            }\n        }\n\n        process.waitUntilExit()\n\n        guard process.terminationStatus == 0 else {\n            let output = String(data: outputData, encoding: .utf8) ?? ";
"\\(self.service.name) 重启失败 (退出码: \\(process.terminationStatus))" = "\\(self.service.name) no se reinició correctamente (código de salida: \\(process.terminationStatus))";
"\n    @State private var showAdvancedSettingsSheet = false\n    @EnvironmentObject var appState: AppState\n\n    // 获取唯一域名数量（去重）\n    private var uniqueDomainsCount: Int {\n        Set(site.domains).count\n    }\n\n    var body: some View {\n        HStack(alignment: .center, spacing: 8) {\n            // 复选框\n            Button(action: onToggleSelection) {\n                ZStack {\n                    Circle()\n                        .fill(Color.white)\n                        .frame(width: 18, height: 18)\n                        .overlay(\n                            Circle()\n                                .stroke(AppColors.border, lineWidth: 1)\n                        )\n\n                    if isSelected {\n                        Image(systemName: " = "\n    @State private var showAdvancedSettingsSheet = false\n    @EnvironmentObject var appState: AppState\n\n    // Obtener el número de dominios únicos (sin duplicados)\n    private var uniqueDomainsCount: Int {\n        Set(site.domains).count\n    }\n\n    var body: some View {\n        HStack(alignment: .center, spacing: 8) {\n            // Casilla de verificación\n            Button(action: onToggleSelection) {\n                ZStack {\n                    Circle()\n                        .fill(Color.white)\n                        .frame(width: 18, height: 18)\n                        .overlay(\n                            Circle()\n                                .stroke(AppColors.border, lineWidth: 1)\n                        )\n\n                    if isSelected {\n                        Image(systemName: ";
")\n                            .foregroundColor(isPortValid ? .green : .orange)\n                        Text(portValidationMessage)\n                            .font(.caption)\n                            .foregroundColor(isPortValid ? .green : .orange)\n                    }\n                }\n\n                // 端口同步结果\n                if showPortSyncResult, let result = portSyncResult {\n                    VStack(alignment: .leading, spacing: 8) {\n                        Divider()\n                        HStack {\n                            Image(systemName: result.success ? " = ")\n                            .foregroundColor(isPortValid ? .green : .orange)\n                        Text(portValidationMessage)\n                            .font(.caption)\n                            .foregroundColor(isPortValid ? .green : .orange)\n                    }\n                }\n\n                // Resultado de la sincronización de puertos\n                if showPortSyncResult, let result = portSyncResult {\n                    VStack(alignment: .leading, spacing: 8) {\n                        Divider()\n                        HStack {\n                            Image(systemName: result.success ? ";
"\n        }\n\n        do {\n            if FileManager.default.fileExists(atPath: configPath) {\n                try FileManager.default.removeItem(atPath: configPath)\n            }\n\n            // 重载 Nginx\n            _ = await reloadNginx()\n            return true\n        } catch {\n            lastError = " = "\n        }\n\n        do {\n            if FileManager.default.fileExists(atPath: configPath) {\n                try FileManager.default.removeItem(atPath: configPath)\n            }\n\n            // Recargar Nginx\n            _ = await reloadNginx()\n            return true\n        } catch {\n            lastError = ";
"\n                    inRewriteBlock = false\n                }\n                continue\n            }\n\n            if inRewriteBlock {\n                // 收集 RewriteCond\n                if trimmed.hasPrefix(" = "\n                    inRewriteBlock = false\n                }\n                continue\n            }\n\n            if inRewriteBlock {\n                // Recopilar RewriteCond\n                if trimmed.hasPrefix(";
"⚠️ 端口 \\(port) 已被占用" = "⚠️ El puerto \\(port) ya está en uso";
" && isApacheRunning {\n            return getApacheDocumentRoot()\n        } else if isNginxRunning {\n            return getNginxDocumentRoot()\n        } else if isApacheRunning {\n            return getApacheDocumentRoot()\n        }\n\n        return nil\n    }\n\n    /// 获取 Nginx 文档根目录\n    func getNginxDocumentRoot() -> String {\n        // 尝试从配置文件解析\n        if let root = parseNginxRoot() {\n            return root\n        }\n        return nginxHtmlPath\n    }\n\n    /// 获取 Apache 文档根目录\n    func getApacheDocumentRoot() -> String {\n        // 尝试从配置文件解析\n        if let root = parseApacheDocumentRoot() {\n            return root\n        }\n        return apacheDocumentRoot\n    }\n\n    /// 解析 Nginx 配置中的 root 目录\n    private func parseNginxRoot() -> String? {\n        guard let content = try? String(contentsOfFile: nginxConfigPath, encoding: .utf8) else {\n            return nil\n        }\n\n        // 简单解析 root 指令\n        let lines = content.components(separatedBy: .newlines)\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed.hasPrefix(" = " && isApacheRunning {\n            return getApacheDocumentRoot()\n        } else if isNginxRunning {\n            return getNginxDocumentRoot()\n        } else if isApacheRunning {\n            return getApacheDocumentRoot()\n        }\n\n        return nil\n    }\n\n    /// 获取 Nginx 文档根目录\n    func getNginxDocumentRoot() -> String {\n        // 尝试从配置文件解析\n        if let root = parseNginxRoot() {\n            return root\n        }\n        return nginxHtmlPath\n    }\n\n    /// 获取 Apache 文档根目录\n    func getApacheDocumentRoot() -> String {\n        // 尝试从配置文件解析\n        if let root = parseApacheDocumentRoot() {\n            return root\n        }\n        return apacheDocumentRoot\n    }\n\n    /// 解析 Nginx 配置中的 root 目录\n    private func parseNginxRoot() -> String? {\n        guard let content = try? String(contentsOfFile: nginxConfigPath, encoding: .utf8) else {\n            return nil\n        }\n\n        // 简单解析 root 指令\n        let lines = content.components(separatedBy: .newlines)\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed.hasPrefix(";
"MySQL 重启失败 (退出码: \\(process.terminationStatus))" = "MySQL 重启失败 (退出码: \\(process.terminationStatus))";
"执行 brew 命令失败: \\(error)" = "执行 brew 命令失败: \\(error)";
", nil)\n    }\n\n    /// 加载 Apache 端口数据\n    private func loadApachePortData() -> (port: String, error: String?) {\n        let lines = configContent.components(separatedBy: .newlines)\n        if let value = extractValue(for: " = ", nil)\n    }\n\n    /// 加载 Apache 端口数据\n    private func loadApachePortData() -> (port: String, error: String?) {\n        let lines = configContent.components(separatedBy: .newlines)\n        if let value = extractValue(for: ";
"\n\n        let result = await executeSQL(sql)\n\n        if result.success, let output = result.output {\n            databases = parseDatabaseInfo(output)\n        } else {\n            errorMessage = result.error\n        }\n\n        isLoading = false\n    }\n\n    /// 解析数据库信息\n    private func parseDatabaseInfo(_ output: String) -> [MySQLDatabaseInfo] {\n        let lines = output.components(separatedBy: .newlines)\n            .filter { !$0.isEmpty && !$0.contains(" = "\n\n        let result = await executeSQL(sql)\n\n        if result.success, let output = result.output {\n            databases = parseDatabaseInfo(output)\n        } else {\n            errorMessage = result.error\n        }\n\n        isLoading = false\n    }\n\n    /// 解析数据库信息\n    private func parseDatabaseInfo(_ output: String) -> [MySQLDatabaseInfo] {\n        let lines = output.components(separatedBy: .newlines)\n            .filter { !$0.isEmpty && !$0.contains(";
")\n                    .font(.system(size: 10))\n                    .foregroundColor(isHoveringName ? .blue : .secondary.opacity(0.6))\n                    .padding(.leading, 2)\n\n                // 如果有多个唯一域名，显示下拉箭头\n                if uniqueDomainsCount > 1 {\n                    Image(systemName: showDomainDropdown ? " = ")\n                    .font(.system(size: 10))\n                    .foregroundColor(isHoveringName ? .blue : .secondary.opacity(0.6))\n                    .padding(.leading, 2)\n\n                // 如果有多个唯一域名，显示下拉箭头\n                if uniqueDomainsCount > 1 {\n                    Image(systemName: showDomainDropdown ? ";
"读取 hosts 原始内容失败：\\(error.localizedDescription)" = "读取 hosts 原始内容失败：\\(error.localizedDescription)";
"运行 'lsof -i :\\(port)' 查看端口占用详情" = "运行 'lsof -i :\\(port)' 查看端口占用详情";
"\n    @State private var isCheckingPort = false\n    @State private var portAvailable: Bool? = nil\n    @State private var isCurrentPort: Bool = false  // 是否是当前端口\n    @State private var suggestedPort: Int? = nil    // 建议的可用端口\n    @State private var isViewReady = false          // 视图是否准备好\n\n    // 根据软件类型返回端口标签\n    private var portLabel: String {\n        switch softwareName.lowercased() {\n        case " = "\n    @State private var isCheckingPort = false\n    @State private var portAvailable: Bool? = nil\n    @State private var isCurrentPort: Bool = false  // 是否是当前端口\n    @State private var suggestedPort: Int? = nil    // 建议的可用端口\n    @State private var isViewReady = false          // 视图是否准备好\n\n    // 根据软件类型返回端口标签\n    private var portLabel: String {\n        switch softwareName.lowercased() {\n        case ";
"加载错误日志失败: \\(error)" = "加载错误日志失败: \\(error)";
")\n                    proxyBackendPort = Int(portStr)\n                }\n            }\n\n            // 解析 listen\n            if trimmed.hasPrefix(" = ")\n                    proxyBackendPort = Int(portStr)\n                }\n            }\n\n            // 解析 listen\n            if trimmed.hasPrefix(";
"\n\n    // 配置文件\n    var configContent: String = " = "\n\n    // 配置文件\n    var configContent: String = ";
")\n\n        // 按分组生成配置\n        let sections = ServiceConfigDefinitions.getConfigs(for: service.name, version: service.version)\n\n        for section in sections {\n            lines.append(" = ")\n\n        // 按分组生成配置\n        let sections = ServiceConfigDefinitions.getConfigs(for: service.name, version: service.version)\n\n        for section in sections {\n            lines.append(";
") {\n                let parts = trimmed.dropFirst(12).trimmingCharacters(in: .whitespaces)\n                // 移除引号\n                let path = parts.replacingOccurrences(of: " = ") {\n                let parts = trimmed.dropFirst(12).trimmingCharacters(in: .whitespaces)\n                // 移除引号\n                let path = parts.replacingOccurrences(of: ";
"正在修改 \\(software.name) 端口..." = "正在修改 \\(software.name) 端口...";
"\n        }\n        // 只有目录存在时才添加\n        if fileManager.fileExists(atPath: nginxPath) {\n            directories.append((label: " = "\n        }\n        // 只有目录存在时才添加\n        if fileManager.fileExists(atPath: nginxPath) {\n            directories.append((label: ";
") {\n                    currentPage = max(1, currentPage - 1)\n                }\n                .disabled(currentPage <= 1)\n\n                // V2 页码显示\n                Text(" = ") {\n                    currentPage = max(1, currentPage - 1)\n                }\n                .disabled(currentPage <= 1)\n\n                // V2 页码显示\n                Text(";
")\n                    .font(.system(size: 12))\n            }\n            .toggleStyle(.checkbox)\n\n            Spacer()\n\n            // 数据库数量\n            Text(" = ")\n                    .font(.system(size: 12))\n            }\n            .toggleStyle(.checkbox)\n\n            Spacer()\n\n            // 数据库数量\n            Text(";
" }\n        return processName\n    }\n\n    /// 查找 lsof 命令的路径\n    private func findLsofPath() -> String? {\n        let possiblePaths = [\n            " = " }\n        return processName\n    }\n\n    /// 查找 lsof 命令的路径\n    private func findLsofPath() -> String? {\n        let possiblePaths = [\n            ";
"✗ 复制默认首页失败: \\(error.localizedDescription)" = "✗ 复制默认首页失败: \\(error.localizedDescription)";
"\n        case .customIP: host = customIP\n        }\n\n        Task {\n            let success = await connector.changeUserHost(username: username, newHost: host)\n            await MainActor.run {\n                isSaving = false\n                if success { dismiss() }\n            }\n        }\n    }\n}\n\n// MARK: - 远程备份列表 Sheet\nstruct RemoteBackupListSheet: View {\n    let databaseName: String\n    @ObservedObject var connector: MySQLConnector\n    @Environment(\\.dismiss) private var dismiss\n\n    @State private var backupFiles: [URL] = []\n    @State private var isLoading = true\n\n    var body: some View {\n        VStack(spacing: 0) {\n            HStack {\n                Text(" = "\n        case .customIP: host = customIP\n        }\n\n        Task {\n            let success = await connector.changeUserHost(username: username, newHost: host)\n            await MainActor.run {\n                isSaving = false\n                if success { dismiss() }\n            }\n        }\n    }\n}\n\n// MARK: - 远程备份列表 Sheet\nstruct RemoteBackupListSheet: View {\n    let databaseName: String\n    @ObservedObject var connector: MySQLConnector\n    @Environment(\\.dismiss) private var dismiss\n\n    @State private var backupFiles: [URL] = []\n    @State private var isLoading = true\n\n    var body: some View {\n        VStack(spacing: 0) {\n            HStack {\n                Text(";
")\n    }\n\n    // 打开站点设置\n    private func openSiteSettings() {\n        showSettingsSheet = true\n    }\n\n    // 打开站点编辑器\n    private func openSiteEditor() {\n        showEditSheet = true\n    }\n\n    // 复制站点\n    private func duplicateSite() {\n        Task {\n            do {\n                try await siteManager.duplicateSite(site)\n            } catch {\n                let alert = NSAlert()\n                alert.messageText = " = ")\n    }\n\n    // 打开站点设置\n    private func openSiteSettings() {\n        showSettingsSheet = true\n    }\n\n    // 打开站点编辑器\n    private func openSiteEditor() {\n        showEditSheet = true\n    }\n\n    // 复制站点\n    private func duplicateSite() {\n        Task {\n            do {\n                try await siteManager.duplicateSite(site)\n            } catch {\n                let alert = NSAlert()\n                alert.messageText = ";
"))\n        }\n\n        // 如果有文件检查条件，使用 try_files\n        if hasFileCheck {\n            if replacement.contains(" = "))\n        }\n\n        // 如果有文件检查条件，使用 try_files\n        if hasFileCheck {\n            if replacement.contains(";
").localizedCaseInsensitiveContains(searchText) ||\n            site.note.localizedCaseInsensitiveContains(searchText)\n        }\n    }\n\n    // 获取选中的站点列表\n    private var selectedSitesList: [Site] {\n        siteManager.sites.filter { selectedSites.contains($0.id) }\n    }\n\n    // 判断是否所有选中的站点都在运行\n    private var allSelectedSitesRunning: Bool {\n        guard !selectedSites.isEmpty else { return false }\n        return selectedSitesList.allSatisfy { $0.status == .running }\n    }\n\n    // 判断是否所有选中的站点都已停止\n    private var allSelectedSitesStopped: Bool {\n        guard !selectedSites.isEmpty else { return false }\n        return selectedSitesList.allSatisfy { $0.status == .stopped }\n    }\n\n    // 顶部工具栏\n    private var topToolbar: some View {\n        HStack(spacing: 12) {\n            // 添加站点按钮\n            Button(action: { showAddSiteSheet = true }) {\n                Text(" = ").localizedCaseInsensitiveContains(searchText) ||\n            site.note.localizedCaseInsensitiveContains(searchText)\n        }\n    }\n\n    // 获取选中的站点列表\n    private var selectedSitesList: [Site] {\n        siteManager.sites.filter { selectedSites.contains($0.id) }\n    }\n\n    // 判断是否所有选中的站点都在运行\n    private var allSelectedSitesRunning: Bool {\n        guard !selectedSites.isEmpty else { return false }\n        return selectedSitesList.allSatisfy { $0.status == .running }\n    }\n\n    // 判断是否所有选中的站点都已停止\n    private var allSelectedSitesStopped: Bool {\n        guard !selectedSites.isEmpty else { return false }\n        return selectedSitesList.allSatisfy { $0.status == .stopped }\n    }\n\n    // 顶部工具栏\n    private var topToolbar: some View {\n        HStack(spacing: 12) {\n            // 添加站点按钮\n            Button(action: { showAddSiteSheet = true }) {\n                Text(";
")\n                            .font(.caption2)\n                            .foregroundColor(.secondary)\n                    }\n                }\n            }\n            .frame(height: 200)\n            .padding()\n            .glass(.input)\n        }\n    }\n\n    // 详细统计\n    private var detailedStatistics: some View {\n        VStack(alignment: .leading, spacing: 16) {\n            Text(" = ")\n                            .font(.caption2)\n                            .foregroundColor(.secondary)\n                    }\n                }\n            }\n            .frame(height: 200)\n            .padding()\n            .glass(.input)\n        }\n    }\n\n    // 详细统计\n    private var detailedStatistics: some View {\n        VStack(alignment: .leading, spacing: 16) {\n            Text(";
")\n        }\n    }\n\n    /// 检查文件名是否匹配模式\n    private func matchFilePattern(_ fileName: String, pattern: String) -> Bool {\n        // 简单的通配符匹配\n        if pattern == " = ")\n        }\n    }\n\n    /// 检查文件名是否匹配模式\n    private func matchFilePattern(_ fileName: String, pattern: String) -> Bool {\n        // 简单的通配符匹配\n        if pattern == ";
"\n\n                    let newTab = OpenedDatabaseTab(\n                        id: newTabId,\n                        name: newName,\n                        isLocal: tab.isLocal,\n                        host: newHost,\n                        port: newPort,\n                        databaseId: dbId\n                    )\n\n                    newTabs[index] = newTab\n                    needsUpdate = true\n\n                    // 如果当前标签是激活状态，更新 activeTabId\n                    if databaseManager.activeTabId == tab.id {\n                        newActiveId = newTabId\n                        activeIdChanged = true\n                    }\n                }\n            } else {\n                // 数据库已被删除，可以在这里选择关闭标签，或者不做处理\n                // 如果不做处理，标签页保留旧数据\n            }\n        }\n\n        if needsUpdate {\n            databaseManager.openedDatabaseTabs = newTabs\n            if activeIdChanged {\n                databaseManager.activeTabId = newActiveId\n            }\n        }\n    }\n\n    // 获取本地 MySQL 数据库列表\n    // 说明：\n    // - 之前这里通过 mysqlServiceInfo.isRunning 做了提前判断，导致在服务检测状态异常时不再尝试真实连接\n    // - 现在改为直接尝试通过 mysql 客户端获取数据库列表，由底层命令决定是否可用\n    //   这样即使服务检测逻辑有问题，只要 MySQL 实际在运行，这里也能获取到真实的库列表\n    private func fetchLocalDatabases() {\n        Task {\n            let dbs = await databaseManager.fetchLocalMySQLDatabases()\n            await MainActor.run {\n                databaseManager.localMySQLDatabases = dbs\n            }\n        }\n    }\n\n    // 获取当前MySQL服务信息\n    private var mysqlServiceInfo: (version: String, isRunning: Bool, installed: Bool) {\n        // 首先尝试从 appState.services 获取（服务加载完成后）\n        if !appState.isLoadingServices {\n            for service in appState.services {\n                let name = service.name.lowercased()\n                if name == " = "\n\n                    let newTab = OpenedDatabaseTab(\n                        id: newTabId,\n                        name: newName,\n                        isLocal: tab.isLocal,\n                        host: newHost,\n                        port: newPort,\n                        databaseId: dbId\n                    )\n\n                    newTabs[index] = newTab\n                    needsUpdate = true\n\n                    // 如果当前标签是激活状态，更新 activeTabId\n                    if databaseManager.activeTabId == tab.id {\n                        newActiveId = newTabId\n                        activeIdChanged = true\n                    }\n                }\n            } else {\n                // 数据库已被删除，可以在这里选择关闭标签，或者不做处理\n                // 如果不做处理，标签页保留旧数据\n            }\n        }\n\n        if needsUpdate {\n            databaseManager.openedDatabaseTabs = newTabs\n            if activeIdChanged {\n                databaseManager.activeTabId = newActiveId\n            }\n        }\n    }\n\n    // 获取本地 MySQL 数据库列表\n    // 说明：\n    // - 之前这里通过 mysqlServiceInfo.isRunning 做了提前判断，导致在服务检测状态异常时不再尝试真实连接\n    // - 现在改为直接尝试通过 mysql 客户端获取数据库列表，由底层命令决定是否可用\n    //   这样即使服务检测逻辑有问题，只要 MySQL 实际在运行，这里也能获取到真实的库列表\n    private func fetchLocalDatabases() {\n        Task {\n            let dbs = await databaseManager.fetchLocalMySQLDatabases()\n            await MainActor.run {\n                databaseManager.localMySQLDatabases = dbs\n            }\n        }\n    }\n\n    // 获取当前MySQL服务信息\n    private var mysqlServiceInfo: (version: String, isRunning: Bool, installed: Bool) {\n        // 首先尝试从 appState.services 获取（服务加载完成后）\n        if !appState.isLoadingServices {\n            for service in appState.services {\n                let name = service.name.lowercased()\n                if name == ";
"\n                # HTTP 服务器块 - 重定向到 HTTPS\n                server {\n                    listen 80;\n                    server_name \\(serverNames);\n\n                    # 强制跳转到 HTTPS\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # HTTPS 服务器块\n                server {\n                    listen 443 ssl;\n                    server_name \\(serverNames);\n                    root \\(publicRoot);\n                    index index.php index.html index.htm;\n\n                    # SSL 证书配置\n                    ssl_certificate \\(certPath);\n                    ssl_certificate_key \\(keyPath);\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # 访问日志\n                    access_log \\(accessLog);\n                    error_log \\(errorLog);\n\n                    location / {\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\\\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:\\(phpPort);\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # 禁止访问隐藏文件\n                    location ~ /\\\\. {\n                        deny all;\n                    }\n                }\n                " = "\n                # HTTP 服务器块 - 重定向到 HTTPS\n                server {\n                    listen 80;\n                    server_name \\(serverNames);\n\n                    # 强制跳转到 HTTPS\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # HTTPS 服务器块\n                server {\n                    listen 443 ssl;\n                    server_name \\(serverNames);\n                    root \\(publicRoot);\n                    index index.php index.html index.htm;\n\n                    # SSL 证书配置\n                    ssl_certificate \\(certPath);\n                    ssl_certificate_key \\(keyPath);\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # 访问日志\n                    access_log \\(accessLog);\n                    error_log \\(errorLog);\n\n                    location / {\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    location ~ \\\\.php$ {\n                        include fastcgi_params;\n                        fastcgi_pass 127.0.0.1:\\(phpPort);\n                        fastcgi_index index.php;\n                        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                    }\n\n                    # 禁止访问隐藏文件\n                    location ~ /\\\\. {\n                        deny all;\n                    }\n                }\n                ";
" ? Color.clear : Color(NSColor.controlBackgroundColor).opacity(AppTokens.Opacity.disabled))\n    }\n\n    // 端口配置区域\n    private var portConfigSection: some View {\n        GroupBox {\n            VStack(alignment: .leading, spacing: 12) {\n                HStack {\n                    Image(systemName: " = " ? Color.clear : Color(NSColor.controlBackgroundColor).opacity(AppTokens.Opacity.disabled))\n    }\n\n    // 端口配置区域\n    private var portConfigSection: some View {\n        GroupBox {\n            VStack(alignment: .leading, spacing: 12) {\n                HStack {\n                    Image(systemName: ";
"\n\n        alert.informativeText = detailsText\n        alert.alertStyle = .informational\n\n        // 添加按钮\n        alert.addButton(withTitle: " = "\n\n        alert.informativeText = detailsText\n        alert.alertStyle = .informational\n\n        // 添加按钮\n        alert.addButton(withTitle: ";
"]),\n\n        // ===== 日期时间 =====\n        " = "]),\n\n        // ===== 日期时间 =====\n        ";
"\n                }\n\n                // 生成证书文件\n                let certName = domainList[0]\n                let (certPath, keyPath) = try await CertificateManager.shared.generateCertificate(\n                    domains: domainList,\n                    outputDirectory: sslDir,\n                    certificateName: certName\n                )\n\n                // 解析生成的证书信息\n                let certInfo = CertificateManager.shared.parseCertificateInfo(certPath: certPath)\n\n                let certificate = Certificate(\n                    domains: domainList,\n                    certificateType: .selfSigned,\n                    certificatePath: certPath,\n                    privateKeyPath: keyPath,\n                    issuer: certInfo?.issuer ?? " = "\n                }\n\n                // 生成证书文件\n                let certName = domainList[0]\n                let (certPath, keyPath) = try await CertificateManager.shared.generateCertificate(\n                    domains: domainList,\n                    outputDirectory: sslDir,\n                    certificateName: certName\n                )\n\n                // 解析生成的证书信息\n                let certInfo = CertificateManager.shared.parseCertificateInfo(certPath: certPath)\n\n                let certificate = Certificate(\n                    domains: domainList,\n                    certificateType: .selfSigned,\n                    certificatePath: certPath,\n                    privateKeyPath: keyPath,\n                    issuer: certInfo?.issuer ?? ";
"\n            }\n        }\n        loadConfigFromFile()\n    }\n\n    private func loadConfigFromFile() {\n        let fileManager = FileManager.default\n        if fileManager.fileExists(atPath: configFilePath) {\n            do {\n                config.configContent = try String(contentsOfFile: configFilePath, encoding: .utf8)\n            } catch {\n                config.configContent = generateDefaultConfig()\n            }\n        } else {\n            config.configContent = generateDefaultConfig()\n        }\n    }\n\n    private func saveConfigToFile() {\n        isSaving = true\n        showSaveSuccess = false\n\n        Task {\n            do {\n                let fileManager = FileManager.default\n\n                // 备份原文件\n                if fileManager.fileExists(atPath: configFilePath) {\n                    let backupPath = configFilePath + " = "\n            }\n        }\n        loadConfigFromFile()\n    }\n\n    private func loadConfigFromFile() {\n        let fileManager = FileManager.default\n        if fileManager.fileExists(atPath: configFilePath) {\n            do {\n                config.configContent = try String(contentsOfFile: configFilePath, encoding: .utf8)\n            } catch {\n                config.configContent = generateDefaultConfig()\n            }\n        } else {\n            config.configContent = generateDefaultConfig()\n        }\n    }\n\n    private func saveConfigToFile() {\n        isSaving = true\n        showSaveSuccess = false\n\n        Task {\n            do {\n                let fileManager = FileManager.default\n\n                // 备份原文件\n                if fileManager.fileExists(atPath: configFilePath) {\n                    let backupPath = configFilePath + ";
")\n        }\n    }\n\n    /// 从文件加载令牌\n    private func loadToken() {\n        guard FileManager.default.fileExists(atPath: tokenStorageURL.path) else {\n            return\n        }\n\n        do {\n            let data = try Data(contentsOf: tokenStorageURL)\n            let token = try JSONDecoder().decode(AuthToken.self, from: data)\n\n            // 只加载未过期的令牌\n            if token.isValid {\n                currentToken = token\n            } else {\n                // 删除过期令牌\n                try? FileManager.default.removeItem(at: tokenStorageURL)\n            }\n        } catch {\n            print(" = ")\n        }\n    }\n\n    /// 从文件加载令牌\n    private func loadToken() {\n        guard FileManager.default.fileExists(atPath: tokenStorageURL.path) else {\n            return\n        }\n\n        do {\n            let data = try Data(contentsOf: tokenStorageURL)\n            let token = try JSONDecoder().decode(AuthToken.self, from: data)\n\n            // 只加载未过期的令牌\n            if token.isValid {\n                currentToken = token\n            } else {\n                // 删除过期令牌\n                try? FileManager.default.removeItem(at: tokenStorageURL)\n            }\n        } catch {\n            print(";
"\n        let workspaceIniPath = workspaceConfig.phpConfigFile(version: version)\n        try setupConfigSymlink(\n            source: brewIniPath,\n            workspace: workspaceIniPath,\n            fileManager: fm\n        )\n    }\n\n    /// 设置配置文件符号链接（Homebrew -> 工作空间）\n    private func setupConfigSymlink(source: String, workspace: String, fileManager fm: FileManager) throws {\n        // 源文件不存在则跳过\n        guard fm.fileExists(atPath: source) else { return }\n\n        // 检查源文件是否已经是符号链接\n        let attrs = try? fm.attributesOfItem(atPath: source)\n        let isSymlink = attrs?[.type] as? FileAttributeType == .typeSymbolicLink\n\n        if isSymlink {\n            // 已经是符号链接，检查是否指向工作空间\n            if let dest = try? fm.destinationOfSymbolicLink(atPath: source), dest == workspace {\n                return // 已正确配置\n            }\n        }\n\n        // 工作空间文件不存在，从源复制\n        if !fm.fileExists(atPath: workspace) {\n            try fm.copyItem(atPath: source, toPath: workspace)\n        }\n\n        // 备份并删除原文件，创建符号链接\n        let backupPath = source + " = "\n        let workspaceIniPath = workspaceConfig.phpConfigFile(version: version)\n        try setupConfigSymlink(\n            source: brewIniPath,\n            workspace: workspaceIniPath,\n            fileManager: fm\n        )\n    }\n\n    /// 设置配置文件符号链接（Homebrew -> 工作空间）\n    private func setupConfigSymlink(source: String, workspace: String, fileManager fm: FileManager) throws {\n        // 源文件不存在则跳过\n        guard fm.fileExists(atPath: source) else { return }\n\n        // 检查源文件是否已经是符号链接\n        let attrs = try? fm.attributesOfItem(atPath: source)\n        let isSymlink = attrs?[.type] as? FileAttributeType == .typeSymbolicLink\n\n        if isSymlink {\n            // 已经是符号链接，检查是否指向工作空间\n            if let dest = try? fm.destinationOfSymbolicLink(atPath: source), dest == workspace {\n                return // 已正确配置\n            }\n        }\n\n        // 工作空间文件不存在，从源复制\n        if !fm.fileExists(atPath: workspace) {\n            try fm.copyItem(atPath: source, toPath: workspace)\n        }\n\n        // 备份并删除原文件，创建符号链接\n        let backupPath = source + ";
"即将打开 \\(service.name) 的配置文件" = "即将打开 \\(service.name) 的配置文件";
"\n    \n    // UI状态\n    @State private var showPassword = false\n    @State private var isSubmitting = false\n    @State private var showError = false\n    @State private var errorMessage = " = "\n    \n    // UI状态\n    @State private var showPassword = false\n    @State private var isSubmitting = false\n    @State private var showError = false\n    @State private var errorMessage = ";
")\n\n        for section in configSections {\n            for item in section.items {\n                // 跳过空值\n                guard !item.value.isEmpty else { continue }\n\n                // 获取带单位的值\n                let valueWithUnit = getValueWithUnit(item)\n\n                // 更新或添加配置项，传递isEnabled状态\n                lines = updateConfigLine(lines: lines, key: item.key, value: valueWithUnit, isEnabled: item.isEnabled)\n            }\n        }\n\n        configContent = lines.joined(separator: " = ")\n\n        for section in configSections {\n            for item in section.items {\n                // 跳过空值\n                guard !item.value.isEmpty else { continue }\n\n                // 获取带单位的值\n                let valueWithUnit = getValueWithUnit(item)\n\n                // 更新或添加配置项，传递isEnabled状态\n                lines = updateConfigLine(lines: lines, key: item.key, value: valueWithUnit, isEnabled: item.isEnabled)\n            }\n        }\n\n        configContent = lines.joined(separator: ";
"\n\n        // 添加 phpMyAdmin 配置\n        if hasPMA {\n            config += " = "\n\n        // 添加 phpMyAdmin 配置\n        if hasPMA {\n            config += ";
" ? Color.white.opacity(0.2) : AppColors.border\n        }\n    }\n\n    var body: some View {\n        Button(action: {\n            if !isDisabled {\n                isSelected.toggle()\n            }\n        }) {\n            HStack(spacing: 12) {\n                // 图标\n                Text(icon)\n                    .font(.system(size: 18))\n                    .frame(width: 32, height: 32)\n                    .background(iconBackground)\n                    .cornerRadius(8)\n                    .opacity(isDisabled ? 0.5 : 1.0)\n\n                // 文字\n                VStack(alignment: .leading, spacing: 2) {\n                    Text(title)\n                        .font(.system(size: 13, weight: .medium))\n                        .foregroundColor(isDisabled ? .secondary : .primary)\n                    Text(isDisabled ? (disabledReason ?? subtitle) : subtitle)\n                        .font(.system(size: 11))\n                        .foregroundColor(isDisabled ? .red.opacity(AppTokens.Opacity.textSecondary) : .secondary)\n                }\n\n                Spacer()\n\n                // 选择圆圈\n                if !isDisabled {\n                    Image(systemName: isSelected ? " = " ? Color.white.opacity(0.2) : AppColors.border\n        }\n    }\n\n    var body: some View {\n        Button(action: {\n            if !isDisabled {\n                isSelected.toggle()\n            }\n        }) {\n            HStack(spacing: 12) {\n                // 图标\n                Text(icon)\n                    .font(.system(size: 18))\n                    .frame(width: 32, height: 32)\n                    .background(iconBackground)\n                    .cornerRadius(8)\n                    .opacity(isDisabled ? 0.5 : 1.0)\n\n                // 文字\n                VStack(alignment: .leading, spacing: 2) {\n                    Text(title)\n                        .font(.system(size: 13, weight: .medium))\n                        .foregroundColor(isDisabled ? .secondary : .primary)\n                    Text(isDisabled ? (disabledReason ?? subtitle) : subtitle)\n                        .font(.system(size: 11))\n                        .foregroundColor(isDisabled ? .red.opacity(AppTokens.Opacity.textSecondary) : .secondary)\n                }\n\n                Spacer()\n\n                // 选择圆圈\n                if !isDisabled {\n                    Image(systemName: isSelected ? ";
")\n        }\n        try content.write(toFile: path, atomically: true, encoding: .utf8)\n    }\n\n    // MARK: - Redis 端口修改\n\n    func changeRedisPort(_ software: SoftwareVersion, newPort: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(" = ")\n        }\n        try content.write(toFile: path, atomically: true, encoding: .utf8)\n    }\n\n    // MARK: - Modificación del puerto de Redis\n\n    func changeRedisPort(_ software: SoftwareVersion, newPort: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(";
" }\n\n    /// Nginx 主配置文件\n    var nginxConfigFile: String { " = " }\n\n    /// Archivo de configuración principal de Nginx\n    var nginxConfigFile: String { ";
"\n        var detail = output\n        var progress = 0.0\n\n        // 检测不同阶段\n        if lowercased.contains(" = "\n        var detail = output\n        var progress = 0.0\n\n        // Detectar distintas fases\n        if lowercased.contains(";
"版本: \\(log.version)" = "Versión: \\(log.version)";
")\n\n        // 加载证书数据\n        loadCertificates()\n    }\n\n    // MARK: - 数据持久化\n\n    /// 加载证书数据\n    func loadCertificates() {\n        // 先从 JSON 文件加载已保存的证书数据\n        if FileManager.default.fileExists(atPath: certificatesFileURL.path) {\n            do {\n                let data = try Data(contentsOf: certificatesFileURL)\n                certificates = try JSONDecoder().decode([Certificate].self, from: data)\n                print(" = ")\n\n        // Cargar datos de certificados\n        loadCertificates()\n    }\n\n    // MARK: - Persistencia de datos\n\n    /// Cargar los datos de los certificados\n    func loadCertificates() {\n        // Primero cargar los datos de certificados guardados desde el archivo JSON\n        if FileManager.default.fileExists(atPath: certificatesFileURL.path) {\n            do {\n                let data = try Data(contentsOf: certificatesFileURL)\n                certificates = try JSONDecoder().decode([Certificate].self, from: data)\n                print(";
"Web服务器类型: \\(webServer.rawValue)" = "Tipo de servidor web: \\(webServer.rawValue)";
")\n            }\n            \n            if let icon = loadedIcon {\n                // 将图标缩放到适合状态栏的大小 (18x18)\n                let newSize = NSSize(width: 18, height: 18)\n                let resizedIcon = NSImage(size: newSize)\n                resizedIcon.lockFocus()\n                icon.draw(in: NSRect(origin: .zero, size: newSize),\n                          from: NSRect(origin: .zero, size: icon.size),\n                          operation: .copy,\n                          fraction: 1.0)\n                resizedIcon.unlockFocus()\n                // 如果想要在深色/浅色模式下自动变成纯色轮廓，可以设置 isTemplate = true\n                // resizedIcon.isTemplate = true \n                button.image = resizedIcon\n            } else {\n                button.image = NSImage(systemSymbolName: " = ")\n            }\n            \n            if let icon = loadedIcon {\n                // Redimensionar el icono para que se ajuste al tamaño de la barra de estado (18x18)\n                let newSize = NSSize(width: 18, height: 18)\n                let resizedIcon = NSImage(size: newSize)\n                resizedIcon.lockFocus()\n                icon.draw(in: NSRect(origin: .zero, size: newSize),\n                          from: NSRect(origin: .zero, size: icon.size),\n                          operation: .copy,\n                          fraction: 1.0)\n                resizedIcon.unlockFocus()\n                // Si desea que el icono se convierta automáticamente en un contorno monocromático en modo oscuro/claro, puede establecer isTemplate = true\n                // resizedIcon.isTemplate = true \n                button.image = resizedIcon\n            } else {\n                button.image = NSImage(systemSymbolName: ";
"]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = Pipe()\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            guard let output = String(data: data, encoding: .utf8),\n                  !output.isEmpty else { return false }\n\n            let lines = output.components(separatedBy: .newlines)\n                .filter { !$0.isEmpty }\n            return lines.count > 1 // 第一行是标题\n        } catch {\n            return false\n        }\n    }\n\n    /// 获取软件对应的配置文件路径列表（在主线程调用，预计算路径）\n    private func getConfigPathsForVersion(_ version: SoftwareVersion) -> [String] {\n        let name = version.name.lowercased()\n        let pathProvider = ConfigPathProvider.shared\n\n        if name.contains(" = "]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = Pipe()\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            guard let output = String(data: data, encoding: .utf8),\n                  !output.isEmpty else { return false }\n\n            let lines = output.components(separatedBy: .newlines)\n                .filter { !$0.isEmpty }\n            return lines.count > 1 // La primera línea es el encabezado\n        } catch {\n            return false\n        }\n    }\n\n    /// Obtener la lista de rutas de archivos de configuración correspondientes al software (llamado en el hilo principal, cálculo previo de rutas)\n    private func getConfigPathsForVersion(_ version: SoftwareVersion) -> [String] {\n        let name = version.name.lowercased()\n        let pathProvider = ConfigPathProvider.shared\n\n        if name.contains(";
"FTP 账号和数据库自动创建将在后续版本开放，当前版本先确保站点主链路稳定可用。" = "La creación automática de cuentas FTP y bases de datos estará disponible en futuras versiones; en esta versión, primero aseguramos la estabilidad y disponibilidad de la cadena principal del sitio.";
"\n    }\n\n    /// PHP 日志目录\n    var phpLogsPath: String { " = "\n    }\n\n    /// Directorio de registros de PHP\n    var phpLogsPath: String { ";
"查询失败: \\(msg)" = "Consulta fallida: \\(msg)";
"  // 用户密码\n    var userHost: String = " = "  // Contraseña del usuario\n    var userHost: String = ";
")\n            return\n        }\n        \n        // 如果目标文件已存在，先删除\n        if fileManager.fileExists(atPath: targetIndexFile) {\n            try fileManager.removeItem(atPath: targetIndexFile)\n        }\n        \n        // 复制文件\n        do {\n            try fileManager.copyItem(atPath: errorPageIndexFile, toPath: targetIndexFile)\n            print(" = ")\n            return\n        }\n        \n        // Si el archivo de destino ya existe, eliminarlo primero\n        if fileManager.fileExists(atPath: targetIndexFile) {\n            try fileManager.removeItem(atPath: targetIndexFile)\n        }\n        \n        // Copiar el archivo\n        do {\n            try fileManager.copyItem(atPath: errorPageIndexFile, toPath: targetIndexFile)\n            print(";
"恢复失败: \\(error.localizedDescription)" = "Restauración fallida: \\(error.localizedDescription)";
")\n                // 如果保存失败，尝试恢复备份\n                if let backupURL = self.backupURL, FileManager.default.fileExists(atPath: backupURL.path) {\n                    try? FileManager.default.copyItem(at: backupURL, to: self.fileURL)\n                }\n            }\n        }\n    }\n    \n    /// 强制保存（立即执行，不防抖）\n    func forceSave(_ items: [T]) async throws {\n        // 取消防抖任务\n        saveTask?.cancel()\n        saveTask = nil\n        \n        // 立即保存\n        try await saveImmediately(items)\n    }\n    \n    /// 验证数据完整性\n    func validate(_ items: [T]) -> Bool {\n        // 基本验证：检查数组不为nil\n        // 可以在这里添加更多验证逻辑\n        return true\n    }\n    \n    /// 恢复备份\n    func restoreBackup() throws {\n        guard let backupURL = backupURL,\n              FileManager.default.fileExists(atPath: backupURL.path) else {\n            throw NSError(domain: " = ")\n                // Si falla la grabación, intentar restaurar la copia de seguridad\n                if let backupURL = self.backupURL, FileManager.default.fileExists(atPath: backupURL.path) {\n                    try? FileManager.default.copyItem(at: backupURL, to: self.fileURL)\n                }\n            }\n        }\n    }\n    \n    /// Guardado forzoso (ejecución inmediata, sin retardo)\n    func forceSave(_ items: [T]) async throws {\n        // Cancelar la tarea de retardo\n        saveTask?.cancel()\n        saveTask = nil\n        \n        // Guardar inmediatamente\n        try await saveImmediately(items)\n    }\n    \n    /// Validar la integridad de los datos\n    func validate(_ items: [T]) -> Bool {\n        // Validación básica: comprobar que el arreglo no sea nulo\n        // Aquí se pueden agregar más lógicas de validación\n        return true\n    }\n    \n    /// Restaurar copia de seguridad\n    func restoreBackup() throws {\n        guard let backupURL = backupURL,\n              FileManager.default.fileExists(atPath: backupURL.path) else {\n            throw NSError(domain: ";
")\n        }\n    }\n\n    // 首选 Web 服务器 (nginx / apache)\n    @Published var preferredWebServer: String {\n        didSet {\n            UserDefaults.standard.set(preferredWebServer, forKey: " = ")\n        }\n    }\n\n    // Servidor web preferido (nginx / apache)\n    @Published var preferredWebServer: String {\n        didSet {\n            UserDefaults.standard.set(preferredWebServer, forKey: ";
" {\n            return true\n        }\n\n        // 检查域名\n        let domains = config.parsed.domains.map { $0.lowercased() }\n        if domains.contains(" = " {\n            return true\n        }\n\n        // Verificar dominio\n        let domains = config.parsed.domains.map { $0.lowercased() }\n        if domains.contains(";
") {\n        _selectedVersion = State(initialValue: phpVersion)\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            headerToolbar\n            Divider()\n\n            if extensionManager.isLoading {\n                loadingView\n            } else {\n                extensionListView\n            }\n\n            Divider()\n            footerNotes\n        }\n        .onAppear {\n            loadInstalledVersions()\n            Task {\n                await extensionManager.loadExtensions(for: selectedVersion)\n            }\n        }\n        .onChange(of: selectedVersion) { newVersion in\n            Task {\n                await extensionManager.loadExtensions(for: newVersion)\n            }\n        }\n    }\n\n    // MARK: - 顶部工具栏\n    private var headerToolbar: some View {\n        HStack(spacing: 12) {\n            // PHP 版本选择\n            HStack(spacing: 6) {\n                Text(" = ") {\n        _selectedVersion = State(initialValue: phpVersion)\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            headerToolbar\n            Divider()\n\n            if extensionManager.isLoading {\n                loadingView\n            } else {\n                extensionListView\n            }\n\n            Divider()\n            footerNotes\n        }\n        .onAppear {\n            loadInstalledVersions()\n            Task {\n                await extensionManager.loadExtensions(for: selectedVersion)\n            }\n        }\n        .onChange(of: selectedVersion) { newVersion in\n            Task {\n                await extensionManager.loadExtensions(for: newVersion)\n            }\n        }\n    }\n\n    // MARK: - Barra de herramientas superior\n    private var headerToolbar: some View {\n        HStack(spacing: 12) {\n            // Selección de versión de PHP\n            HStack(spacing: 6) {\n                Text(";
")\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 100, alignment: .leading)\n\n            // 数据库位置\n            HStack(spacing: 4) {\n                Image(systemName: " = ")\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 100, alignment: .leading)\n\n            // Ubicación de la base de datos\n            HStack(spacing: 4) {\n                Image(systemName: ";
") {\n                                    if let regex = regex,\n                                       let match = regex.firstMatch(in: line, options: [], range: NSRange(location: 0, length: line.utf16.count)),\n                                       match.numberOfRanges > 1,\n                                       let portRange = Range(match.range(at: 1), in: line),\n                                       let port = Int(String(line[portRange])) {\n                                        mysqlPorts.append(port)\n                                    }\n                                }\n                            }\n\n                            // 优先返回 3306 端口（标准 MySQL 端口）\n                            // 排除 33060（MySQL X Protocol 端口）\n                            if mysqlPorts.contains(3306) {\n                                continuation.resume(returning: 3306)\n                                return\n                            }\n\n                            // 如果没有 3306，返回第一个非 33060 的端口\n                            if let port = mysqlPorts.first(where: { $0 != 33060 }) {\n                                continuation.resume(returning: port)\n                                return\n                            }\n\n                            // 如果只有 33060，返回默认 3306\n                            if !mysqlPorts.isEmpty {\n                                continuation.resume(returning: 3306)\n                                return\n                            }\n                        }\n                    }\n\n                    // 如果都失败，返回默认端口 3306\n                    continuation.resume(returning: 3306)\n                } catch {\n                    continuation.resume(returning: 3306)\n                }\n            }\n        }\n    }\n    \n    // 复制端口到剪贴板\n    private func copyPortToClipboard() {\n        let port = activeConnectionInfo.port\n        NSPasteboard.general.clearContents()\n        NSPasteboard.general.setString(" = ") {\n                                    if let regex = regex,\n                                       let match = regex.firstMatch(in: line, options: [], range: NSRange(location: 0, length: line.utf16.count)),\n                                       match.numberOfRanges > 1,\n                                       let portRange = Range(match.range(at: 1), in: line),\n                                       let port = Int(String(line[portRange])) {\n                                        mysqlPorts.append(port)\n                                    }\n                                }\n                            }\n\n                            // Priorizar el puerto 3306 (puerto estándar de MySQL)\n                            // Excluir el puerto 33060 (puerto de MySQL X Protocol)\n                            if mysqlPorts.contains(3306) {\n                                continuation.resume(returning: 3306)\n                                return\n                            }\n\n                            // Si no hay 3306, devolver el primer puerto distinto de 33060\n                            if let port = mysqlPorts.first(where: { $0 != 33060 }) {\n                                continuation.resume(returning: port)\n                                return\n                            }\n\n                            // Si solo hay 33060, devolver el valor predeterminado 3306\n                            if !mysqlPorts.isEmpty {\n                                continuation.resume(returning: 3306)\n                                return\n                            }\n                        }\n                    }\n\n                    // Si todo falla, devolver el puerto predeterminado 3306\n                    continuation.resume(returning: 3306)\n                } catch {\n                    continuation.resume(returning: 3306)\n                }\n            }\n        }\n    }\n    \n    // Copiar puerto al portapapeles\n    private func copyPortToClipboard() {\n        let port = activeConnectionInfo.port\n        NSPasteboard.general.clearContents()\n        NSPasteboard.general.setString(";
")\n            }\n            .buttonStyle(.bordered)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 10)\n        .background(Color(NSColor.controlBackgroundColor).opacity(AppTokens.Opacity.disabled))\n    }\n\n    // MARK: - 加载视图\n    private var loadingView: some View {\n        VStack(spacing: 16) {\n            ProgressView()\n                .scaleEffect(1.2)\n            Text(" = ")\n            }\n            .buttonStyle(.bordered)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 10)\n        .background(Color(NSColor.controlBackgroundColor).opacity(AppTokens.Opacity.disabled))\n    }\n\n    // MARK: - Vista de carga\n    private var loadingView: some View {\n        VStack(spacing: 16) {\n            ProgressView()\n                .scaleEffect(1.2)\n            Text(";
", at: arguments.count - 1)\n                }\n                \n                process.arguments = arguments\n                \n                let errorPipe = Pipe()\n                process.standardError = errorPipe\n                process.standardOutput = Pipe()\n                \n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    \n                    if process.terminationStatus == 0 {\n                        continuation.resume(returning: true)\n                    } else {\n                        continuation.resume(returning: false)\n                    }\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n    \n    // 检测MySQL实际监听的端口\n    // 说明：\n    // - 原来这里通过 mysqlServiceInfo.isRunning 做条件判断，但 mysqlServiceInfo 自身又依赖 actualMySQLPort，\n    //   导致形成「先有鸡还是先有蛋」的循环依赖，端口检测永远不会真正执行，界面一直显示“检测中… / 已停止”\n    // - 现在只根据是否“已安装”来决定要不要检测端口，端口检测本身用 lsof/netstat 来判断是否真正在监听\n    private func detectMySQLPort() {\n        // 如果连 MySQL 客户端都不存在，就没必要检测端口\n        guard mysqlServiceInfo.installed else {\n            actualMySQLPort = nil\n            return\n        }\n        \n        isDetectingPort = true\n        \n        Task {\n            let port = await getMySQLListeningPort()\n            await MainActor.run {\n                actualMySQLPort = port\n                isDetectingPort = false\n            }\n        }\n    }\n    \n    // 获取MySQL实际监听的端口\n    private func getMySQLListeningPort() async -> Int? {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                // 使用 lsof 命令查找 MySQL 监听的端口\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: " = ", en: arguments.count - 1)\n                }\n                \n                process.arguments = arguments\n                \n                let errorPipe = Pipe()\n                process.standardError = errorPipe\n                process.standardOutput = Pipe()\n                \n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    \n                    if process.terminationStatus == 0 {\n                        continuation.resume(returning: true)\n                    } else {\n                        continuation.resume(returning: false)\n                    }\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n    \n    // Detectar el puerto real en el que escucha MySQL\n    // Nota:\n    // - Originalmente aquí se usaba mysqlServiceInfo.isRunning como condición, pero mysqlServiceInfo depende a su vez de actualMySQLPort,\n    //   lo que genera una dependencia circular del tipo «¿qué fue primero, la gallina o el huevo?», impidiendo que nunca se ejecute realmente la detección del puerto y mostrando continuamente «Detectando… / Detenido» en la interfaz.\n    // - Ahora solo se decide si detectar el puerto según si «está instalado»; la detección del puerto en sí se realiza mediante lsof/netstat para verificar si realmente está escuchando.\n    private func detectMySQLPort() {\n        // Si ni siquiera existe el cliente MySQL, no tiene sentido detectar el puerto.\n        guard mysqlServiceInfo.installed else {\n            actualMySQLPort = nil\n            return\n        }\n        \n        isDetectingPort = true\n        \n        Task {\n            let port = await getMySQLListeningPort()\n            await MainActor.run {\n                actualMySQLPort = port\n                isDetectingPort = false\n            }\n        }\n    }\n    \n    // Obtener el puerto real en el que escucha MySQL\n    private func getMySQLListeningPort() async -> Int? {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                // Usar el comando lsof para buscar el puerto en el que escucha MySQL\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: ";
"端口 \\(port) 已被运行中的 \\(conflictSoftware) 占用" = "El puerto \\(port) ya está ocupado por \\(conflictSoftware), que se encuentra en ejecución";
"\n        }\n    }\n\n    // 更新配置行\n    private func updateConfigLine(lines: [String], key: String, value: String, isEnabled: Bool = true) -> [String] {\n        var result = lines\n        var found = false\n        var inMysqldSection = false\n        var mysqldSectionEndIndex = -1\n        let commentPrefix = getCommentPrefix()\n\n        for i in 0..<result.count {\n            let trimmed = result[i].trimmingCharacters(in: .whitespaces)\n\n            // 检测 [mysqld] 段（MySQL 配置）\n            if trimmed.lowercased() == " = "\n        }\n    }\n\n    // Actualizar una línea de configuración\n    private func updateConfigLine(lines: [String], key: String, value: String, isEnabled: Bool = true) -> [String] {\n        var result = lines\n        var found = false\n        var inMysqldSection = false\n        var mysqldSectionEndIndex = -1\n        let commentPrefix = getCommentPrefix()\n\n        for i in 0..<result.count {\n            let trimmed = result[i].trimmingCharacters(in: .whitespaces)\n\n            // Detectar la sección [mysqld] (configuración de MySQL)\n            if trimmed.lowercased() == ";
" combined\n                </VirtualHost>\n\n                # HTTPS VirtualHost\n                # Apache 后端端口：\\(httpsPort)\n                <VirtualHost *:\\(httpsPort)>\n                    ServerName \\(domains.first ?? " = " combinado\n                </VirtualHost>\n\n                # VirtualHost HTTPS\n                # Puerto de backend de Apache: \\(httpsPort)\n                <VirtualHost *:\\(httpsPort)>\n                    ServerName \\(domains.first ?? ";
"(端口 \\(port))" = "(puerto \\(port))";
"\n\n    // PHP版本信息结构\n    struct PHPVersionInfo: Identifiable {\n        let id: String\n        let version: String\n        let isRunning: Bool\n    }\n\n    // 获取已安装的PHP版本列表（包含运行状态）\n    private var installedPHPVersions: [PHPVersionInfo] {\n        return softwareManager.phpVersions\n            .filter { $0.installed }\n            .map { PHPVersionInfo(id: $0.id, version: $0.version, isRunning: $0.isRunning) }\n            .sorted { $0.version > $1.version }  // 降序排列，新版本在前\n    }\n\n    // 检查指定PHP版本是否正在运行\n    private func isPHPVersionRunning(_ version: String) -> Bool {\n        return softwareManager.phpVersions\n            .first { $0.version == version && $0.installed }?\n            .isRunning ?? false\n    }\n\n    // 获取已安装的Web服务器列表\n    private var installedWebServers: [WebServerType] {\n        var servers: [WebServerType] = []\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if name == " = "\n\n    // Estructura de información sobre versiones de PHP\n    struct PHPVersionInfo: Identifiable {\n        let id: String\n        let version: String\n        let isRunning: Bool\n    }\n\n    // Obtener la lista de versiones de PHP instaladas (incluido su estado de ejecución)\n    private var installedPHPVersions: [PHPVersionInfo] {\n        return softwareManager.phpVersions\n            .filter { $0.installed }\n            .map { PHPVersionInfo(id: $0.id, version: $0.version, isRunning: $0.isRunning) }\n            .sorted { $0.version > $1.version }  // Orden descendente: las versiones más recientes primero\n    }\n\n    // Verificar si una versión específica de PHP está en ejecución\n    private func isPHPVersionRunning(_ version: String) -> Bool {\n        return softwareManager.phpVersions\n            .first { $0.version == version && $0.installed }?\n            .isRunning ?? false\n    }\n\n    // Obtener la lista de servidores web instalados\n    private var installedWebServers: [WebServerType] {\n        var servers: [WebServerType] = []\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if name == ";
")))\n                        }\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                    }\n                    \n                    // SSL状态\n                    GroupBox(" = ")))\n                        }\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                    }\n                    \n                    // Estado SSL\n                    GroupBox(";
"复制自: \\(site.name)" = "Copia de: \\(site.name)";
")\n                        .font(.system(size: 12))\n                        .foregroundColor(tab.isLocal ? .blue : .orange)\n                }\n            }\n\n            Spacer()\n        }\n        .padding(.horizontal, 20)\n        .padding(.vertical, 12)\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // 搜索栏\n    private var searchBar: some View {\n        HStack {\n            Image(systemName: " = ")\n                        .font(.system(size: 12))\n                        .foregroundColor(tab.isLocal ? .blue : .orange)\n                }\n            }\n\n            Spacer()\n        }\n        .padding(.horizontal, 20)\n        .padding(.vertical, 12)\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // Barra de búsqueda\n    private var searchBar: some View {\n        HStack {\n            Image(systemName: ";
"\n    }\n}\n\n// MARK: - 错误类型\n\nenum WorkspaceError: LocalizedError {\n    case notConfigured\n    case pathIsNotDirectory\n    case noWritePermission\n    case cannotCreateDirectory\n    case initializationFailed\n\n    var errorDescription: String? {\n        switch self {\n        case .notConfigured:\n            return " = "\n    }\n}\n\n// MARK: - Tipos de error\n\nenum WorkspaceError: LocalizedError {\n    case notConfigured\n    case pathIsNotDirectory\n    case noWritePermission\n    case cannotCreateDirectory\n    case initializationFailed\n\n    var errorDescription: String? {\n        switch self {\n        case .notConfigured:\n            return ";
" && !software.installed {\n            await MainActor.run {\n                showNginxInstallSheet = true\n            }\n            return\n        }\n\n        // Apache 特殊处理：显示安装选项\n        if software.id == " = " && !software.installed {\n            await MainActor.run {\n                showNginxInstallSheet = true\n            }\n            return\n        }\n\n        // Tratamiento especial para Apache: mostrar opciones de instalación\n        if software.id == ";
"准备安装 \\(software.name)..." = "Preparando la instalación de \\(software.name)...";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // MARK: - 表单内容\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 24) {\n                // 检查 acme.sh 安装状态\n                if isCheckingAcme {\n                    acmeCheckingView\n                } else if !isAcmeInstalled {\n                    acmeInstallationView\n                } else {\n                    // 重要提示\n                    importantNotice\n\n                    // 站点选择\n                    siteSelectionSection\n\n                    // 域名验证部分\n                    if selectedSite != nil {\n                        domainVerificationSection\n                    }\n\n                    // 证书申请部分（仅在验证成功后显示）\n                    if case .reachable = verificationStatus {\n                        certificateApplicationSection\n                    }\n                }\n            }\n            .padding()\n        }\n    }\n\n    // MARK: - 重要提示\n    private var importantNotice: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            HStack {\n                Image(systemName: " = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // MARK: - Contenido del formulario\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 24) {\n                // Verificar el estado de instalación de acme.sh\n                if isCheckingAcme {\n                    acmeCheckingView\n                } else if !isAcmeInstalled {\n                    acmeInstallationView\n                } else {\n                    // Aviso importante\n                    importantNotice\n\n                    // Selección del sitio\n                    siteSelectionSection\n\n                    // Sección de verificación de dominios\n                    if selectedSite != nil {\n                        domainVerificationSection\n                    }\n\n                    // Sección de solicitud de certificado (solo se muestra tras una verificación exitosa)\n                    if case .reachable = verificationStatus {\n                        certificateApplicationSection\n                    }\n                }\n            }\n            .padding()\n        }\n    }\n\n    // MARK: - Aviso importante\n    private var importantNotice: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            HStack {\n                Image(systemName: ";
"\n\n        // 1. 创建工作空间的 servers 目录（保持与 Homebrew 一致）\n        if !fm.fileExists(atPath: wsServersDir) {\n            try fm.createDirectory(atPath: wsServersDir, withIntermediateDirectories: true)\n        }\n\n        // 2. 复制 Homebrew 的配置文件到工作空间（如果工作空间还没有）\n        if !fm.fileExists(atPath: wsNginxConf) && fm.fileExists(atPath: brewNginxConf) {\n            try fm.copyItem(atPath: brewNginxConf, toPath: wsNginxConf)\n            // 删除主配置中的示例 server 块，避免与 default.conf 冲突\n            try removeExampleServerBlock(at: wsNginxConf)\n        }\n\n        if !fm.fileExists(atPath: wsDefaultConf) && fm.fileExists(atPath: brewDefaultConf) {\n            try fm.copyItem(atPath: brewDefaultConf, toPath: wsDefaultConf)\n        }\n\n        // 2.1. 复制 Nginx 必需的支持文件（mime.types, fastcgi_params 等）\n        let supportFiles = [\n            " = "\n\n        // 1. Crear el directorio 'servers' del espacio de trabajo (manteniendo coherencia con Homebrew)\n        if !fm.fileExists(atPath: wsServersDir) {\n            try fm.createDirectory(atPath: wsServersDir, withIntermediateDirectories: true)\n        }\n\n        // 2. Copiar los archivos de configuración de Homebrew al espacio de trabajo (si aún no existen allí)\n        if !fm.fileExists(atPath: wsNginxConf) && fm.fileExists(atPath: brewNginxConf) {\n            try fm.copyItem(atPath: brewNginxConf, toPath: wsNginxConf)\n            // Eliminar el bloque de ejemplo 'server' de la configuración principal para evitar conflictos con default.conf\n            try removeExampleServerBlock(at: wsNginxConf)\n        }\n\n        if !fm.fileExists(atPath: wsDefaultConf) && fm.fileExists(atPath: brewDefaultConf) {\n            try fm.copyItem(atPath: brewDefaultConf, toPath: wsDefaultConf)\n        }\n\n        // 2.1. Copiar los archivos de soporte necesarios de Nginx (mime.types, fastcgi_params, etc.)\n        let supportFiles = [\n            ";
"证书即将过期（\\(remainingDays)天）" = "El certificado caducará pronto (en \\(remainingDays) días)";
"\n                # HTTP VirtualHost - 重定向到 HTTPS\n                # Apache 后端端口：\\(httpPort)\n                <VirtualHost *:\\(httpPort)>\n                    ServerName \\(domains.first ?? " = "\n                # VirtualHost HTTP: redirección a HTTPS\n                # Puerto de backend de Apache: \\(httpPort)\n                <VirtualHost *:\\(httpPort)>\n                    ServerName \\(domains.first ?? ";
"默认: \\(item.defaultValue)" = "Predeterminado: \\(item.defaultValue)";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 内容区域\n    private var contentArea: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 16) {\n                // 说明信息\n                infoCard\n\n                // 站点列表\n                sitesList\n            }\n            .padding()\n        }\n    }\n\n    // 说明信息卡片\n    private var infoCard: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            HStack {\n                Image(systemName: " = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // Área de contenido\n    private var contentArea: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 16) {\n                // Información explicativa\n                infoCard\n\n                // Lista de sitios\n                sitesList\n            }\n            .padding()\n        }\n    }\n\n    // Tarjeta de información explicativa\n    private var infoCard: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            HStack {\n                Image(systemName: ";
"\n\n                // 检查证书是否已经在列表中\n                if certificates.contains(where: { $0.certificatePath == certPath }) {\n                    continue\n                }\n\n                // 读取证书信息\n                if let certInfo = parseCertificateInfo(certPath: certPath) {\n                    let certificate = Certificate(\n                        domains: certInfo.domains,\n                        certificateType: .ca,\n                        certificatePath: certPath,\n                        privateKeyPath: keyPath,\n                        issuer: certInfo.issuer,\n                        validFrom: certInfo.validFrom,\n                        validUntil: certInfo.validUntil\n                    )\n                    certificates.append(certificate)\n                    print(" = "\n\n                // Verificar si el certificado ya está en la lista\n                if certificates.contains(where: { $0.certificatePath == certPath }) {\n                    continue\n                }\n\n                // Leer la información del certificado\n                if let certInfo = parseCertificateInfo(certPath: certPath) {\n                    let certificate = Certificate(\n                        domains: certInfo.domains,\n                        certificateType: .ca,\n                        certificatePath: certPath,\n                        privateKeyPath: keyPath,\n                        issuer: certInfo.issuer,\n                        validFrom: certInfo.validFrom,\n                        validUntil: certInfo.validUntil\n                    )\n                    certificates.append(certificate)\n                    print(";
"\n        }\n    }\n\n    // 获取配置文件路径（优先使用工作空间路径）\n    private func getConfigPathWithWorkspace() -> String {\n        let version = extractMajorMinorVersion(service.version)\n\n        switch service.name {\n        case " = "\n        }\n    }\n\n    // Obtener la ruta del archivo de configuración (priorizando la ruta del espacio de trabajo)\n    private func getConfigPathWithWorkspace() -> String {\n        let version = extractMajorMinorVersion(service.version)\n\n        switch service.name {\n        case ";
"\n        ),\n\n        // ===== 邮件服务 =====\n        " = "\n        ),\n\n        // ===== Servicio de correo electrónico =====\n        ";
")\n                    .frame(width: 80, alignment: .trailing)\n                Text(databaseName)\n                    .foregroundColor(.secondary)\n                Spacer()\n            }\n\n            // 选择文件按钮\n            HStack {\n                Text(" = ")\n                    .frame(width: 80, alignment: .trailing)\n                Text(databaseName)\n                    .foregroundColor(.secondary)\n                Spacer()\n            }\n\n            // Botón para seleccionar archivo\n            HStack {\n                Text(";
"\n    }\n\n    // 判断服务是否支持端口配置\n    private var supportsPortConfig: Bool {\n        [" = "\n    }\n\n    // Determinar si el servicio admite la configuración de puertos\n    private var supportsPortConfig: Bool {\n        [";
"确定要从备份 \\(backup.name) 恢复数据库吗？当前数据将被覆盖！" = "¿Desea restaurar la base de datos desde la copia de seguridad \\(backup.name)? Los datos actuales se sobrescribirán.";
")\n            mkcertStatus = .installed\n            return\n        }\n\n        // 检查系统安装的 mkcert\n        let isInstalled = await isCommandAvailable(" = ")\n            mkcertStatus = .installed\n            return\n        }\n\n        // Verificar la instalación del sistema de mkcert\n        let isInstalled = await isCommandAvailable(";
"]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            guard let output = String(data: data, encoding: .utf8) else {\n                return nil\n            }\n\n            // 解析输出\n            var domains: [String] = []\n            var issuer = " = "]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            guard let output = String(data: data, encoding: .utf8) else {\n                return nil\n            }\n\n            // Analizar la salida\n            var domains: [String] = []\n            var issuer = ";
")\n        }\n    }\n\n    // 导入数据库\n    func importDatabase(_ database: Database, from filePath: String, progressCallback: @escaping (Double) -> Void) async throws {\n        // 使用 mysql 命令导入\n        let success = await performMySQLImport(databaseName: database.name, from: filePath, progressCallback: progressCallback)\n        if !success {\n            throw DatabaseError.operationFailed(" = ")\n        }\n    }\n\n    // Importar base de datos\n    func importDatabase(_ database: Database, from filePath: String, progressCallback: @escaping (Double) -> Void) async throws {\n        // Importar mediante el comando mysql\n        let success = await performMySQLImport(databaseName: database.name, from: filePath, progressCallback: progressCallback)\n        if !success {\n            throw DatabaseError.operationFailed(";
"\n        }\n    }\n\n    var iconColor: Color {\n        switch self {\n        case .nginx: return .green\n        case .apache: return .red\n        }\n    }\n\n    var defaultPort: Int {\n        switch self {\n        case .nginx: return 80\n        case .apache: return 8080\n        }\n    }\n}\n\n// 安装模式枚举（通用）\nenum WebServerInstallMode: String, CaseIterable {\n    case takeOver = " = "\n        }\n    }\n\n    var iconColor: Color {\n        switch self {\n        case .nginx: return .green\n        case .apache: return .red\n        }\n    }\n\n    var defaultPort: Int {\n        switch self {\n        case .nginx: return 80\n        case .apache: return 8080\n        }\n    }\n}\n\n// Enumeración del modo de instalación del servidor web (genérica)\nenum WebServerInstallMode: String, CaseIterable {\n    case takeOver = ";
"\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 表单内容\n            formContent\n\n            Divider()\n\n            // 底部按钮\n            bottomButtons\n        }\n        .frame(width: 600, height: 400)\n        .alert(" = "\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Barra de título\n            headerBar\n\n            Divider()\n\n            // Contenido del formulario\n            formContent\n\n            Divider()\n\n            // Botones inferiores\n            bottomButtons\n        }\n        .frame(width: 600, height: 400)\n        .alert(";
")\n                .font(.headline)\n\n            // 简单的柱状图表示（实际应该使用 Charts 框架）\n            HStack(alignment: .bottom, spacing: 8) {\n                ForEach(statistics.hourlyTraffic, id: \\.hour) { data in\n                    VStack {\n                        Rectangle()\n                            .fill(Color.blue)\n                            .frame(width: 30, height: CGFloat(data.visits) * 2)\n                        Text(" = ")\n                .font(.headline)\n\n            // Gráfico de barras simple (en la práctica debería usarse el marco Charts)\n            HStack(alignment: .bottom, spacing: 8) {\n                ForEach(statistics.hourlyTraffic, id: \\.hour) { data in\n                    VStack {\n                        Rectangle()\n                            .fill(Color.blue)\n                            .frame(width: 30, height: CGFloat(data.visits) * 2)\n                        Text(";
"Nginx + Apache 配置正常" = "Configuración de Nginx + Apache correcta";
", nil)\n        }\n    }\n\n    /// 加载 Nginx 端口数据\n    private func loadNginxPortData() -> (port: String, error: String?) {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n\n        // 检查工作空间配置\n        if let config = WorkspaceManager.shared.config, config.isInitialized {\n            let defaultConfPath = " = ", nil)\n        }\n    }\n\n    /// Cargar los datos del puerto de Nginx\n    private func loadNginxPortData() -> (port: String, error: String?) {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n\n        // Verificar la configuración del espacio de trabajo\n        if let config = WorkspaceManager.shared.config, config.isInitialized {\n            let defaultConfPath = ";
" }\n                .sorted { url1, url2 in\n                    let date1 = (try? url1.resourceValues(forKeys: [.creationDateKey]).creationDate) ?? Date.distantPast\n                    let date2 = (try? url2.resourceValues(forKeys: [.creationDateKey]).creationDate) ?? Date.distantPast\n                    return date1 > date2\n                }\n        } catch {\n            return []\n        }\n    }\n\n    /// 备份数据库\n    func backupDatabase(name: String) async -> (success: Bool, filePath: String?, error: String?) {\n        let backupDir = getBackupDirectory()\n        let timestamp = ISO8601DateFormatter().string(from: Date()).replacingOccurrences(of: " = " }\n                .sorted { url1, url2 in\n                    let date1 = (try? url1.resourceValues(forKeys: [.creationDateKey]).creationDate) ?? Date.distantPast\n                    let date2 = (try? url2.resourceValues(forKeys: [.creationDateKey]).creationDate) ?? Date.distantPast\n                    return date1 > date2\n                }\n        } catch {\n            return []\n        }\n    }\n\n    /// Realizar copia de seguridad de la base de datos\n    func backupDatabase(name: String) async -> (success: Bool, filePath: String?, error: String?) {\n        let backupDir = getBackupDirectory()\n        let timestamp = ISO8601DateFormatter().string(from: Date()).replacingOccurrences(of: ";
")\n                .foregroundColor(.gray)\n                .font(.system(size: 16))\n\n            // 检查项图标和名称\n            Label(type.rawValue, systemImage: type.icon)\n                .font(.subheadline)\n\n            Spacer()\n\n            // 待检测状态\n            Text(" = ")\n                .foregroundColor(.gray)\n                .font(.system(size: 16))\n\n            // Icono y nombre del elemento a comprobar\n            Label(type.rawValue, systemImage: type.icon)\n                .font(.subheadline)\n\n            Spacer()\n\n            // Estado pendiente de verificación\n            Text(";
"\n\n    // 可用的数据库版本（从服务状态获取）\n    private let availableVersions = [\n        DatabaseVersion(version: " = "\n\n    // Versiones disponibles de bases de datos (obtenidas del estado del servicio)\n    private let availableVersions = [\n        DatabaseVersion(version: ";
"建议端口: \\(suggestedPort)" = "Puerto recomendado: \\(suggestedPort)";
")\n            for item in section.items {\n                // 使用动态建议值（会根据工作空间配置）\n                let value = ServiceConfigDefinitions.getDynamicSuggestedValue(\n                    for: item.key,\n                    serviceName: service.name,\n                    version: service.version\n                ) ?? item.defaultValue\n\n                // 根据服务类型生成不同格式的配置行\n                let configLine = formatConfigLine(key: item.key, value: value)\n                lines.append(configLine)\n            }\n            lines.append(" = ")\n            for item in section.items {\n                // Usar valores sugeridos dinámicos (se ajustan según la configuración del espacio de trabajo)\n                let value = ServiceConfigDefinitions.getDynamicSuggestedValue(\n                    for: item.key,\n                    serviceName: service.name,\n                    version: service.version\n                ) ?? item.defaultValue\n\n                // Generar líneas de configuración en distintos formatos según el tipo de servicio\n                let configLine = formatConfigLine(key: item.key, value: value)\n                lines.append(configLine)\n            }\n            lines.append(";
"\n        }\n\n        // 记录是否正在运行\n        let wasRunning = software.isRunning\n\n        // 如果正在运行，先停止服务\n        if wasRunning {\n            let brewName = software.brewName ?? software.id\n            let stopSuccess = await runBrewCommand(brewPath, arguments: [" = "\n        }\n\n        // Registrar si estaba en ejecución\n        let wasRunning = software.isRunning\n\n        // Si estaba en ejecución, detener primero el servicio\n        if wasRunning {\n            let brewName = software.brewName ?? software.id\n            let stopSuccess = await runBrewCommand(brewPath, arguments: [";
" ? Color.white.opacity(0.2) : AppColors.border, lineWidth: 1)\n                                )\n                        }\n                        .buttonStyle(.plain)\n                    }\n                }\n\n                // 运行目录\n                VStack(alignment: .leading, spacing: 8) {\n                    HStack(spacing: 4) {\n                        Text(LocalizedStringKey(" = " ? Color.white.opacity(0.2) : AppColors.border, lineWidth: 1)\n                                )\n                        }\n                        .buttonStyle(.plain)\n                    }\n                }\n\n                // Directorio de ejecución\n                VStack(alignment: .leading, spacing: 8) {\n                    HStack(spacing: 4) {\n                        Text(LocalizedStringKey(";
") && service.installed {\n                let version = String(service.name.dropFirst(4)) // 去掉 " = ") && service.installed {\n                let version = String(service.name.dropFirst(4)) // 去掉 ";
"\n\n        // 工作空间 Nginx 配置文件路径\n        let wsNginxConf = config.nginxConfigFile\n        let wsServersDir = " = "\n\n        // 工作空间 Nginx 配置文件路径\n        let wsNginxConf = config.nginxConfigFile\n        let wsServersDir = ";
")\n        }\n    }\n\n    // MARK: - phpMyAdmin 访问\n\n    /// 打开 phpMyAdmin（带认证令牌，自动登录）\n    func openPhpMyAdmin(withIP ip: String) {\n        // 生成新令牌\n        let token = generateToken(forIP: ip)\n\n        // 从设置获取端口\n        let port = AppSettings.shared.defaultSitePort\n\n        // 构建带令牌的 URL，指向 signon 脚本实现自动登录\n        var urlString = " = ")\n        }\n    }\n\n    // MARK: - phpMyAdmin 访问\n\n    /// 打开 phpMyAdmin（带认证令牌，自动登录）\n    func openPhpMyAdmin(withIP ip: String) {\n        // 生成新令牌\n        let token = generateToken(forIP: ip)\n\n        // 从设置获取端口\n        let port = AppSettings.shared.defaultSitePort\n\n        // 构建带令牌的 URL，指向 signon 脚本实现自动登录\n        var urlString = ";
")\n\n            // 保存错误日志（含详细信息）\n            ErrorLogManager.shared.addLog(\n                softwareId: software.id,\n                softwareName: software.name,\n                version: software.version,\n                operation: " = ")\n\n            // 保存错误日志（含详细信息）\n            ErrorLogManager.shared.addLog(\n                softwareId: software.id,\n                softwareName: software.name,\n                version: software.version,\n                operation: ";
"文件不存在：\\(filePath)" = "文件不存在：\\(filePath)";
"\n                            if fileManager.fileExists(atPath: fullDocRoot) {\n                                documentRoot = fullDocRoot\n                            } else {\n                                documentRootExists = false\n                            }\n                        }\n\n                        // 创建候选站点\n                        let candidate = CandidateSite(\n                            directoryName: item,\n                            directoryPath: itemPath,\n                            inferredDomain: inferredDomain,\n                            domainStatus: domainStatus,\n                            documentRoot: documentRoot,\n                            documentRootExists: documentRootExists,\n                            siteName: inferredDomain,\n                            phpVersion: defaultPHPVersion,\n                            webServer: defaultWebServer,\n                            enableSSL: false,\n                            forceHttps: false,\n                            sslMode: .nginxTermination,\n                            createFTP: false,\n                            createDatabase: false,\n                            writeHosts: true,\n                            conflictMessage: nil\n                        )\n\n                        candidates.append(candidate)\n                    }\n                } catch {\n                    NSLog(" = "\n                            if fileManager.fileExists(atPath: fullDocRoot) {\n                                documentRoot = fullDocRoot\n                            } else {\n                                documentRootExists = false\n                            }\n                        }\n\n                        // 创建候选站点\n                        let candidate = CandidateSite(\n                            directoryName: item,\n                            directoryPath: itemPath,\n                            inferredDomain: inferredDomain,\n                            domainStatus: domainStatus,\n                            documentRoot: documentRoot,\n                            documentRootExists: documentRootExists,\n                            siteName: inferredDomain,\n                            phpVersion: defaultPHPVersion,\n                            webServer: defaultWebServer,\n                            enableSSL: false,\n                            forceHttps: false,\n                            sslMode: .nginxTermination,\n                            createFTP: false,\n                            createDatabase: false,\n                            writeHosts: true,\n                            conflictMessage: nil\n                        )\n\n                        candidates.append(candidate)\n                    }\n                } catch {\n                    NSLog(";
")\n\n        return regex.stringByReplacingMatches(\n            in: content,\n            options: [],\n            range: range,\n            withTemplate: replacementString\n        )\n    }\n}\n\n/// 配置文件位置\nstruct ConfigFileLocation {\n    let workspacePath: String?   // 工作空间模式下的路径（相对于工作空间根目录）\n    let homebrewPath: String?    // Homebrew 模式下的路径\n    let filePattern: String?     // 文件匹配模式（如 " = ")\n\n        return regex.stringByReplacingMatches(\n            in: content,\n            options: [],\n            range: range,\n            withTemplate: replacementString\n        )\n    }\n}\n\n/// 配置文件位置\nstruct ConfigFileLocation {\n    let workspacePath: String?   // 工作空间模式下的路径（相对于工作空间根目录）\n    let homebrewPath: String?    // Homebrew 模式下的路径\n    let filePattern: String?     // 文件匹配模式（如 ";
", selection: $selectedPlan) {\n                    ForEach(MemoryPlan.allCases) { plan in\n                        Text(LocalizedStringKey(plan.rawValue)).tag(plan)\n                    }\n                }\n                .frame(width: 140)\n                .onChange(of: selectedPlan) { newPlan in\n                    applyPlanPreset(newPlan)\n                }\n            }\n\n            // 预估内存使用\n            HStack(spacing: 8) {\n                Text(" = ", selection: $selectedPlan) {\n                    ForEach(MemoryPlan.allCases) { plan in\n                        Text(LocalizedStringKey(plan.rawValue)).tag(plan)\n                    }\n                }\n                .frame(width: 140)\n                .onChange(of: selectedPlan) { newPlan in\n                    applyPlanPreset(newPlan)\n                }\n            }\n\n            // 预估内存使用\n            HStack(spacing: 8) {\n                Text(";
") {\n            NSWorkspace.shared.open(url)\n        }\n    }\n\n    // 刷新权限\n    private func refreshPrivileges() async {\n        // 执行 FLUSH PRIVILEGES\n    }\n\n    // 导出数据库\n    private func exportDatabase(_ db: MySQLDatabaseInfo) {\n        let savePanel = NSSavePanel()\n        savePanel.allowedContentTypes = [.init(filenameExtension: " = ") {\n            NSWorkspace.shared.open(url)\n        }\n    }\n\n    // 刷新权限\n    private func refreshPrivileges() async {\n        // 执行 FLUSH PRIVILEGES\n    }\n\n    // 导出数据库\n    private func exportDatabase(_ db: MySQLDatabaseInfo) {\n        let savePanel = NSSavePanel()\n        savePanel.allowedContentTypes = [.init(filenameExtension: ";
"]),\n    ]\n\n    // MARK: - Redis 配置字典\n    static let redis: [String: ConfigMeta] = [\n        // 网络配置\n        " = "]),\n    ]\n\n    // MARK: - Redis 配置字典\n    static let redis: [String: ConfigMeta] = [\n        // 网络配置\n        ";
"保存日志配置失败: \\(error)" = "保存日志配置失败: \\(error)";
") {\n                inLocationBlock = false\n                continue\n            }\n\n            if inLocationBlock {\n                // 转换 try_files\n                if trimmed.contains(" = ") {\n                inLocationBlock = false\n                continue\n            }\n\n            if inLocationBlock {\n                // 转换 try_files\n                if trimmed.contains(";
"\n        # 默认站点配置\n        # 由 JihePHPEnv 自动生成\n        # 生成时间: \\(ISO8601DateFormatter().string(from: Date()))\n\n        server {\n            listen       \\(port);\n            server_name  localhost 127.0.0.1;\n\n            root   \\(documentRoot);\n            index  index.php index.html index.htm;\n\n            location / {\n                try_files $uri $uri/ /index.php?$query_string;\n            }\n\n            # PHP-FPM 配置\n            location ~ \\\\.php$ {\n                fastcgi_pass   127.0.0.1:9000;\n                fastcgi_index  index.php;\n                fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;\n                include        fastcgi_params;\n            }\n\n        " = "\n        # 默认站点配置\n        # 由 JihePHPEnv 自动生成\n        # 生成时间: \\(ISO8601DateFormatter().string(from: Date()))\n\n        server {\n            listen       \\(port);\n            server_name  localhost 127.0.0.1;\n\n            root   \\(documentRoot);\n            index  index.php index.html index.htm;\n\n            location / {\n                try_files $uri $uri/ /index.php?$query_string;\n            }\n\n            # PHP-FPM 配置\n            location ~ \\\\.php$ {\n                fastcgi_pass   127.0.0.1:9000;\n                fastcgi_index  index.php;\n                fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;\n                include        fastcgi_params;\n            }\n\n        ";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(20)\n        .frame(height: 60)\n    }\n\n    // MARK: - 标题栏\n    private var headerBar: some View {\n        HStack {\n            Text(LocalizedStringKey(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(20)\n        .frame(height: 60)\n    }\n\n    // MARK: - 标题栏\n    private var headerBar: some View {\n        HStack {\n            Text(LocalizedStringKey(";
", isOn: Binding(\n                    get: { config.corsEnabled },\n                    set: { config.corsEnabled = $0 }\n                ))\n                .toggleStyle(.switch)\n                .controlSize(.small)\n            }\n            \n            // 来源\n            VStack(alignment: .leading, spacing: 8) {\n                HStack(spacing: 4) {\n                    Text(LocalizedStringKey(" = ", isOn: Binding(\n                    get: { config.corsEnabled },\n                    set: { config.corsEnabled = $0 }\n                ))\n                .toggleStyle(.switch)\n                .controlSize(.small)\n            }\n            \n            // 来源\n            VStack(alignment: .leading, spacing: 8) {\n                HStack(spacing: 4) {\n                    Text(LocalizedStringKey(";
"\n        ]\n\n        return phpPatterns.contains { path.contains($0) }\n    }\n\n    // MARK: - 扫描行为检测\n    private func detectScanning(path: String, userAgent: String, statusCode: Int) -> Bool {\n        // 常见扫描路径\n        let scanPaths = [\n            " = "\n        ]\n\n        return phpPatterns.contains { path.contains($0) }\n    }\n\n    // MARK: - 扫描行为检测\n    private func detectScanning(path: String, userAgent: String, statusCode: Int) -> Bool {\n        // 常见扫描路径\n        let scanPaths = [\n            ";
" ? Color.blue.opacity(AppTokens.Opacity.light) : Color.clear)\n                    }\n                    .buttonStyle(.plain)\n\n                    // 已安装的PHP版本\n                    ForEach(installedPHPVersions, id: \\.self) { version in\n                        Button(action: {\n                            changePHPVersion(version)\n                            withAnimation(.easeInOut(duration: 0.2)) {\n                                showPHPDropdown = false\n                            }\n                        }) {\n                            let textColor: Color = version == site.phpVersion ? .blue : .primary\n                            let bgColor: Color = version == site.phpVersion ? .blue.opacity(AppTokens.Opacity.light) : .clear\n                            \n                            Text(version)\n                                .font(.system(size: 12))\n                                .foregroundColor(textColor)\n                                .frame(maxWidth: .infinity, alignment: .leading)\n                                .padding(.horizontal, 12)\n                                .padding(.vertical, 8)\n                                .background(bgColor)\n                        }\n                        .buttonStyle(.plain)\n                    }\n                }\n                .frame(width: 100)\n                .padding(.vertical, 4)\n            }\n            \n            // SSL证书\n            Button(action: {\n                showSSLDetailsSheet = true\n            }) {\n                Text(sslStatusText)\n                    .font(.system(size: 12))\n                    .foregroundColor(sslStatusColor)\n                    .frame(width: 60, alignment: .leading)\n            }\n            .buttonStyle(.plain)\n            \n            // 操作\n            HStack(spacing: 8) {\n                if isTrashMode {\n                    MacButton(icon: " = " ? Color.blue.opacity(AppTokens.Opacity.light) : Color.clear)\n                    }\n                    .buttonStyle(.plain)\n\n                    // 已安装的PHP版本\n                    ForEach(installedPHPVersions, id: \\.self) { version in\n                        Button(action: {\n                            changePHPVersion(version)\n                            withAnimation(.easeInOut(duration: 0.2)) {\n                                showPHPDropdown = false\n                            }\n                        }) {\n                            let textColor: Color = version == site.phpVersion ? .blue : .primary\n                            let bgColor: Color = version == site.phpVersion ? .blue.opacity(AppTokens.Opacity.light) : .clear\n                            \n                            Text(version)\n                                .font(.system(size: 12))\n                                .foregroundColor(textColor)\n                                .frame(maxWidth: .infinity, alignment: .leading)\n                                .padding(.horizontal, 12)\n                                .padding(.vertical, 8)\n                                .background(bgColor)\n                        }\n                        .buttonStyle(.plain)\n                    }\n                }\n                .frame(width: 100)\n                .padding(.vertical, 4)\n            }\n            \n            // SSL证书\n            Button(action: {\n                showSSLDetailsSheet = true\n            }) {\n                Text(sslStatusText)\n                    .font(.system(size: 12))\n                    .foregroundColor(sslStatusColor)\n                    .frame(width: 60, alignment: .leading)\n            }\n            .buttonStyle(.plain)\n            \n            // 操作\n            HStack(spacing: 8) {\n                if isTrashMode {\n                    MacButton(icon: ";
")\n                        .font(.system(size: 12))\n                }\n                .buttonStyle(.bordered)\n                .disabled(currentPage <= 1)\n\n                // 页码显示\n                Text(" = ")\n                        .font(.system(size: 12))\n                }\n                .buttonStyle(.bordered)\n                .disabled(currentPage <= 1)\n\n                // 页码显示\n                Text(";
"\n    private var authPluginData: Data = Data()\n\n    private let host: String\n    private let port: Int\n    private let username: String\n    private let password: String\n\n    private let connectionTimeout: TimeInterval = 10\n\n    init(host: String, port: Int, username: String, password: String) {\n        self.host = host\n        self.port = port\n        self.username = username\n        self.password = password\n    }\n\n    deinit {\n        disconnect()\n    }\n\n    // MARK: - 连接管理\n\n    /// 连接到 MySQL 服务器\n    func connect() async throws {\n        return try await withCheckedThrowingContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                do {\n                    try self.connectSync()\n                    continuation.resume()\n                } catch {\n                    continuation.resume(throwing: error)\n                }\n            }\n        }\n    }\n\n    /// 同步连接\n    private func connectSync() throws {\n        // 创建 Socket 连接\n        var readStream: Unmanaged<CFReadStream>?\n        var writeStream: Unmanaged<CFWriteStream>?\n\n        CFStreamCreatePairWithSocketToHost(nil, host as CFString, UInt32(port), &readStream, &writeStream)\n\n        guard let input = readStream?.takeRetainedValue() as InputStream?,\n              let output = writeStream?.takeRetainedValue() as OutputStream? else {\n            throw MySQLError.connectionFailed(" = "\n    private var authPluginData: Data = Data()\n\n    private let host: String\n    private let port: Int\n    private let username: String\n    private let password: String\n\n    private let connectionTimeout: TimeInterval = 10\n\n    init(host: String, port: Int, username: String, password: String) {\n        self.host = host\n        self.port = port\n        self.username = username\n        self.password = password\n    }\n\n    deinit {\n        disconnect()\n    }\n\n    // MARK: - 连接管理\n\n    /// 连接到 MySQL 服务器\n    func connect() async throws {\n        return try await withCheckedThrowingContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                do {\n                    try self.connectSync()\n                    continuation.resume()\n                } catch {\n                    continuation.resume(throwing: error)\n                }\n            }\n        }\n    }\n\n    /// 同步连接\n    private func connectSync() throws {\n        // 创建 Socket 连接\n        var readStream: Unmanaged<CFReadStream>?\n        var writeStream: Unmanaged<CFWriteStream>?\n\n        CFStreamCreatePairWithSocketToHost(nil, host as CFString, UInt32(port), &readStream, &writeStream)\n\n        guard let input = readStream?.takeRetainedValue() as InputStream?,\n              let output = writeStream?.takeRetainedValue() as OutputStream? else {\n            throw MySQLError.connectionFailed(";
" {\n                        stack.removeLast()\n                    } else {\n                        // 缺少左括号\n                        errors.append(NSRange(location: i, length: 1))\n                    }\n                    \n                    formatted.append(" = " {\n                        stack.removeLast()\n                    } else {\n                        // 缺少左括号\n                        errors.append(NSRange(location: i, length: 1))\n                    }\n                    \n                    formatted.append(";
" {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .onAppear {\n            softwareManager.loadSoftwareVersions()\n        }\n    }\n    \n    // V1 旧版布局\n    private var v1Body: some View {\n        HStack(spacing: 0) {\n            // 左侧分类列表\n            VStack(alignment: .leading, spacing: 0) {\n                // 顶部留白，匹配原型\n                Color.clear.frame(height: 16)\n                \n                // 菜单列表\n                ForEach(ComponentCategory.allCases, id: \\.self) { category in\n                    Button(action: {\n                        selectedCategory = category\n                    }) {\n                        HStack {\n                            Text(LocalizedStringKey(category.rawValue))\n                                .font(.system(size: 13, weight: category == selectedCategory ? .medium : .regular))\n                                .foregroundColor(category == selectedCategory ? .blue : .primary)\n                            \n                            Spacer()\n                            \n                            // 数量统计 Badge\n                            Text(" = " {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .onAppear {\n            softwareManager.loadSoftwareVersions()\n        }\n    }\n    \n    // V1 Diseño antiguo\n    private var v1Body: some View {\n        HStack(spacing: 0) {\n            // Lista de categorías izquierda\n            VStack(alignment: .leading, spacing: 0) {\n                // Espacio en blanco superior para coincidir con el prototipo\n                Color.clear.frame(height: 16)\n                \n                // Lista de menú\n                ForEach(ComponentCategory.allCases, id: \\.self) { category in\n                    Button(action: {\n                        selectedCategory = category\n                    }) {\n                        HStack {\n                            Text(LocalizedStringKey(category.rawValue))\n                                .font(.system(size: 13, weight: category == selectedCategory ? .medium : .regular))\n                                .foregroundColor(category == selectedCategory ? .blue : .primary)\n                            \n                            Spacer()\n                            \n                            // Insignia de recuento\n                            Text(";
"错误 \\(errorCode): \\(message)" = "Error \\(errorCode): \\(message)";
" }\n                    .sorted { path1, path2 in\n                        let date1 = (try? FileManager.default.attributesOfItem(atPath: path1)[.modificationDate] as? Date) ?? Date.distantPast\n                        let date2 = (try? FileManager.default.attributesOfItem(atPath: path2)[.modificationDate] as? Date) ?? Date.distantPast\n                        return date1 > date2\n                    }\n                if let latestErr = errFiles.first {\n                    return latestErr\n                }\n            }\n            // 备用路径\n            return " = " }\n                    .sorted { path1, path2 in\n                        let date1 = (try? FileManager.default.attributesOfItem(atPath: path1)[.modificationDate] as? Date) ?? Date.distantPast\n                        let date2 = (try? FileManager.default.attributesOfItem(atPath: path2)[.modificationDate] as? Date) ?? Date.distantPast\n                        return date1 > date2\n                    }\n                if let latestErr = errFiles.first {\n                    return latestErr\n                }\n            }\n            // Ruta alternativa\n            return ";
" {\n                return .crashExit\n            }\n        }\n\n        // 5. 检查最近错误日志\n        if hasRecentError {\n            return .crashExit\n        }\n\n        return .manual\n    }\n\n    /// 检查端口是否被占用\n    private nonisolated static func isPortOccupied(_ port: Int) -> Bool {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = " {\n                return .crashExit\n            }\n        }\n\n        // 5. Comprobar los registros de errores más recientes\n        if hasRecentError {\n            return .crashExit\n        }\n\n        return .manual\n    }\n\n    /// Comprobar si un puerto está ocupado\n    private nonisolated static func isPortOccupied(_ port: Int) -> Bool {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: ";
"\n            # ============================================\n            # 🔄 Nginx 反向代理配置 (Apache 后端)\n            # 此配置将请求转发到 Apache 后端服务器\n            # 前端端口: 80 (Nginx)\n            # 后端端口: \\(backendHttpPort) (Apache)\n            # ============================================\n\n            server {\n                listen 80;\n                server_name \\(serverNames);\n            \\(authBasicBlocks)\n                location / {\n                    proxy_pass http://127.0.0.1:\\(backendHttpPort);\n                    proxy_set_header Host $host;\n                    proxy_set_header X-Real-IP $remote_addr;\n                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                    proxy_set_header X-Forwarded-Proto $scheme;\n                    proxy_set_header X-Forwarded-Host $host;\n                    proxy_set_header X-Forwarded-Port $server_port;\n                }\n            }\n            " = "\n            # ============================================\n            # 🔄 Configuración del proxy inverso de Nginx (backend Apache)\n            # Esta configuración redirige las solicitudes al servidor backend Apache\n            # Puerto frontend: 80 (Nginx)\n            # Puerto backend: \\(backendHttpPort) (Apache)\n            # ============================================\n\n            server {\n                listen 80;\n                server_name \\(serverNames);\n            \\(authBasicBlocks)\n                location / {\n                    proxy_pass http://127.0.0.1:\\(backendHttpPort);\n                    proxy_set_header Host $host;\n                    proxy_set_header X-Real-IP $remote_addr;\n                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                    proxy_set_header X-Forwarded-Proto $scheme;\n                    proxy_set_header X-Forwarded-Host $host;\n                    proxy_set_header X-Forwarded-Port $server_port;\n                }\n            }\n            ";
"\n        refresh()\n    }\n\n    /// 配置 PHP-FPM 端口\n    private func configurePHPFPMPort(phpVersion: String, port: Int) async throws {\n        let version = phpVersion == " = "\n        refresh()\n    }\n\n    /// Configurar el puerto de PHP-FPM\n    private func configurePHPFPMPort(phpVersion: String, port: Int) async throws {\n        let version = phpVersion == ";
"修改配置文件，将端口改为 \\(altPort)" = "Modificar el archivo de configuración y cambiar el puerto a \\(altPort)";
") {\n            // 简单验证是否像域名格式\n            let components = directoryName.split(separator: " = ") {\n            // Validación sencilla para comprobar si tiene formato de nombre de dominio\n            let components = directoryName.split(separator: ";
"\n    @State private var isChanging = false\n    @State private var errorMessage: String?\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            HStack {\n                Text(" = "\n    @State private var isChanging = false\n    @State private var errorMessage: String?\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Barra de título\n            HStack {\n                Text(";
"\n        ]\n\n        return sqlPatterns.contains { path.contains($0) }\n    }\n\n    // MARK: - PHP攻击检测\n    private func detectPHPAttack(path: String) -> Bool {\n        let phpPatterns = [\n            " = "\n        ]\n\n        return sqlPatterns.contains { path.contains($0) }\n    }\n\n    // MARK: - Detección de ataques PHP\n    private func detectPHPAttack(path: String) -> Bool {\n        let phpPatterns = [\n            ";
")\n        }\n\n        // 2. 创建用户\n        let createUserSQL = " = ")\n        }\n\n        // 2. Crear usuario\n        let createUserSQL = ";
"📊 扫描完成，共找到 \\(configs.count) 个配置" = "📊 Escaneo completado. Se encontraron \\(configs.count) configuraciones";
"\n                    found = true\n                    break\n                }\n            }\n        }\n\n        // 如果没找到，在 [mysqld] 段末尾添加\n        if !found && mysqldSectionIndex >= 0 {\n            // 找到 [mysqld] 段的末尾\n            var insertIndex = mysqldSectionIndex + 1\n            for i in (mysqldSectionIndex + 1)..<lines.count {\n                let trimmed = lines[i].trimmingCharacters(in: .whitespaces)\n                if trimmed.hasPrefix(" = "\n                    found = true\n                    break\n                }\n            }\n        }\n\n        // Si no se encuentra, añadir al final de la sección [mysqld]\n        if !found && mysqldSectionIndex >= 0 {\n            // Encontrar el final de la sección [mysqld]\n            var insertIndex = mysqldSectionIndex + 1\n            for i in (mysqldSectionIndex + 1)..<lines.count {\n                let trimmed = lines[i].trimmingCharacters(in: .whitespaces)\n                if trimmed.hasPrefix(";
"\n            showError = true\n            return\n        }\n\n        // 验证密码长度\n        guard password.count >= 8 else {\n            errorMessage = " = "\n            showError = true\n            return\n        }\n\n        // Validar longitud de la contraseña\n        guard password.count >= 8 else {\n            errorMessage = ";
"\n        主机: \\(tab.host)\n        端口: \\(tab.port)\n        用户名: root\n        密码: \\(password)\n        " = "\n        Host: \\(tab.host)\n        Puerto: \\(tab.port)\n        Nombre de usuario: root\n        Contraseña: \\(password)\n        ";
")\n                .foregroundColor(.red)\n            Text(error)\n                .font(.caption)\n                .foregroundColor(.red)\n        }\n        .padding()\n        .background(Color.red.opacity(AppTokens.Opacity.light))\n        .cornerRadius(8)\n    }\n\n    // 底部按钮\n    private var bottomButtons: some View {\n        HStack {\n            Spacer()\n            Button(" = ")\n                .foregroundColor(.red)\n            Text(error)\n                .font(.caption)\n                .foregroundColor(.red)\n        }\n        .padding()\n        .background(Color.red.opacity(AppTokens.Opacity.light))\n        .cornerRadius(8)\n    }\n\n    // Botones inferiores\n    private var bottomButtons: some View {\n        HStack {\n            Spacer()\n            Button(";
"\n        #endif\n\n        if FileManager.default.fileExists(atPath: mkcertPath) {\n            return mkcertPath\n        }\n\n        // 回退到通用版本\n        let genericPath = " = "\n        #endif\n\n        if FileManager.default.fileExists(atPath: mkcertPath) {\n            return mkcertPath\n        }\n\n        // Volver a la versión genérica\n        let genericPath = ";
"\n    }\n\n    // 格式化字节\n    private func formatBytes(_ bytes: Int64) -> String {\n        let formatter = ByteCountFormatter()\n        formatter.allowedUnits = [.useMB, .useGB]\n        formatter.countStyle = .file\n        return formatter.string(fromByteCount: bytes)\n    }\n}\n\n// 统计行组件\nstruct StatRow: View {\n    let label: String\n    let value: String\n\n    var body: some View {\n        HStack {\n            Text(label)\n                .foregroundColor(.secondary)\n            Spacer()\n            Text(value)\n                .fontWeight(.medium)\n        }\n    }\n}\n\n// 站点统计数据模型\nstruct SiteStatistics {\n    let totalVisits: Int\n    let uniqueVisitors: Int\n    let pageViews: Int\n    let avgResponseTime: Int\n    let peakVisits: Int\n    let peakHour: Int\n    let topPage: String\n    let errorRate: Double\n    let bandwidth: Int64\n    let hourlyTraffic: [HourlyTraffic]\n\n    struct HourlyTraffic {\n        let hour: Int\n        let visits: Int\n    }\n\n    static let mockData = SiteStatistics(\n        totalVisits: 12345,\n        uniqueVisitors: 5678,\n        pageViews: 23456,\n        avgResponseTime: 125,\n        peakVisits: 890,\n        peakHour: 14,\n        topPage: " = "\n    }\n\n    // Formatear bytes\n    private func formatBytes(_ bytes: Int64) -> String {\n        let formatter = ByteCountFormatter()\n        formatter.allowedUnits = [.useMB, .useGB]\n        formatter.countStyle = .file\n        return formatter.string(fromByteCount: bytes)\n    }\n}\n\n// Componente de fila estadística\nstruct StatRow: View {\n    let label: String\n    let value: String\n\n    var body: some View {\n        HStack {\n            Text(label)\n                .foregroundColor(.secondary)\n            Spacer()\n            Text(value)\n                .fontWeight(.medium)\n        }\n    }\n}\n\n// Modelo de estadísticas del sitio\nstruct SiteStatistics {\n    let totalVisits: Int\n    let uniqueVisitors: Int\n    let pageViews: Int\n    let avgResponseTime: Int\n    let peakVisits: Int\n    let peakHour: Int\n    let topPage: String\n    let errorRate: Double\n    let bandwidth: Int64\n    let hourlyTraffic: [HourlyTraffic]\n\n    struct HourlyTraffic {\n        let hour: Int\n        let visits: Int\n    }\n\n    static let mockData = SiteStatistics(\n        totalVisits: 12345,\n        uniqueVisitors: 5678,\n        pageViews: 23456,\n        avgResponseTime: 125,\n        peakVisits: 890,\n        peakHour: 14,\n        topPage: ";
")\n\n        return paths\n    }\n\n    // MARK: - Redis 路径\n\n    /// 获取 Redis 配置文件路径（优先返回存在的文件）\n    func getRedisConfigPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.redisConfigFile\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        return " = ")\n\n        return paths\n    }\n\n    // MARK: - Rutas de Redis\n\n    /// Obtener la ruta del archivo de configuración de Redis (devuelve primero el archivo existente)\n    func getRedisConfigPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.redisConfigFile\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        return ";
", port: 3309, isRunning: false),\n    ]\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 表单内容\n            formContent\n\n            Divider()\n\n            // 底部按钮\n            bottomButtons\n        }\n        .frame(width: 600, height: 550)\n        .onAppear {\n            // 默认选择第一个运行中的版本\n            selectedVersion = availableVersions.first(where: { $0.isRunning }) ?? availableVersions.first\n        }\n        .alert(" = ", port: 3309, isRunning: false),\n    ]\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Barra de título\n            headerBar\n\n            Divider()\n\n            // Contenido del formulario\n            formContent\n\n            Divider()\n\n            // Botones inferiores\n            bottomButtons\n        }\n        .frame(width: 600, height: 550)\n        .onAppear {\n            // Seleccionar por defecto la primera versión en ejecución\n            selectedVersion = availableVersions.first(where: { $0.isRunning }) ?? availableVersions.first\n        }\n        .alert(";
"\n    }\n\n    /// 获取 PHP 配置文件路径\n    func getPHPConfigPath(version: String) -> String {\n        if let config = config, config.isInitialized {\n            return config.phpConfigFile(version: version)\n        }\n        return " = "\n    }\n\n    /// Obtener la ruta del archivo de configuración de PHP\n    func getPHPConfigPath(version: String) -> String {\n        if let config = config, config.isInitialized {\n            return config.phpConfigFile(version: version)\n        }\n        return ";
"确定要彻底删除选中的 \\(selectedSites.count) 个站点吗？此操作不可恢复。" = "¿Está seguro de que desea eliminar definitivamente los \\(selectedSites.count) sitios seleccionados? Esta acción no se puede deshacer.";
"\n        return formatter\n    }()\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            HStack {\n                Text(LocalizedStringKey(" = "\n        return formatter\n    }()\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Barra de título\n            HStack {\n                Text(LocalizedStringKey(";
")\n                                .resizable()\n                        }\n                    }\n                        .scaledToFit()\n                        .frame(width: 200, height: 260)\n                        .cornerRadius(8)\n                        .shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 2)\n                        .padding(.vertical, 8)\n                    Spacer()\n                }\n            }\n    }\n\n    // MARK: - 默认站点配置区域\n    private var defaultSiteSection: some View {\n        Section(" = ")\n                                .font(.system(size: 11))\n                                .foregroundColor(category == selectedCategory ? .blue : .secondary)\n                                .padding(.horizontal, 8)\n                                .padding(.vertical, 2)\n                                .background(\n                                    Capsule()\n                                        .fill(category == selectedCategory ? Color.blue.opacity(0.15) : Color.gray.opacity(0.1))\n                                )\n                        }\n                        .padding(.vertical, 10)\n                        .padding(.horizontal, 16)\n                        .background(category == selectedCategory ? Color.blue.opacity(0.1) : Color.clear)\n                        .cornerRadius(AppTokens.Radius.medium)\n                        .contentShape(Rectangle())\n                    }\n                    .buttonStyle(.plain)\n                }\n                Spacer()\n            }\n            .padding(.horizontal, 16)\n            .frame(width: 200)\n            .background(Color.white.opacity(0.4))\n            .overlay(\n                Rectangle()\n                    .frame(width: 1)\n                    .foregroundColor(Color.white.opacity(0.2)),\n                alignment: .trailing\n            )\n\n            // Lista de versiones a la derecha\n            VStack(spacing: 0) {\n                // Barra de herramientas\n                HStack(spacing: 12) {\n                    HStack {\n                        Image(systemName: ";
")\n                                .font(.system(size: 11))\n                                .foregroundColor(category == selectedCategory ? .blue : .secondary)\n                                .padding(.horizontal, 8)\n                                .padding(.vertical, 2)\n                                .background(\n                                    Capsule()\n                                        .fill(category == selectedCategory ? Color.blue.opacity(0.15) : Color.gray.opacity(0.1))\n                                )\n                        }\n                        .padding(.vertical, 10)\n                        .padding(.horizontal, 16)\n                        .background(category == selectedCategory ? Color.blue.opacity(0.1) : Color.clear)\n                        .cornerRadius(AppTokens.Radius.medium)\n                        .contentShape(Rectangle())\n                    }\n                    .buttonStyle(.plain)\n                }\n                Spacer()\n            }\n            .padding(.horizontal, 16)\n            .frame(width: 200)\n            .background(Color.white.opacity(0.4))\n            .overlay(\n                Rectangle()\n                    .frame(width: 1)\n                    .foregroundColor(Color.white.opacity(0.2)),\n                alignment: .trailing\n            )\n\n            // 右侧版本列表\n            VStack(spacing: 0) {\n                // Toolbar\n                HStack(spacing: 12) {\n                    HStack {\n                        Image(systemName: " = ")\n                                .resizable()\n                        }\n                    }\n                        .scaledToFit()\n                        .frame(width: 200, height: 260)\n                        .cornerRadius(8)\n                        .shadow(color: Color.black.opacity(0.1), radius: 4, x: 0, y: 2)\n                        .padding(.vertical, 8)\n                    Spacer()\n                }\n            }\n    }\n\n    // MARK: - Sección de configuración predeterminada del sitio\n    private var defaultSiteSection: some View {\n        Section(";
"\n    }\n\n    /// 获取 Apache 配置搜索路径列表\n    func getApacheConfigSearchPaths() -> [String] {\n        var paths: [String] = []\n\n        if let config = workspaceManager.config, config.isInitialized {\n            paths.append(config.apacheConfigFile)\n        }\n\n        paths.append(" = "\n    }\n\n    /// Obtener la lista de rutas de búsqueda para la configuración de Apache\n    func getApacheConfigSearchPaths() -> [String] {\n        var paths: [String] = []\n\n        if let config = workspaceManager.config, config.isInitialized {\n            paths.append(config.apacheConfigFile)\n        }\n\n        paths.append(";
"\n            publicDirectory = cleanDir\n        }\n    }\n\n    // 扫描网站目录下的子目录\n    private func scanSubdirectories() {\n        availableSubdirs = []\n        guard !siteDirectory.isEmpty else { return }\n\n        let fileManager = FileManager.default\n        guard fileManager.fileExists(atPath: siteDirectory) else { return }\n\n        do {\n            let contents = try fileManager.contentsOfDirectory(atPath: siteDirectory)\n            let knownDirs = [" = "\n            publicDirectory = cleanDir\n        }\n    }\n\n    // Escanear subdirectorios dentro del directorio del sitio\n    private func scanSubdirectories() {\n        availableSubdirs = []\n        guard !siteDirectory.isEmpty else { return }\n\n        let fileManager = FileManager.default\n        guard fileManager.fileExists(atPath: siteDirectory) else { return }\n\n        do {\n            let contents = try fileManager.contentsOfDirectory(atPath: siteDirectory)\n            let knownDirs = [";
") {\n                    inServerBlock = true\n                    serverBlockStart = i\n                    braceCount = 1\n                    continue\n                }\n\n                // 在 server 块内，计算大括号层级\n                if inServerBlock {\n                    braceCount += line.filter { $0 == " = ") {\n                    inServerBlock = true\n                    serverBlockStart = i\n                    braceCount = 1\n                    continue\n                }\n\n                // Dentro del bloque server, calcular el nivel de llaves\n                if inServerBlock {\n                    braceCount += line.filter { $0 == ";
") {\n                    // 提取过期日期\n                    let dateStr = line.replacingOccurrences(of: " = ") {\n                    // Extraer la fecha de expiración\n                    let dateStr = line.replacingOccurrences(of: ";
"))\n                    // 尝试提取时间部分（前面的日期时间）\n                    let datePattern = #" = "))\n                    // Intentar extraer la parte horaria (fecha y hora anteriores)\n                    let datePattern = #";
"))\n        }) {\n            // 检查服务是否运行\n            if !service.isRunning {\n                // 如果服务未运行，在主线程显示提示\n                await MainActor.run {\n                    // 使用 NSAlert 显示提示信息\n                    let alert = NSAlert()\n                    alert.messageText = " = "))\n        }) {\n            // Verificar si el servicio está en ejecución\n            if !service.isRunning {\n                // Si el servicio no está en ejecución, mostrar una notificación en el hilo principal\n                await MainActor.run {\n                    // Usar NSAlert para mostrar el mensaje\n                    let alert = NSAlert()\n                    alert.messageText = ";
")\n                }\n            }\n\n            // 解析 VirtualHost 端口\n            if trimmed.hasPrefix(" = ")\n                }\n            }\n\n            // Analizar el puerto de VirtualHost\n            if trimmed.hasPrefix(";
"确定要卸载 \\(software.displayName) 吗？此操作无法撤销。" = "¿Está seguro de que desea desinstalar \\(software.displayName)? Esta acción no se puede deshacer.";
"\n\n        // 检查 Homebrew 是否安装\n        guard await isHomebrewInstalled() else {\n            installationStatus = .failed(" = "\n\n        // Verificar si Homebrew está instalado\n        guard await isHomebrewInstalled() else {\n            installationStatus = .failed(";
", at: mysqldSectionEndIndex + 1)\n                } else {\n                    // 如果没有 [mysqld] 段，在文件末尾添加\n                    result.append(" = ", at: mysqldSectionEndIndex + 1)\n                } else {\n                    // Si no hay una sección [mysqld], agregarla al final del archivo\n                    result.append(";
"\n                    try fileManager.copyItem(atPath: configFilePath, toPath: backupPath)\n                }\n\n                // 确保目录存在\n                let directory = (configFilePath as NSString).deletingLastPathComponent\n                if !fileManager.fileExists(atPath: directory) {\n                    try fileManager.createDirectory(atPath: directory, withIntermediateDirectories: true)\n                }\n\n                // 写入新配置\n                try config.configContent.write(toFile: configFilePath, atomically: true, encoding: .utf8)\n\n                config.configLastModified = Date()\n                try await siteManager.updateSiteAdvancedConfig(site: site, config: config)\n\n                await MainActor.run {\n                    isSaving = false\n                    showSaveSuccess = true\n                    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {\n                        showSaveSuccess = false\n                    }\n                }\n            } catch {\n                await MainActor.run {\n                    isSaving = false\n                    errorMessage = error.localizedDescription\n                    showSaveError = true\n                }\n            }\n        }\n    }\n\n    private func openConfigFileInFinder() {\n        let url = URL(fileURLWithPath: configFilePath)\n        NSWorkspace.shared.activateFileViewerSelecting([url])\n    }\n\n    private func resetToDefault() {\n        config.configContent = generateDefaultConfig()\n        config.configLastModified = Date()\n    }\n    \n    private func generateDefaultConfig() -> String {\n        let serverName = site.primaryDomain\n        let root = site.documentRoot\n        \n        if site.webServer == .nginx {\n            return " = "\n                    try fileManager.copyItem(atPath: configFilePath, toPath: backupPath)\n                }\n\n                // Asegurarse de que el directorio exista\n                let directory = (configFilePath as NSString).deletingLastPathComponent\n                if !fileManager.fileExists(atPath: directory) {\n                    try fileManager.createDirectory(atPath: directory, withIntermediateDirectories: true)\n                }\n\n                // Escribir la nueva configuración\n                try config.configContent.write(toFile: configFilePath, atomically: true, encoding: .utf8)\n\n                config.configLastModified = Date()\n                try await siteManager.updateSiteAdvancedConfig(site: site, config: config)\n\n                await MainActor.run {\n                    isSaving = false\n                    showSaveSuccess = true\n                    DispatchQueue.main.asyncAfter(deadline: .now() + 2) {\n                        showSaveSuccess = false\n                    }\n                }\n            } catch {\n                await MainActor.run {\n                    isSaving = false\n                    errorMessage = error.localizedDescription\n                    showSaveError = true\n                }\n            }\n        }\n    }\n\n    private func openConfigFileInFinder() {\n        let url = URL(fileURLWithPath: configFilePath)\n        NSWorkspace.shared.activateFileViewerSelecting([url])\n    }\n\n    private func resetToDefault() {\n        config.configContent = generateDefaultConfig()\n        config.configLastModified = Date()\n    }\n    \n    private func generateDefaultConfig() -> String {\n        let serverName = site.primaryDomain\n        let root = site.documentRoot\n        \n        if site.webServer == .nginx {\n            return ";
"\n        let workspaceFpmPath = workspaceConfig.phpFpmConfigFile(version: version)\n        try setupConfigSymlink(\n            source: brewFpmConfPath,\n            workspace: workspaceFpmPath,\n            fileManager: fm\n        )\n\n        // 处理 www.conf（新版本 PHP 如 8.x）\n        let brewWwwConfPath = " = "\n        let workspaceFpmPath = workspaceConfig.phpFpmConfigFile(version: version)\n        try setupConfigSymlink(\n            source: brewFpmConfPath,\n            workspace: workspaceFpmPath,\n            fileManager: fm\n        )\n\n        // Manejar www.conf (versiones nuevas de PHP como 8.x)\n        let brewWwwConfPath = ";
")\n                        return value\n                    }\n                }\n            }\n        }\n        return nil\n    }\n\n    // 应用方案预设\n    private func applyPlanPreset(_ plan: MemoryPlan) {\n        let presetValues = PerformancePresets.getRecommendedValues(for: plan)\n\n        if plan == .custom {\n            // 自定义模式不改变当前值\n            return\n        }\n\n        for (key, value) in presetValues {\n            configValues[key] = value\n        }\n\n        updateEstimatedMemory()\n    }\n\n    // 自动选择方案\n    private func autoSelectPlan() {\n        selectedPlan = PerformancePresets.autoSelectPlan()\n        applyPlanPreset(selectedPlan)\n    }\n\n    // 获取推荐值\n    private func getRecommendedValue(for key: String) -> String {\n        let presetValues = PerformancePresets.getRecommendedValues(for: selectedPlan)\n        return presetValues[key] ?? " = ")\n                        return value\n                    }\n                }\n            }\n        }\n        return nil\n    }\n\n    // Aplicar preajustes de plan\n    private func applyPlanPreset(_ plan: MemoryPlan) {\n        let presetValues = PerformancePresets.getRecommendedValues(for: plan)\n\n        if plan == .custom {\n            // El modo personalizado no modifica los valores actuales\n            return\n        }\n\n        for (key, value) in presetValues {\n            configValues[key] = value\n        }\n\n        updateEstimatedMemory()\n    }\n\n    // Selección automática de plan\n    private func autoSelectPlan() {\n        selectedPlan = PerformancePresets.autoSelectPlan()\n        applyPlanPreset(selectedPlan)\n    }\n\n    // Obtener valor recomendado\n    private func getRecommendedValue(for key: String) -> String {\n        let presetValues = PerformancePresets.getRecommendedValues(for: selectedPlan)\n        return presetValues[key] ?? ";
"),\n           let ports = try? JSONDecoder().decode([String: Int].self, from: data) {\n            self.phpPorts = ports\n        } else {\n            self.phpPorts = [:]\n        }\n    }\n    \n    // 检查目录是否存在且有权限\n    func validateDirectory(_ path: String) -> (exists: Bool, writable: Bool, error: String?) {\n        let fileManager = FileManager.default\n        var isDirectory: ObjCBool = false\n        \n        // 检查路径是否存在\n        guard fileManager.fileExists(atPath: path, isDirectory: &isDirectory) else {\n            return (false, false, " = "),\n           let ports = try? JSONDecoder().decode([String: Int].self, from: data) {\n            self.phpPorts = ports\n        } else {\n            self.phpPorts = [:]\n        }\n    }\n    \n    // Verificar si el directorio existe y tiene permisos adecuados\n    func validateDirectory(_ path: String) -> (exists: Bool, writable: Bool, error: String?) {\n        let fileManager = FileManager.default\n        var isDirectory: ObjCBool = false\n        \n        // Verificar si la ruta existe\n        guard fileManager.fileExists(atPath: path, isDirectory: &isDirectory) else {\n            return (false, false, ";
")\n            }\n            .buttonStyle(.bordered)\n\n            // 应用配置\n            Button(action: { applyDefaultSiteConfig() }) {\n                if isApplyingConfig {\n                    ProgressView()\n                        .scaleEffect(0.7)\n                } else {\n                    Label(" = ")\n            }\n            .buttonStyle(.bordered)\n\n            // Aplicar configuración\n            Button(action: { applyDefaultSiteConfig() }) {\n                if isApplyingConfig {\n                    ProgressView()\n                        .scaleEffect(0.7)\n                } else {\n                    Label(";
"SSL 证书正常（剩余 \\(remainingDays) 天）" = "SSL 证书正常（剩余 \\(remainingDays) 天）";
"))\n                                    .font(.system(size: 12))\n                                    .foregroundColor(server == site.webServer ? .blue : .primary)\n                                    .frame(maxWidth: .infinity, alignment: .leading)\n                                    .padding(.horizontal, 8)\n                                    .padding(.vertical, 4)\n                                    .background(server == site.webServer ? Color.blue.opacity(AppTokens.Opacity.light) : Color.clear)\n                            }\n                            .buttonStyle(.plain)\n                            .frame(width: 70)\n                        }\n                    }\n            .glass(.input)\n                    .shadow(color: .black.opacity(AppTokens.Opacity.hover), radius: 8, x: 0, y: 4)\n                    .overlay(\n                        RoundedRectangle(cornerRadius: 4)\n                            .stroke(AppColors.border, lineWidth: 1)\n                    )\n                    .offset(y: 20)\n                    .zIndex(10000)\n                    .transition(.opacity.combined(with: .scale(scale: 0.95)))\n                }\n            }\n\n            // PHP版本（可下拉更换）\n            HStack(alignment: .center, spacing: 4) {\n                Text(site.phpVersion == " = "))\n                                    .font(.system(size: 12))\n                                    .foregroundColor(server == site.webServer ? .blue : .primary)\n                                    .frame(maxWidth: .infinity, alignment: .leading)\n                                    .padding(.horizontal, 8)\n                                    .padding(.vertical, 4)\n                                    .background(server == site.webServer ? Color.blue.opacity(AppTokens.Opacity.light) : Color.clear)\n                            }\n                            .buttonStyle(.plain)\n                            .frame(width: 70)\n                        }\n                    }\n            .glass(.input)\n                    .shadow(color: .black.opacity(AppTokens.Opacity.hover), radius: 8, x: 0, y: 4)\n                    .overlay(\n                        RoundedRectangle(cornerRadius: 4)\n                            .stroke(AppColors.border, lineWidth: 1)\n                    )\n                    .offset(y: 20)\n                    .zIndex(10000)\n                    .transition(.opacity.combined(with: .scale(scale: 0.95)))\n                }\n            }\n\n            // PHP版本（可下拉更换）\n            HStack(alignment: .center, spacing: 4) {\n                Text(site.phpVersion == ";
")\n                            .font(.system(size: 11))\n                            .foregroundColor(.secondary)\n                            .frame(width: 60, alignment: .trailing)\n\n                        Text(candidate.inferredDomain)\n                            .font(.system(size: 11, design: .monospaced))\n                            .foregroundColor(candidate.domainStatus == .needsManual ? .red : .primary)\n                    }\n\n                    // 第三行：运行目录\n                    HStack(spacing: 8) {\n                        Text(" = ")\n                            .font(.system(size: 11))\n                            .foregroundColor(.secondary)\n                            .frame(width: 60, alignment: .trailing)\n\n                        Text(candidate.inferredDomain)\n                            .font(.system(size: 11, design: .monospaced))\n                            .foregroundColor(candidate.domainStatus == .needsManual ? .red : .primary)\n                    }\n\n                    // 第三行：运行目录\n                    HStack(spacing: 8) {\n                        Text(";
" ? Color.green.opacity(AppTokens.Opacity.light) : Color.clear)\n                        }\n                        .buttonStyle(.plain)\n                        .frame(width: 70)\n\n                        // 已安装的PHP版本\n                        ForEach(installedPHPVersions, id: \\.self) { version in\n                            Button(action: {\n                                changePHPVersion(version)\n                                withAnimation(.easeInOut(duration: 0.2)) {\n                                    showPHPDropdown = false\n                                }\n                            }) {\n                                Text(version)\n                                    .font(.system(size: 12))\n                                    .foregroundColor(version == site.phpVersion ? .green : .primary)\n                                    .frame(maxWidth: .infinity, alignment: .leading)\n                                    .padding(.horizontal, 8)\n                                    .padding(.vertical, 4)\n                                    .background(version == site.phpVersion ? Color.green.opacity(AppTokens.Opacity.light) : Color.clear)\n                            }\n                            .buttonStyle(.plain)\n                            .frame(width: 70)\n                        }\n                    }\n            .glass(.input)\n                    .shadow(color: .black.opacity(AppTokens.Opacity.hover), radius: 8, x: 0, y: 4)\n                    .overlay(\n                        RoundedRectangle(cornerRadius: 4)\n                            .stroke(AppColors.border, lineWidth: 1)\n                    )\n                    .offset(y: 20)\n                    .zIndex(10000)\n                    .transition(.opacity.combined(with: .scale(scale: 0.95)))\n                    .onDisappear {\n                        showPHPDropdown = false\n                    }\n                }\n            }\n            .onTapGesture { }  // 阻止点击穿透\n\n            // SSL证书\n            sslStatusView\n                .frame(width: 80, alignment: .leading)\n\n            // 操作按钮\n            actionButtons\n                .frame(width: 280, alignment: .leading)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 12)\n        .background(isHovered ? Color.gray.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n        .contentShape(Rectangle())\n        .onTapGesture {\n            // 点击行的其它地方时关闭下拉菜单\n            if showPHPDropdown || showWebServerDropdown || showDomainDropdown {\n                withAnimation(.easeInOut(duration: 0.2)) {\n                    showPHPDropdown = false\n                    showWebServerDropdown = false\n                    showDomainDropdown = false\n                }\n            }\n        }\n        .onHover { hovering in\n            isHovered = hovering\n        }\n        .alert(" = " ? Color.green.opacity(AppTokens.Opacity.light) : Color.clear)\n                        }\n                        .buttonStyle(.plain)\n                        .frame(width: 70)\n\n                        // 已安装的PHP版本\n                        ForEach(installedPHPVersions, id: \\.self) { version in\n                            Button(action: {\n                                changePHPVersion(version)\n                                withAnimation(.easeInOut(duration: 0.2)) {\n                                    showPHPDropdown = false\n                                }\n                            }) {\n                                Text(version)\n                                    .font(.system(size: 12))\n                                    .foregroundColor(version == site.phpVersion ? .green : .primary)\n                                    .frame(maxWidth: .infinity, alignment: .leading)\n                                    .padding(.horizontal, 8)\n                                    .padding(.vertical, 4)\n                                    .background(version == site.phpVersion ? Color.green.opacity(AppTokens.Opacity.light) : Color.clear)\n                            }\n                            .buttonStyle(.plain)\n                            .frame(width: 70)\n                        }\n                    }\n            .glass(.input)\n                    .shadow(color: .black.opacity(AppTokens.Opacity.hover), radius: 8, x: 0, y: 4)\n                    .overlay(\n                        RoundedRectangle(cornerRadius: 4)\n                            .stroke(AppColors.border, lineWidth: 1)\n                    )\n                    .offset(y: 20)\n                    .zIndex(10000)\n                    .transition(.opacity.combined(with: .scale(scale: 0.95)))\n                    .onDisappear {\n                        showPHPDropdown = false\n                    }\n                }\n            }\n            .onTapGesture { }  // 阻止点击穿透\n\n            // SSL证书\n            sslStatusView\n                .frame(width: 80, alignment: .leading)\n\n            // 操作按钮\n            actionButtons\n                .frame(width: 280, alignment: .leading)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 12)\n        .background(isHovered ? Color.gray.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n        .contentShape(Rectangle())\n        .onTapGesture {\n            // 点击行的其它地方时关闭下拉菜单\n            if showPHPDropdown || showWebServerDropdown || showDomainDropdown {\n                withAnimation(.easeInOut(duration: 0.2)) {\n                    showPHPDropdown = false\n                    showWebServerDropdown = false\n                    showDomainDropdown = false\n                }\n            }\n        }\n        .onHover { hovering in\n            isHovered = hovering\n        }\n        .alert(";
"\n        ]\n\n        return xssPatterns.contains { path.contains($0) }\n    }\n\n    // MARK: - SQL注入检测\n    private func detectSQLInjection(path: String) -> Bool {\n        let sqlPatterns = [\n            " = "\n        ]\n\n        return xssPatterns.contains { path.contains($0) }\n    }\n\n    // MARK: - SQL注入检测\n    private func detectSQLInjection(path: String) -> Bool {\n        let sqlPatterns = [\n            ";
"\n    @State private var isTestingConnection = false\n    @State private var connectionTestResult: (success: Bool, message: String)? = nil\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n            \n            Divider()\n            \n            // 表单内容\n            formContent\n            \n            Divider()\n            \n            // 底部按钮\n            bottomButtons\n        }\n        .frame(width: 600, height: 550)\n        .onAppear {\n            loadDatabaseData()\n        }\n        .alert(" = "\n    @State private var isTestingConnection = false\n    @State private var connectionTestResult: (success: Bool, message: String)? = nil\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n            \n            Divider()\n            \n            // 表单内容\n            formContent\n            \n            Divider()\n            \n            // 底部按钮\n            bottomButtons\n        }\n        .frame(width: 600, height: 550)\n        .onAppear {\n            loadDatabaseData()\n        }\n        .alert(";
") 配置存在: \\(path)" = ") 配置存在: \\(path)";
"已啟用 \\(Self.allCases.count) / \\(Self.maxLanguageCapacity)" = "已啟用 \\(Self.allCases.count) / \\(Self.maxLanguageCapacity)";
"\n        return owner\n    }\n\n    // MARK: - 文件操作\n\n    func createDirectory(at path: String) async throws {\n        try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true)\n    }\n\n    func delete(at path: String) async throws {\n        try fileManager.removeItem(atPath: path)\n    }\n\n    func rename(from: String, to: String) async throws {\n        try fileManager.moveItem(atPath: from, toPath: to)\n    }\n\n    func copy(from: String, to: String) async throws {\n        try fileManager.copyItem(atPath: from, toPath: to)\n    }\n\n    func move(from: String, to: String) async throws {\n        try fileManager.moveItem(atPath: from, toPath: to)\n    }\n\n    // MARK: - 权限管理\n\n    func getPermissions(at path: String) async throws -> FilePermissions {\n        let attributes = try fileManager.attributesOfItem(atPath: path)\n        guard let posixPermissions = attributes[.posixPermissions] as? NSNumber else {\n            throw FileSystemError.permissionDenied\n        }\n\n        let mode = posixPermissions.intValue\n        return FilePermissions(\n            owner: parsePermissionSet(mode: mode, shift: 6),\n            group: parsePermissionSet(mode: mode, shift: 3),\n            others: parsePermissionSet(mode: mode, shift: 0)\n        )\n    }\n\n    func setPermissions(at path: String, permissions: FilePermissions) async throws {\n        let octalValue = Int(permissions.octalString, radix: 8) ?? 0o644\n        try fileManager.setAttributes([.posixPermissions: octalValue], ofItemAtPath: path)\n    }\n\n    private func parsePermissionSet(mode: Int, shift: Int) -> FilePermissions.PermissionSet {\n        let mask = (mode >> shift) & 0o7\n        return FilePermissions.PermissionSet(\n            read: (mask & 0o4) != 0,\n            write: (mask & 0o2) != 0,\n            execute: (mask & 0o1) != 0\n        )\n    }\n\n    func getOwner(at path: String) async throws -> FileOwner {\n        let attributes = try fileManager.attributesOfItem(atPath: path)\n        let user = attributes[.ownerAccountName] as? String ?? " = "\n        return owner\n    }\n\n    // MARK: - 文件操作\n\n    func createDirectory(at path: String) async throws {\n        try fileManager.createDirectory(atPath: path, withIntermediateDirectories: true)\n    }\n\n    func delete(at path: String) async throws {\n        try fileManager.removeItem(atPath: path)\n    }\n\n    func rename(from: String, to: String) async throws {\n        try fileManager.moveItem(atPath: from, toPath: to)\n    }\n\n    func copy(from: String, to: String) async throws {\n        try fileManager.copyItem(atPath: from, toPath: to)\n    }\n\n    func move(from: String, to: String) async throws {\n        try fileManager.moveItem(atPath: from, toPath: to)\n    }\n\n    // MARK: - 权限管理\n\n    func getPermissions(at path: String) async throws -> FilePermissions {\n        let attributes = try fileManager.attributesOfItem(atPath: path)\n        guard let posixPermissions = attributes[.posixPermissions] as? NSNumber else {\n            throw FileSystemError.permissionDenied\n        }\n\n        let mode = posixPermissions.intValue\n        return FilePermissions(\n            owner: parsePermissionSet(mode: mode, shift: 6),\n            group: parsePermissionSet(mode: mode, shift: 3),\n            others: parsePermissionSet(mode: mode, shift: 0)\n        )\n    }\n\n    func setPermissions(at path: String, permissions: FilePermissions) async throws {\n        let octalValue = Int(permissions.octalString, radix: 8) ?? 0o644\n        try fileManager.setAttributes([.posixPermissions: octalValue], ofItemAtPath: path)\n    }\n\n    private func parsePermissionSet(mode: Int, shift: Int) -> FilePermissions.PermissionSet {\n        let mask = (mode >> shift) & 0o7\n        return FilePermissions.PermissionSet(\n            read: (mask & 0o4) != 0,\n            write: (mask & 0o2) != 0,\n            execute: (mask & 0o1) != 0\n        )\n    }\n\n    func getOwner(at path: String) async throws -> FileOwner {\n        let attributes = try fileManager.attributesOfItem(atPath: path)\n        let user = attributes[.ownerAccountName] as? String ?? ";
", brewName])\n            try await Task.sleep(nanoseconds: 1_000_000_000)\n        }\n\n        try await configureApachePort(port: newPort)\n        AppSettings.shared.apachePort = newPort\n\n        // 修改端口后自动重启服务\n        currentOperation = " = ", brewName])\n            try await Task.sleep(nanoseconds: 1_000_000_000)\n        }\n\n        try await configureApachePort(port: newPort)\n        AppSettings.shared.apachePort = newPort\n\n        // 修改端口后自动重启服务\n        currentOperation = ";
"\n    @State private var showAdvancedSettings = false\n    @State private var showAddSiteSheet = false\n    @State private var showRepairSheet = false  // 显示修复配置Sheet\n    @State private var showVhostSyncSheet = false  // 显示配置文件同步Sheet\n    @State private var showBatchImportSheet = false  // 显示批量导入Sheet\n    @State private var showHealthCheckSheet = false  // 显示健康检查Sheet\n    @State private var selectedSites: Set<UUID> = []\n    @State private var currentPage = 1\n    @State private var itemsPerPage = 10\n    @State private var showTrashOnly = false  // 是否只显示回收站的站点\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 顶部工具栏\n            topToolbar\n\n            Divider()\n\n            // 表格内容\n            tableContent\n\n            Divider()\n\n            // 底部分页\n            bottomPagination\n        }\n        .onAppear {\n            // 设置 Web 服务器状态检查函数\n            siteManager.checkWebServerRunning = { webServer in\n                let serviceName: String\n                switch webServer {\n                case .nginx:\n                    serviceName = " = "\n    @State private var showAdvancedSettings = false\n    @State private var showAddSiteSheet = false\n    @State private var showRepairSheet = false  // 显示修复配置Sheet\n    @State private var showVhostSyncSheet = false  // 显示配置文件同步Sheet\n    @State private var showBatchImportSheet = false  // 显示批量导入Sheet\n    @State private var showHealthCheckSheet = false  // 显示健康检查Sheet\n    @State private var selectedSites: Set<UUID> = []\n    @State private var currentPage = 1\n    @State private var itemsPerPage = 10\n    @State private var showTrashOnly = false  // 是否只显示回收站的站点\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 顶部工具栏\n            topToolbar\n\n            Divider()\n\n            // 表格内容\n            tableContent\n\n            Divider()\n\n            // 底部分页\n            bottomPagination\n        }\n        .onAppear {\n            // 设置 Web 服务器状态检查函数\n            siteManager.checkWebServerRunning = { webServer in\n                let serviceName: String\n                switch webServer {\n                case .nginx:\n                    serviceName = ";
"\n        }\n        return version\n    }\n\n    /// 获取安装命令\n    func getInstallCommand(phpVersion: String) -> String {\n        if isBuiltin {\n            return " = "\n        }\n        return version\n    }\n\n    /// 获取安装命令\n    func getInstallCommand(phpVersion: String) -> String {\n        if isBuiltin {\n            return ";
"分析失败: \\(error.localizedDescription)" = "分析失败: \\(error.localizedDescription)";
")\n                        .frame(width: 80, alignment: .trailing)\n                    Text(databaseName)\n                        .foregroundColor(.secondary)\n                    Spacer()\n                }\n\n                // 用户名（只读）\n                HStack {\n                    Text(" = ")\n                        .frame(width: 80, alignment: .trailing)\n                    Text(databaseName)\n                        .foregroundColor(.secondary)\n                    Spacer()\n                }\n\n                // 用户名（只读）\n                HStack {\n                    Text(";
"\n    }\n}\n\n// MARK: - 认证令牌模型\nstruct AuthToken: Codable {\n    let token: String\n    let createdAt: Date\n    let expiresAt: Date\n    let allowedIP: String\n\n    var isExpired: Bool {\n        return Date() > expiresAt\n    }\n\n    var isValid: Bool {\n        return !isExpired\n    }\n}\n\n// MARK: - phpMyAdmin 访问控制服务\n@MainActor\nclass PhpMyAdminService: ObservableObject {\n    static let shared = PhpMyAdminService()\n\n    @Published var localIPAddresses: [LocalIPAddress] = []\n    @Published var currentToken: AuthToken?\n    @Published var isDetectingIPs = false\n\n    // 令牌有效期（秒）\n    private let tokenValidityDuration: TimeInterval = 3600 // 1小时\n\n    // 令牌存储路径\n    private let tokenStorageURL: URL = {\n        let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!\n        let folderURL = appSupport.appendingPathComponent(" = "\n    }\n}\n\n// MARK: - 认证令牌模型\nstruct AuthToken: Codable {\n    let token: String\n    let createdAt: Date\n    let expiresAt: Date\n    let allowedIP: String\n\n    var isExpired: Bool {\n        return Date() > expiresAt\n    }\n\n    var isValid: Bool {\n        return !isExpired\n    }\n}\n\n// MARK: - phpMyAdmin 访问控制服务\n@MainActor\nclass PhpMyAdminService: ObservableObject {\n    static let shared = PhpMyAdminService()\n\n    @Published var localIPAddresses: [LocalIPAddress] = []\n    @Published var currentToken: AuthToken?\n    @Published var isDetectingIPs = false\n\n    // 令牌有效期（秒）\n    private let tokenValidityDuration: TimeInterval = 3600 // 1小时\n\n    // 令牌存储路径\n    private let tokenStorageURL: URL = {\n        let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!\n        let folderURL = appSupport.appendingPathComponent(";
"• 修复所有站点：批量强力修复所有站点的配置文件，并补齐缺失的 hosts 记录" = "• 修复所有站点：批量强力修复所有站点的配置文件，并补齐缺失的 hosts 记录";
"主配置: \\(brewNginxConf) -> \\(linkDest) (软链接)" = "主配置: \\(brewNginxConf) -> \\(linkDest) (软链接)";
")\n            }\n        }\n    }\n\n    // 默认 PHP 端口\n    static let defaultPHPPort = 9000\n\n    private init() {\n        self.defaultSiteRoot = UserDefaults.standard.string(forKey: " = ")\n            }\n        }\n    }\n\n    // 默认 PHP 端口\n    static let defaultPHPPort = 9000\n\n    private init() {\n        self.defaultSiteRoot = UserDefaults.standard.string(forKey: ";
"站点已创建成功，但 \\(serviceName) Web 服务器当前未运行。\\n\\n请启动 \\(serviceName) 后，网站才能正常访问。\\n\\n您可以在「服务管理」中启动 \\(serviceName)。" = "站点已创建成功，但 \\(serviceName) Web 服务器当前未运行。\\n\\n请启动 \\(serviceName) 后，网站才能正常访问。\\n\\n您可以在「服务管理」中启动 \\(serviceName)。";
") && service.installed {\n                servers.append(.apache)\n            }\n        }\n        return servers\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            headerView\n            Divider()\n\n            // 显示扫描目录列表\n            scanDirectoriesView\n            Divider()\n\n            if isScanning {\n                scanningView\n            } else if foundConfigs.isEmpty {\n                emptyView\n            } else {\n                configListView\n            }\n\n            Divider()\n            footerView\n        }\n        .frame(width: 800, height: 600)\n        .onAppear {\n            scanConfigs()\n        }\n        .alert(" = ") && service.installed {\n                servers.append(.apache)\n            }\n        }\n        return servers\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            headerView\n            Divider()\n\n            // 显示扫描目录列表\n            scanDirectoriesView\n            Divider()\n\n            if isScanning {\n                scanningView\n            } else if foundConfigs.isEmpty {\n                emptyView\n            } else {\n                configListView\n            }\n\n            Divider()\n            footerView\n        }\n        .frame(width: 800, height: 600)\n        .onAppear {\n            scanConfigs()\n        }\n        .alert(";
"操作失败: \\(message)" = "Operación fallida: \\(message)";
"\n                        showError = true\n                    }\n                }\n\n            case .useNewPort(let port):\n                // 使用新端口安装\n                do {\n                    try await softwareManager.installPHPWithPort(software, port: port)\n                } catch {\n                    errorMessage = error.localizedDescription\n                    showError = true\n                }\n\n            case .cancel:\n                break\n            }\n\n            // 清理状态\n            pendingPHPInstall = nil\n            phpPortConflictInfo = nil\n        }\n    }\n\n    private func handleUninstall(_ software: SoftwareVersion) async {\n        softwareToUninstall = software\n        showUninstallConfirm = true\n    }\n\n    private func performUninstall(_ software: SoftwareVersion) async {\n        do {\n            try await softwareManager.uninstallSoftware(software, category: selectedCategory)\n        } catch {\n            errorMessage = error.localizedDescription\n            showError = true\n        }\n    }\n\n    private func handleSetDefault(_ software: SoftwareVersion) async {\n        do {\n            try await softwareManager.setDefaultVersion(software, category: selectedCategory)\n        } catch {\n            errorMessage = error.localizedDescription\n            showError = true\n        }\n    }\n\n    private func handleStart(_ software: SoftwareVersion) async {\n        do {\n            try await softwareManager.startSoftware(software, category: selectedCategory)\n        } catch {\n            errorMessage = error.localizedDescription\n            showError = true\n        }\n    }\n\n    private func handleStop(_ software: SoftwareVersion) async {\n        do {\n            try await softwareManager.stopSoftware(software, category: selectedCategory)\n        } catch {\n            errorMessage = error.localizedDescription\n            showError = true\n        }\n    }\n\n    private func handleChangePort(_ software: SoftwareVersion) {\n        changingPortPHP = software\n        showPHPChangePortSheet = true\n    }\n\n    private func handlePHPPortChange(_ software: SoftwareVersion, newPort: Int) {\n        Task {\n            do {\n                switch software.name.lowercased() {\n                case " = "\n                        showError = true\n                    }\n                }\n\n            case .useNewPort(let port):\n                // Instalar con un nuevo puerto\n                do {\n                    try await softwareManager.installPHPWithPort(software, port: port)\n                } catch {\n                    errorMessage = error.localizedDescription\n                    showError = true\n                }\n\n            case .cancel:\n                break\n            }\n\n            // Limpiar estado\n            pendingPHPInstall = nil\n            phpPortConflictInfo = nil\n        }\n    }\n\n    private func handleUninstall(_ software: SoftwareVersion) async {\n        softwareToUninstall = software\n        showUninstallConfirm = true\n    }\n\n    private func performUninstall(_ software: SoftwareVersion) async {\n        do {\n            try await softwareManager.uninstallSoftware(software, category: selectedCategory)\n        } catch {\n            errorMessage = error.localizedDescription\n            showError = true\n        }\n    }\n\n    private func handleSetDefault(_ software: SoftwareVersion) async {\n        do {\n            try await softwareManager.setDefaultVersion(software, category: selectedCategory)\n        } catch {\n            errorMessage = error.localizedDescription\n            showError = true\n        }\n    }\n\n    private func handleStart(_ software: SoftwareVersion) async {\n        do {\n            try await softwareManager.startSoftware(software, category: selectedCategory)\n        } catch {\n            errorMessage = error.localizedDescription\n            showError = true\n        }\n    }\n\n    private func handleStop(_ software: SoftwareVersion) async {\n        do {\n            try await softwareManager.stopSoftware(software, category: selectedCategory)\n        } catch {\n            errorMessage = error.localizedDescription\n            showError = true\n        }\n    }\n\n    private func handleChangePort(_ software: SoftwareVersion) {\n        changingPortPHP = software\n        showPHPChangePortSheet = true\n    }\n\n    private func handlePHPPortChange(_ software: SoftwareVersion, newPort: Int) {\n        Task {\n            do {\n                switch software.name.lowercased() {\n                case ";
"\\(installedPhpCount)个" = "\\(installedPhpCount) elementos";
"站点 \\(site.name) 的配置文件已重新生成，并已补齐缺失的 hosts 记录" = "Se ha regenerado la configuración del sitio \\(site.name) y se han completado los registros faltantes en el archivo hosts";
", script]\n\n        let pipe = Pipe()\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            // 清理临时文件\n            try? FileManager.default.removeItem(atPath: tempPath)\n\n            if process.terminationStatus != 0 {\n                let errorData = pipe.fileHandleForReading.readDataToEndOfFile()\n                let errorMessage = String(data: errorData, encoding: .utf8) ?? " = ", script]\n\n        let pipe = Pipe()\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            // Limpiar archivos temporales\n            try? FileManager.default.removeItem(atPath: tempPath)\n\n            if process.terminationStatus != 0 {\n                let errorData = pipe.fileHandleForReading.readDataToEndOfFile()\n                let errorMessage = String(data: errorData, encoding: .utf8) ?? ";
"\n        return result\n    }\n}\n\n// MARK: - 文件所有者\n\n/// 文件所有者信息\nstruct FileOwner: Codable, Equatable {\n    let user: String\n    let group: String\n    let userId: Int?\n    let groupId: Int?\n}\n\n// MARK: - 编辑器类型\n\n/// 支持的编辑器类型\nenum EditorType: String, CaseIterable {\n    case vscode = " = "\n        return result\n    }\n}\n\n// MARK: - Propietario del archivo\n\n/// Información sobre el propietario del archivo\nstruct FileOwner: Codable, Equatable {\n    let user: String\n    let group: String\n    let userId: Int?\n    let groupId: Int?\n}\n\n// MARK: - Tipos de editor\n\n/// Tipos de editor compatibles\nenum EditorType: String, CaseIterable {\n    case vscode = ";
"加密失败: \\(error.localizedDescription)" = "Error al cifrar: \\(error.localizedDescription)";
", selection: $displayMode) {\n                ForEach(ExtensionDisplayMode.allCases, id: \\.self) { mode in\n                    Text(LocalizedStringKey(mode.rawValue)).tag(mode)\n                }\n            }\n            .pickerStyle(.segmented)\n            .frame(width: 200)\n\n            Spacer()\n\n            // 刷新按钮\n            Button(action: { refreshExtensions() }) {\n                Label(" = ", selection: $displayMode) {\n                ForEach(ExtensionDisplayMode.allCases, id: \\.self) { mode in\n                    Text(LocalizedStringKey(mode.rawValue)).tag(mode)\n                }\n            }\n            .pickerStyle(.segmented)\n            .frame(width: 200)\n\n            Spacer()\n\n            // Botón de actualización\n            Button(action: { refreshExtensions() }) {\n                Label(";
", nil)\n    }\n\n    /// 加载 MySQL 端口数据\n    private func loadMySQLPortData() -> (port: String, error: String?) {\n        let lines = configContent.components(separatedBy: .newlines)\n        if let value = extractValue(for: " = ", nil)\n    }\n\n    /// Cargar datos del puerto MySQL\n    private func loadMySQLPortData() -> (port: String, error: String?) {\n        let lines = configContent.components(separatedBy: .newlines)\n        if let value = extractValue(for: ";
"为 PHP \\(phpVersion) 配置新端口，不影响现有服务" = "Configurar un nuevo puerto para PHP \\(phpVersion), sin afectar los servicios existentes";
"\n        }\n\n        // 确保目录存在\n        try? FileManager.default.createDirectory(\n            atPath: configDir,\n            withIntermediateDirectories: true\n        )\n\n        // 生成配置\n        let listenPorts = mode == .takeOver ? " = "\n        }\n\n        // Asegurarse de que el directorio exista\n        try? FileManager.default.createDirectory(\n            atPath: configDir,\n            withIntermediateDirectories: true\n        )\n\n        // Generar configuración\n        let listenPorts = mode == .takeOver ? ";
"\n            if FileManager.default.fileExists(atPath: wsPath) {\n                try updateApacheListenPort(at: wsPath, port: port)\n                modified = true\n            }\n        }\n\n        // 同时修改 Homebrew 路径的配置\n        let brewPath = " = "\n            if FileManager.default.fileExists(atPath: wsPath) {\n                try updateApacheListenPort(at: wsPath, port: port)\n                modified = true\n            }\n        }\n\n        // Modificar también la configuración en la ruta de Homebrew\n        let brewPath = ";
"✅ .htpasswd 文件已生成: \\(htpasswdPath)" = "✅ El archivo .htpasswd se ha generado correctamente: \\(htpasswdPath)";
"确定要删除选中的 \\(selectedDatabases.count) 个数据库吗？此操作不可恢复。" = "¿Realmente desea eliminar las \\(selectedDatabases.count) bases de datos seleccionadas? Esta acción no se puede deshacer.";
"\n            } else {\n                // 不强制HTTPS：HTTP和HTTPS都可以访问\n                configContent = " = "\n            } else {\n                // Sin forzar HTTPS: tanto HTTP como HTTPS son accesibles\n                configContent = ";
"以下站点修复失败，请处理后重试：\\n\\(failedSites.joined(separator: " = "Falló la reparación de los siguientes sitios; por favor, resuelva los problemas y vuelva a intentarlo:\\n\\(failedSites.joined(separator: ";
")\n                    .foregroundColor(.secondary)\n                    .frame(width: 70, alignment: .trailing)\n                Text(databaseName)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n            }\n\n            // 用户名\n            HStack(spacing: 16) {\n                Text(" = ")\n                    .foregroundColor(.secondary)\n                    .frame(width: 70, alignment: .trailing)\n                Text(databaseName)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n            }\n\n            // Nombre de usuario\n            HStack(spacing: 16) {\n                Text(";
")\n                        .font(.caption2)\n                        .foregroundColor(.secondary)\n                }\n            }\n            .frame(width: 200, alignment: .leading)\n            .padding(.leading, 16)\n\n            // 当前值输入框\n            HStack(spacing: 4) {\n                TextField(" = ")\n                        .font(.caption2)\n                        .foregroundColor(.secondary)\n                }\n            }\n            .frame(width: 200, alignment: .leading)\n            .padding(.leading, 16)\n\n            // Campo de entrada para el valor actual\n            HStack(spacing: 4) {\n                TextField(";
"]\n                process.standardOutput = Pipe()\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // MARK: - mkcert CA 安装\n\n    /// 安装 mkcert CA（需要用户输入密码）\n    func installMkcertCA() async throws {\n        // 获取 mkcert 路径（优先使用内置版本）\n        let mkcertPath: String\n        if let bundled = getBundledMkcertPath() {\n            mkcertPath = bundled\n        } else if await isCommandAvailable(" = "]\n                process.standardOutput = Pipe()\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // MARK: - Instalación de la CA mkcert\n\n    /// Instalar la CA mkcert (requiere contraseña del usuario)\n    func installMkcertCA() async throws {\n        // Obtener la ruta de mkcert (priorizar la versión integrada)\n        let mkcertPath: String\n        if let bundled = getBundledMkcertPath() {\n            mkcertPath = bundled\n        } else if await isCommandAvailable(";
")\n                                    .map { $0.trimmingCharacters(in: .whitespaces) }\n                                    .filter { !$0.isEmpty }\n\n                                if let firstDomain = domainList.first {\n                                    // 如果站点名称为空，使用第一个域名\n                                    if siteName.isEmpty || siteName == previousDomain {\n                                        siteName = String(firstDomain)\n                                        previousDomain = String(firstDomain)\n                                    }\n                                    // 自动生成目录路径（使用第一个域名）\n                                    updateDocumentRoot()\n                                } else {\n                                    previousDomain = " = ")\n                                    .map { $0.trimmingCharacters(in: .whitespaces) }\n                                    .filter { !$0.isEmpty }\n\n                                if let firstDomain = domainList.first {\n                                    // Si el nombre del sitio está vacío, usar el primer dominio\n                                    if siteName.isEmpty || siteName == previousDomain {\n                                        siteName = String(firstDomain)\n                                        previousDomain = String(firstDomain)\n                                    }\n                                    // Generar automáticamente la ruta del directorio (usando el primer dominio)\n                                    updateDocumentRoot()\n                                } else {\n                                    previousDomain = ";
", $0) }.joined()\n    }\n\n    /// 验证令牌是否有效\n    func validateToken(_ token: String, forIP ip: String) -> Bool {\n        guard let currentToken = currentToken else {\n            return false\n        }\n\n        return currentToken.token == token &&\n               currentToken.allowedIP == ip &&\n               currentToken.isValid\n    }\n\n    /// 使令牌失效\n    func invalidateToken() {\n        currentToken = nil\n        try? FileManager.default.removeItem(at: tokenStorageURL)\n    }\n\n    /// 保存令牌到文件\n    private func saveToken(_ token: AuthToken) {\n        do {\n            let data = try JSONEncoder().encode(token)\n            try data.write(to: tokenStorageURL)\n        } catch {\n            print(" = ", $0) }.joined()\n    }\n\n    /// Validar si el token es válido\n    func validateToken(_ token: String, forIP ip: String) -> Bool {\n        guard let currentToken = currentToken else {\n            return false\n        }\n\n        return currentToken.token == token &&\n               currentToken.allowedIP == ip &&\n               currentToken.isValid\n    }\n\n    /// Invalidar el token\n    func invalidateToken() {\n        currentToken = nil\n        try? FileManager.default.removeItem(at: tokenStorageURL)\n    }\n\n    /// Guardar el token en un archivo\n    private func saveToken(_ token: AuthToken) {\n        do {\n            let data = try JSONEncoder().encode(token)\n            try data.write(to: tokenStorageURL)\n        } catch {\n            print(";
"\n        syncInstallingVersionProgress(software.id)\n\n        // 执行安装\n        let success = await runBrewCommandWithProgress(\n            brewPath,\n            arguments: [" = "\n        syncInstallingVersionProgress(software.id)\n\n        // Ejecutar la instalación\n        let success = await runBrewCommandWithProgress(\n            brewPath,\n            arguments: [";
"仅显示用户 \\(NSUserName()) 的进程" = "Mostrar solo los procesos del usuario \\(NSUserName())";
")\n                                    .foregroundColor(config.corsMethods.contains(method) ? .blue : .secondary)\n                                    .font(.system(size: 14))\n                            }\n                            .buttonStyle(.plain)\n                            \n                            Text(method)\n                                .font(.system(size: 13))\n                                .foregroundColor(.primary)\n                        }\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                    }\n                }\n            }\n            \n            // 请求头\n            VStack(alignment: .leading, spacing: 12) {\n                HStack(spacing: 4) {\n                    Text(LocalizedStringKey(" = ")\n                                    .foregroundColor(config.corsMethods.contains(method) ? .blue : .secondary)\n                                    .font(.system(size: 14))\n                            }\n                            .buttonStyle(.plain)\n                            \n                            Text(method)\n                                .font(.system(size: 13))\n                                .foregroundColor(.primary)\n                        }\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                    }\n                }\n            }\n            \n            // Encabezados de la solicitud\n            VStack(alignment: .leading, spacing: 12) {\n                HStack(spacing: 4) {\n                    Text(LocalizedStringKey(";
"\n        let userId = attributes[.ownerAccountID] as? Int\n        let groupId = attributes[.groupOwnerAccountID] as? Int\n\n        return FileOwner(user: user, group: group, userId: userId, groupId: groupId)\n    }\n\n    func setOwner(at path: String, owner: FileOwner) async throws {\n        // macOS 需要管理员权限才能修改所有者，这里暂不实现\n        throw FileSystemError.notImplemented\n    }\n\n    // MARK: - 文件内容\n\n    func readFile(at path: String) async throws -> Data {\n        return try Data(contentsOf: URL(fileURLWithPath: path))\n    }\n\n    func writeFile(at path: String, data: Data) async throws {\n        try data.write(to: URL(fileURLWithPath: path))\n    }\n\n    // MARK: - 快捷操作\n\n    func openInFileManager(at path: String) async throws {\n        NSWorkspace.shared.selectFile(path, inFileViewerRootedAtPath: " = "\n        let userId = attributes[.ownerAccountID] as? Int\n        let groupId = attributes[.groupOwnerAccountID] as? Int\n\n        return FileOwner(user: user, group: group, userId: userId, groupId: groupId)\n    }\n\n    func setOwner(at path: String, owner: FileOwner) async throws {\n        // En macOS se requieren permisos de administrador para modificar el propietario; por ahora no se implementa\n        throw FileSystemError.notImplemented\n    }\n\n    // MARK: - Contenido del archivo\n\n    func readFile(at path: String) async throws -> Data {\n        return try Data(contentsOf: URL(fileURLWithPath: path))\n    }\n\n    func writeFile(at path: String, data: Data) async throws {\n        try data.write(to: URL(fileURLWithPath: path))\n    }\n\n    // MARK: - Acciones rápidas\n\n    func openInFileManager(at path: String) async throws {\n        NSWorkspace.shared.selectFile(path, inFileViewerRootedAtPath: ";
"\n            # Apache 后端端口：\\(httpPort)\n            <VirtualHost *:\\(httpPort)>\n                ServerName \\(domains.first ?? " = "\n            # Puerto de backend de Apache: \\(httpPort)\n            <VirtualHost *:\\(httpPort)>\n                ServerName \\(domains.first ?? ";
" : appSettings.defaultSiteRoot) : siteDirectory\n        panel.directoryURL = URL(fileURLWithPath: defaultPath)\n\n        if panel.runModal() == .OK {\n            if let url = panel.url {\n                siteDirectory = url.path\n                checkDirectoryPermission()\n                updateDocumentRoot()\n            }\n        }\n    }\n    \n    // 检查目录权限\n    private func checkDirectoryPermission() {\n        guard !siteDirectory.isEmpty else {\n            directoryPermissionStatus = (false, false, nil)\n            return\n        }\n        \n        let siteDir = siteDirectory.hasSuffix(" = " : appSettings.defaultSiteRoot) : siteDirectory\n        panel.directoryURL = URL(fileURLWithPath: defaultPath)\n\n        if panel.runModal() == .OK {\n            if let url = panel.url {\n                siteDirectory = url.path\n                checkDirectoryPermission()\n                updateDocumentRoot()\n            }\n        }\n    }\n    \n    // Verificar permisos del directorio\n    private func checkDirectoryPermission() {\n        guard !siteDirectory.isEmpty else {\n            directoryPermissionStatus = (false, false, nil)\n            return\n        }\n        \n        let siteDir = siteDirectory.hasSuffix(";
"✅ 成功更新 \\(updatedFiles.count) 个配置文件:" = "✅ Configuraciones actualizadas correctamente en \\(updatedFiles.count) archivos:";
"brew 输出:\\n\\(startResult.output)" = "Salida de brew:\\n\\(startResult.output)";
"[SiteManager] 恢复 hosts 条目失败: \\(domain) - \\(error)" = "[SiteManager] Error al restaurar la entrada en hosts: \\(domain) - \\(error)";
"查看回收站" = "Ver papelera";
"目录不存在：\\(directoryPath)" = "El directorio no existe: \\(directoryPath)";
",\n                validFrom: certInfo?.validFrom ?? Date(),\n                validUntil: certInfo?.validUntil ?? Calendar.current.date(byAdding: .day, value: 365, to: Date()) ?? Date(),\n                siteId: siteId\n            )\n            CertificateManager.shared.addCertificate(certificate)\n            certificateId = certificate.id\n        }\n\n        // 创建新站点\n        // 根据 SSL 配置确定监听端口\n        let ports: [Int] = enableSSL ? [80, 443] : [80]\n\n        let newSite = Site(\n            id: siteId,\n            name: finalName,\n            domains: domainList,\n            documentRoot: documentRoot,\n            publicDirectory: publicDirectory,\n            webServer: webServer,\n            phpVersion: phpVersion,\n            sslStatus: enableSSL ? .enabled : .notConfigured,\n            status: .stopped,\n            isEnabled: false,\n            hasBackup: false,\n            backupCount: 0,\n            dailyTraffic: " = ",\n                validFrom: certInfo?.validFrom ?? Date(),\n                validUntil: certInfo?.validUntil ?? Calendar.current.date(byAdding: .day, value: 365, to: Date()) ?? Date(),\n                siteId: siteId\n            )\n            CertificateManager.shared.addCertificate(certificate)\n            certificateId = certificate.id\n        }\n\n        // Crear nuevo sitio\n        // Determinar el puerto de escucha según la configuración SSL\n        let ports: [Int] = enableSSL ? [80, 443] : [80]\n\n        let newSite = Site(\n            id: siteId,\n            name: finalName,\n            domains: domainList,\n            documentRoot: documentRoot,\n            publicDirectory: publicDirectory,\n            webServer: webServer,\n            phpVersion: phpVersion,\n            sslStatus: enableSSL ? .enabled : .notConfigured,\n            status: .stopped,\n            isEnabled: false,\n            hasBackup: false,\n            backupCount: 0,\n            dailyTraffic: ";
"\n    @State private var showDefaultRootSettings = false\n    @State private var showPHPStoppedWarning = false  // PHP已停止警告\n    @State private var showServiceStoppedWarning = false  // 服务已停止警告（多服务）\n    @State private var stoppedServices: [String] = []  // 已停止的服务列表\n    @StateObject private var hostsManager = HostsManager()\n\n    // PHP版本信息结构\n    struct PHPVersionInfo: Identifiable {\n        let id: String\n        let version: String\n        let isRunning: Bool\n\n        var displayName: String {\n            if isRunning {\n                return version\n            } else {\n                return " = "\n    @State private var showDefaultRootSettings = false\n    @State private var showPHPStoppedWarning = false  // Advertencia: PHP detenido\n    @State private var showServiceStoppedWarning = false  // Advertencia: servicio detenido (múltiples servicios)\n    @State private var stoppedServices: [String] = []  // Lista de servicios detenidos\n    @StateObject private var hostsManager = HostsManager()\n\n    // Estructura de información de versión de PHP\n    struct PHPVersionInfo: Identifiable {\n        let id: String\n        let version: String\n        let isRunning: Bool\n\n        var displayName: String {\n            if isRunning {\n                return version\n            } else {\n                return ";
"\n\n        // 检查是否有文件存在性检查条件\n        let hasFileCheck = conditions.contains { cond in\n            cond.contains(" = "\n\n        // Verificar si existen condiciones de comprobación de existencia de archivos\n        let hasFileCheck = conditions.contains { cond in\n            cond.contains(";
"证书申请失败：\\(message)" = "Error al solicitar el certificado: \\(message)";
")\n        }\n    }\n\n    // 默认站点端口\n    @Published var defaultSitePort: Int {\n        didSet {\n            UserDefaults.standard.set(defaultSitePort, forKey: " = ")\n        }\n    }\n\n    // Puerto predeterminado del sitio\n    @Published var defaultSitePort: Int {\n        didSet {\n            UserDefaults.standard.set(defaultSitePort, forKey: ";
")\n\n        // 建议2：修改配置使用其他端口\n        let name = software.name.lowercased()\n        if name.contains(" = ")\n\n        // Sugerencia 2: Modifique la configuración para usar otro puerto\n        let name = software.name.lowercased()\n        if name.contains(";
":\n            configPaths = []  // Memcached 通常通过命令行参数配置\n        case " = ":\n            configPaths = []  // Memcached normalmente se configura mediante parámetros de línea de comandos\n        case ";
")\n                    .foregroundColor(.secondary)\n            }\n        }\n    }\n\n    private var actionButton: some View {\n        Group {\n            if isOperating {\n                // 显示详细进度信息（如果有）\n                if let progress = extensionInfo.installProgress {\n                    progressDetailView(progress)\n                } else {\n                    ProgressView()\n                        .scaleEffect(0.7)\n                }\n            } else if extensionInfo.isEnabled {\n                Button(" = ")\n                    .foregroundColor(.secondary)\n            }\n        }\n    }\n\n    private var actionButton: some View {\n        Group {\n            if isOperating {\n                // Mostrar información detallada de progreso (si está disponible)\n                if let progress = extensionInfo.installProgress {\n                    progressDetailView(progress)\n                } else {\n                    ProgressView()\n                        .scaleEffect(0.7)\n                }\n            } else if extensionInfo.isEnabled {\n                Button(";
"\n                ]\n\n                guard let brewPath = brewPaths.first(where: { FileManager.default.fileExists(atPath: $0) }) else {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                // 步骤1: 停止 MySQL 服务\n                let stopProcess = Process()\n                stopProcess.executableURL = URL(fileURLWithPath: brewPath)\n                stopProcess.arguments = [" = "\n                ]\n\n                guard let brewPath = brewPaths.first(where: { FileManager.default.fileExists(atPath: $0) }) else {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                // 步骤1: 停止 MySQL 服务\n                let stopProcess = Process()\n                stopProcess.executableURL = URL(fileURLWithPath: brewPath)\n                stopProcess.arguments = [";
"\n\n    var body: some View {\n        HStack(spacing: 0) {\n            // 配置项名称\n            VStack(alignment: .leading, spacing: 2) {\n                Text(key)\n                    .font(.system(.body, design: .monospaced))\n                if let unit = unit {\n                    Text(" = "\n\n    var body: some View {\n        HStack(spacing: 0) {\n            // 配置项名称\n            VStack(alignment: .leading, spacing: 2) {\n                Text(key)\n                    .font(.system(.body, design: .monospaced))\n                if let unit = unit {\n                    Text(";
") {\n                showChangePasswordSheet = true\n            }\n            .buttonStyle(.bordered)\n            .controlSize(.small)\n\n            // 删除按钮\n            Button(" = ") {\n                showChangePasswordSheet = true\n            }\n            .buttonStyle(.bordered)\n            .controlSize(.small)\n\n            // 删除按钮\n            Button(";
" : runningDirectory\n\n                try await siteManager.updateSite(\n                    site: site,\n                    name: siteName,\n                    domains: domainList,\n                    documentRoot: documentRoot,\n                    publicDirectory: finalPublicDir,\n                    webServer: selectedWebServer,\n                    phpVersion: selectedPHPVersion,\n                    enableSSL: enableSSL,\n                    forceHttps: forceHttps,\n                    expiryDate: expiryDate.isEmpty ? nil : expiryDate,\n                    note: note\n                )\n                dismiss()\n            } catch {\n                errorMessage = error.localizedDescription\n                showError = true\n                isSubmitting = false\n            }\n        }\n    }\n\n    // 更新 documentRoot（网站目录 + 运行目录）\n    private func updateDocumentRoot() {\n        if runningDirectory.isEmpty || runningDirectory == " = " : runningDirectory\n\n                try await siteManager.updateSite(\n                    site: site,\n                    name: siteName,\n                    domains: domainList,\n                    documentRoot: documentRoot,\n                    publicDirectory: finalPublicDir,\n                    webServer: selectedWebServer,\n                    phpVersion: selectedPHPVersion,\n                    enableSSL: enableSSL,\n                    forceHttps: forceHttps,\n                    expiryDate: expiryDate.isEmpty ? nil : expiryDate,\n                    note: note\n                )\n                dismiss()\n            } catch {\n                errorMessage = error.localizedDescription\n                showError = true\n                isSubmitting = false\n            }\n        }\n    }\n\n    // 更新 documentRoot（网站目录 + 运行目录）\n    private func updateDocumentRoot() {\n        if runningDirectory.isEmpty || runningDirectory == ";
"有 \\(missingDomains.count) 个域名配置异常" = "有 \\(missingDomains.count) 个域名配置异常";
") {\n                    do {\n                        let (data, _) = try await URLSession.shared.data(from: url)\n                        if let img = NSImage(data: data) {\n                            await MainActor.run {\n                                remoteDouyinImage = img\n                            }\n                        }\n                    } catch {\n                        // 加载失败，保留默认本地图片\n                    }\n                }\n            }\n            \n            // 初始化使用时间（用于判断是否显示抖音引导）\n            if douyinGuideFirstLaunchTime == 0 {\n                douyinGuideFirstLaunchTime = Date().timeIntervalSince1970\n            } else if !hasShownDouyinGuide {\n                // 如果不是第一次打开，并且距离首次打开超过 3 天 (3 * 24 * 3600 秒)，或者为了演示可以改短点比如 3 分钟 (180秒)\n                // 这里我们设定为正常使用 3 天后弹出 (259200秒)\n                let timePassed = Date().timeIntervalSince1970 - douyinGuideFirstLaunchTime\n                if timePassed > 259200 { // 3天后弹出 (259200秒)\n                    withAnimation(.easeInOut(duration: 0.5).delay(2.0)) {\n                        showDouyinGuide = true\n                    }\n                }\n            }\n        }\n    }\n    \n    // 后台异步处理图片缩小和模糊，不阻塞主线程\n    private func processAndBlurImage(_ image: NSImage) async -> NSImage? {\n        let imageData = image.tiffRepresentation\n        let imageSize = image.size\n\n        return await Task.detached(priority: .background) { () -> BlurredImageResult in\n            guard let imageData,\n                  let bitmap = NSBitmapImageRep(data: imageData),\n                  let ciImage = CIImage(bitmapImageRep: bitmap) else {\n                return BlurredImageResult(image: nil)\n            }\n\n            let targetWidth: CGFloat = 800\n            let scale = targetWidth / imageSize.width\n            let scaledImage = ciImage.transformed(by: CGAffineTransform(scaleX: scale, y: scale))\n\n            let blurFilter = CIFilter.gaussianBlur()\n            blurFilter.inputImage = scaledImage\n            blurFilter.radius = 40.0\n\n            guard let outputImage = blurFilter.outputImage else {\n                return BlurredImageResult(image: nil)\n            }\n\n            let croppedImage = outputImage.cropped(to: scaledImage.extent)\n\n            let context = CIContext(options: [.useSoftwareRenderer: false])\n            guard let cgImage = context.createCGImage(croppedImage, from: croppedImage.extent) else {\n                return BlurredImageResult(image: nil)\n            }\n\n            let resultImage = NSImage(\n                cgImage: cgImage,\n                size: NSSize(width: croppedImage.extent.width, height: croppedImage.extent.height)\n            )\n            return BlurredImageResult(image: resultImage)\n        }.value.image ?? image\n    }\n    \n    private func loadBackgroundImage() -> NSImage? {\n        let assetName = colorScheme == .dark ? " = ") {\n                    do {\n                        let (data, _) = try await URLSession.shared.data(from: url)\n                        if let img = NSImage(data: data) {\n                            await MainActor.run {\n                                remoteDouyinImage = img\n                            }\n                        }\n                    } catch {\n                        // 加载失败，保留默认本地图片\n                    }\n                }\n            }\n            \n            // 初始化使用时间（用于判断是否显示抖音引导）\n            if douyinGuideFirstLaunchTime == 0 {\n                douyinGuideFirstLaunchTime = Date().timeIntervalSince1970\n            } else if !hasShownDouyinGuide {\n                // 如果不是第一次打开，并且距离首次打开超过 3 天 (3 * 24 * 3600 秒)，或者为了演示可以改短点比如 3 分钟 (180秒)\n                // 这里我们设定为正常使用 3 天后弹出 (259200秒)\n                let timePassed = Date().timeIntervalSince1970 - douyinGuideFirstLaunchTime\n                if timePassed > 259200 { // 3天后弹出 (259200秒)\n                    withAnimation(.easeInOut(duration: 0.5).delay(2.0)) {\n                        showDouyinGuide = true\n                    }\n                }\n            }\n        }\n    }\n    \n    // 后台异步处理图片缩小和模糊，不阻塞主线程\n    private func processAndBlurImage(_ image: NSImage) async -> NSImage? {\n        let imageData = image.tiffRepresentation\n        let imageSize = image.size\n\n        return await Task.detached(priority: .background) { () -> BlurredImageResult in\n            guard let imageData,\n                  let bitmap = NSBitmapImageRep(data: imageData),\n                  let ciImage = CIImage(bitmapImageRep: bitmap) else {\n                return BlurredImageResult(image: nil)\n            }\n\n            let targetWidth: CGFloat = 800\n            let scale = targetWidth / imageSize.width\n            let scaledImage = ciImage.transformed(by: CGAffineTransform(scaleX: scale, y: scale))\n\n            let blurFilter = CIFilter.gaussianBlur()\n            blurFilter.inputImage = scaledImage\n            blurFilter.radius = 40.0\n\n            guard let outputImage = blurFilter.outputImage else {\n                return BlurredImageResult(image: nil)\n            }\n\n            let croppedImage = outputImage.cropped(to: scaledImage.extent)\n\n            let context = CIContext(options: [.useSoftwareRenderer: false])\n            guard let cgImage = context.createCGImage(croppedImage, from: croppedImage.extent) else {\n                return BlurredImageResult(image: nil)\n            }\n\n            let resultImage = NSImage(\n                cgImage: cgImage,\n                size: NSSize(width: croppedImage.extent.width, height: croppedImage.extent.height)\n            )\n            return BlurredImageResult(image: resultImage)\n        }.value.image ?? image\n    }\n    \n    private func loadBackgroundImage() -> NSImage? {\n        let assetName = colorScheme == .dark ? ";
" {\n                            var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))\n                            getnameinfo(\n                                interface.ifa_addr,\n                                socklen_t(interface.ifa_addr.pointee.sa_len),\n                                &hostname,\n                                socklen_t(hostname.count),\n                                nil,\n                                0,\n                                NI_NUMERICHOST\n                            )\n\n                            let address = String(cString: hostname)\n\n                            // 过滤有效的 IP 地址\n                            if self.isValidIPAddress(address) {\n                                result.append(LocalIPAddress(\n                                    address: address,\n                                    interfaceName: name,\n                                    isLoopback: false\n                                ))\n                            }\n                        }\n                    }\n\n                    guard let next = interface.ifa_next else { break }\n                    ptr = next\n                }\n\n                continuation.resume(returning: result)\n            }\n        }\n\n        localIPAddresses = addresses\n        isDetectingIPs = false\n    }\n\n    /// 验证 IP 地址是否有效\n    private nonisolated func isValidIPAddress(_ address: String) -> Bool {\n        // 排除无效地址\n        if address.isEmpty || address == " = " {\n                            var hostname = [CChar](repeating: 0, count: Int(NI_MAXHOST))\n                            getnameinfo(\n                                interface.ifa_addr,\n                                socklen_t(interface.ifa_addr.pointee.sa_len),\n                                &hostname,\n                                socklen_t(hostname.count),\n                                nil,\n                                0,\n                                NI_NUMERICHOST\n                            )\n\n                            let address = String(cString: hostname)\n\n                            // 过滤有效的 IP 地址\n                            if self.isValidIPAddress(address) {\n                                result.append(LocalIPAddress(\n                                    address: address,\n                                    interfaceName: name,\n                                    isLoopback: false\n                                ))\n                            }\n                        }\n                    }\n\n                    guard let next = interface.ifa_next else { break }\n                    ptr = next\n                }\n\n                continuation.resume(returning: result)\n            }\n        }\n\n        localIPAddresses = addresses\n        isDetectingIPs = false\n    }\n\n    /// 验证 IP 地址是否有效\n    private nonisolated func isValidIPAddress(_ address: String) -> Bool {\n        // 排除无效地址\n        if address.isEmpty || address == ";
")\n                }\n\n                continuation.resume(returning: candidates)\n            }\n        }\n    }\n}\n\nenum SiteError: LocalizedError {\n    case invalidInput(String)\n    case duplicateName(String)\n    case operationFailed(String)\n\n    var errorDescription: String? {\n        switch self {\n        case .invalidInput(let message),\n             .duplicateName(let message),\n             .operationFailed(let message):\n            return message\n        }\n    }\n}\n\n/// 配置完整性状态\nenum ConfigCompletenessStatus {\n    case complete              // 配置完整\n    case needsProxyConfig      // Apache配置存在，但缺少Nginx代理配置\n    case missingBackendConfig  // Nginx代理配置存在，但缺少Apache后端配置\n    case conflictingConfigs    // 同时存在完整的Nginx配置和Apache配置（需要用户选择）\n}\n\n/// 扫描到的 vhost 配置信息\nstruct FoundVhostConfig: Identifiable {\n    let id = UUID()\n    let parsed: ParsedSiteConfig      // 解析出的配置信息\n    let filePath: String               // 配置文件路径\n    var alreadyExists: Bool            // 是否已在站点列表中\n    var hasConflict: Bool              // 是否有冲突\n    var conflictWith: WebServerType?   // 与哪个服务器冲突\n    var completenessStatus: ConfigCompletenessStatus  // 配置完整性状态\n    var relatedConfigPath: String?     // 相关配置文件路径（如Apache的Nginx代理路径）\n\n    /// 获取显示名称\n    var displayName: String {\n        parsed.domains.first ?? parsed.fileName\n    }\n\n    /// 获取冲突描述\n    var conflictDescription: String? {\n        guard hasConflict, let conflictServer = conflictWith else { return nil }\n        return " = ")\n                }\n\n                continuation.resume(returning: candidates)\n            }\n        }\n    }\n}\n\nenum SiteError: LocalizedError {\n    case invalidInput(String)\n    case duplicateName(String)\n    case operationFailed(String)\n\n    var errorDescription: String? {\n        switch self {\n        case .invalidInput(let message),\n             .duplicateName(let message),\n             .operationFailed(let message):\n            return message\n        }\n    }\n}\n\n/// 配置完整性状态\nenum ConfigCompletenessStatus {\n    case complete              // 配置完整\n    case needsProxyConfig      // Apache配置存在，但缺少Nginx代理配置\n    case missingBackendConfig  // Nginx代理配置存在，但缺少Apache后端配置\n    case conflictingConfigs    // 同时存在完整的Nginx配置和Apache配置（需要用户选择）\n}\n\n/// 扫描到的 vhost 配置信息\nstruct FoundVhostConfig: Identifiable {\n    let id = UUID()\n    let parsed: ParsedSiteConfig      // 解析出的配置信息\n    let filePath: String               // 配置文件路径\n    var alreadyExists: Bool            // 是否已在站点列表中\n    var hasConflict: Bool              // 是否有冲突\n    var conflictWith: WebServerType?   // 与哪个服务器冲突\n    var completenessStatus: ConfigCompletenessStatus  // 配置完整性状态\n    var relatedConfigPath: String?     // 相关配置文件路径（如Apache的Nginx代理路径）\n\n    /// 获取显示名称\n    var displayName: String {\n        parsed.domains.first ?? parsed.fileName\n    }\n\n    /// 获取冲突描述\n    var conflictDescription: String? {\n        guard hasConflict, let conflictServer = conflictWith else { return nil }\n        return ";
") == true {\n                usedPorts.append(port)\n            }\n        }\n\n        return (!usedPorts.isEmpty, usedPorts)\n    }\n\n    /// 检测单个端口是否可用\n    func checkPort(_ port: Int) async -> PortInfo {\n        await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let result = self.checkPortSync(port)\n                continuation.resume(returning: result)\n            }\n        }\n    }\n\n    /// 同步检测端口\n    private func checkPortSync(_ port: Int) -> PortInfo {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = ") == true {\n                usedPorts.append(port)\n            }\n        }\n\n        return (!usedPorts.isEmpty, usedPorts)\n    }\n\n    /// 检测单个端口是否可用\n    func checkPort(_ port: Int) async -> PortInfo {\n        await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let result = self.checkPortSync(port)\n                continuation.resume(returning: result)\n            }\n        }\n    }\n\n    /// 同步检测端口\n    private func checkPortSync(_ port: Int) -> PortInfo {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: ";
") {\n                let parts = trimmed.components(separatedBy: .whitespaces)\n                for part in parts.dropFirst() {\n                    if !part.isEmpty {\n                        domains.append(part)\n                    }\n                }\n            }\n\n            // 解析 DocumentRoot\n            if trimmed.hasPrefix(" = ") {\n                let parts = trimmed.components(separatedBy: .whitespaces)\n                for part in parts.dropFirst() {\n                    if !part.isEmpty {\n                        domains.append(part)\n                    }\n                }\n            }\n\n            // 解析 DocumentRoot\n            if trimmed.hasPrefix(";
" {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(maxWidth: isEmbedded ? .infinity : 900, maxHeight: isEmbedded ? .infinity : 700)\n        .sheetWithLocale(isPresented: $showAddEntrySheet) {\n            AddHostEntrySheet()\n                .environmentObject(hostsManager)\n        }\n        .sheetWithLocale(item: $editingEntry) { entry in\n            EditHostEntrySheet(entry: entry)\n                .environmentObject(hostsManager)\n        }\n    }\n\n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 工具栏\n            toolbar\n\n            Divider()\n\n            // 内容区域\n            contentArea\n        }\n    }\n\n    // MARK: - V2 视图组件\n    private var headerBarV2: some View {\n        HStack {\n            Text(" = " {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(maxWidth: isEmbedded ? .infinity : 900, maxHeight: isEmbedded ? .infinity : 700)\n        .sheetWithLocale(isPresented: $showAddEntrySheet) {\n            AddHostEntrySheet()\n                .environmentObject(hostsManager)\n        }\n        .sheetWithLocale(item: $editingEntry) { entry in\n            EditHostEntrySheet(entry: entry)\n                .environmentObject(hostsManager)\n        }\n    }\n\n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 工具栏\n            toolbar\n\n            Divider()\n\n            // 内容区域\n            contentArea\n        }\n    }\n\n    // MARK: - V2 视图组件\n    private var headerBarV2: some View {\n        HStack {\n            Text(";
")\n    }\n}\n\n/// 端口同步服务\nclass PortSyncService {\n    static let shared = PortSyncService()\n\n    private let configManager = PortSyncConfigManager.shared\n    private let fileManager = FileManager.default\n\n    private init() {}\n\n    /// 同步软件端口修改到相关配置文件\n    /// - Parameters:\n    ///   - software: 软件名称（如 " = ")\n    }\n}\n\n/// 端口同步服务\nclass PortSyncService {\n    static let shared = PortSyncService()\n\n    private let configManager = PortSyncConfigManager.shared\n    private let fileManager = FileManager.default\n\n    private init() {}\n\n    /// 同步软件端口修改到相关配置文件\n    /// - Parameters:\n    ///   - software: 软件名称（如 ";
"\n        ),\n\n        // ===== 其他 =====\n        " = "\n        ),\n\n        // ===== 其他 =====\n        ";
")\n                }\n            } else {\n                // 其他服务直接在末尾添加\n                result.append(" = ")\n                }\n            } else {\n                // 其他服务直接在末尾添加\n                result.append(";
"\n        }\n\n        // 检查每个 Apache 配置是否有对应的 Nginx 代理\n        for apacheConfig in apacheConfigs {\n            var updatedConfig = apacheConfig\n\n            // 查找是否有对应的 Nginx 代理配置\n            let matchingProxy = nginxProxyConfigs.first { proxyConfig in\n                // 检查域名是否匹配（考虑默认配置的特殊情况）\n                configsMatch(proxyConfig, apacheConfig)\n            }\n\n            if let proxy = matchingProxy {\n                // 有对应的 Nginx 代理，配置完整\n                updatedConfig.completenessStatus = .complete\n                updatedConfig.relatedConfigPath = proxy.filePath\n            } else {\n                // 缺少 Nginx 代理配置\n                updatedConfig.completenessStatus = .needsProxyConfig\n                // 计算应该存在的 Nginx 代理配置路径\n                let siteName = apacheConfig.parsed.fileName.replacingOccurrences(of: " = "\n        }\n\n        // 检查每个 Apache 配置是否有对应的 Nginx 代理\n        for apacheConfig in apacheConfigs {\n            var updatedConfig = apacheConfig\n\n            // 查找是否有对应的 Nginx 代理配置\n            let matchingProxy = nginxProxyConfigs.first { proxyConfig in\n                // 检查域名是否匹配（考虑默认配置的特殊情况）\n                configsMatch(proxyConfig, apacheConfig)\n            }\n\n            if let proxy = matchingProxy {\n                // 有对应的 Nginx 代理，配置完整\n                updatedConfig.completenessStatus = .complete\n                updatedConfig.relatedConfigPath = proxy.filePath\n            } else {\n                // 缺少 Nginx 代理配置\n                updatedConfig.completenessStatus = .needsProxyConfig\n                // 计算应该存在的 Nginx 代理配置路径\n                let siteName = apacheConfig.parsed.fileName.replacingOccurrences(of: ";
",\n                configLocations: [\n                    // 工作空间的 servers 目录\n                    ConfigFileLocation(\n                        workspacePath: " = ",\n                configLocations: [\n                    // 工作空间的 servers 目录\n                    ConfigFileLocation(\n                        workspacePath: ";
")\n                            .foregroundColor(.red)\n                        Text(error)\n                            .font(.caption)\n                            .foregroundColor(.red)\n                    }\n                }\n            }\n            .padding()\n\n            Divider()\n\n            // 底部按钮\n            HStack {\n                Spacer()\n                Button(" = ")\n                            .foregroundColor(.red)\n                        Text(error)\n                            .font(.caption)\n                            .foregroundColor(.red)\n                    }\n                }\n            }\n            .padding()\n\n            Divider()\n\n            // 底部按钮\n            HStack {\n                Spacer()\n                Button(";
")\n                // 如果扫描失败，显示空结果\n                scanResults = SecurityScanResults(\n                    scanTime: Date(),\n                    xssCount: 0,\n                    sqlCount: 0,\n                    scanCount: 0,\n                    phpCount: 0,\n                    totalScanned: 0,\n                    totalThreats: 0,\n                    topIPs: []\n                )\n            }\n\n            isScanning = false\n        }\n    }\n\n    private func formatDate(_ date: Date) -> String {\n        let formatter = DateFormatter()\n        formatter.dateFormat = " = ")\n                // 如果扫描失败，显示空结果\n                scanResults = SecurityScanResults(\n                    scanTime: Date(),\n                    xssCount: 0,\n                    sqlCount: 0,\n                    scanCount: 0,\n                    phpCount: 0,\n                    totalScanned: 0,\n                    totalThreats: 0,\n                    topIPs: []\n                )\n            }\n\n            isScanning = false\n        }\n    }\n\n    private func formatDate(_ date: Date) -> String {\n        let formatter = DateFormatter()\n        formatter.dateFormat = ";
", required: false) {\n                    Text(database.name)\n                        .foregroundColor(.secondary)\n                }\n\n                // 文件选择\n                FormField(" = ", required: false) {\n                    Text(database.name)\n                        .foregroundColor(.secondary)\n                }\n\n                // 文件选择\n                FormField(";
")\n    }()\n\n    init() {\n        setupStatusBar()\n        // 启动全局服务状态监控器\n        ServiceStatusMonitor.shared.startMonitoring()\n        // 订阅状态变化\n        subscribeToStatusMonitor()\n        Task {\n            await loadServicesWithCache()\n        }\n    }\n\n    private func setupStatusBar() {\n        statusBarManager.setupStatusBar(appState: self)\n    }\n\n    /// 订阅全局状态监控器\n    private func subscribeToStatusMonitor() {\n        let monitor = ServiceStatusMonitor.shared\n\n        // 当运行状态变化时，更新仪表盘服务列表的运行状态\n        monitor.$runningServices\n            .dropFirst()\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] running in\n                self?.updateServicesRunningState(running)\n            }\n            .store(in: &cancellables)\n    }\n\n    /// 更新服务列表的运行状态\n    private func updateServicesRunningState(_ running: Set<String>) {\n        for i in services.indices {\n            // 跳过正在过渡中的服务（启动中/停止中）\n            if services[i].state == .starting || services[i].state == .stopping {\n                continue\n            }\n            if let brewName = services[i].brewName {\n                let newState: ServiceState = running.contains(brewName) ? .running : .stopped\n                if services[i].state != newState {\n                    services[i].state = newState\n                }\n                // 运行中的服务清除停止原因\n                if newState == .running {\n                    services[i].stopReason = nil\n                }\n            }\n        }\n\n        // 收集已停止的服务，在后台诊断停止原因\n        var stoppedServices: [(index: Int, service: ServiceStatus)] = []\n        for i in services.indices {\n            if services[i].installed && services[i].state == .stopped {\n                stoppedServices.append((i, services[i]))\n            }\n        }\n\n        guard !stoppedServices.isEmpty else { return }\n\n        // 预计算配置路径（ConfigPathProvider 是 @MainActor）\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        var configPathsMap: [Int: [String]] = [:]\n        for item in stoppedServices {\n            configPathsMap[item.index] = getServiceConfigPaths(\n                for: item.service, homebrewPrefix: homebrewPrefix\n            )\n        }\n\n        // 收集最近错误日志\n        let fiveMinutesAgo = Date().addingTimeInterval(-300)\n        var recentErrorIds: Set<Int> = []\n        for item in stoppedServices {\n            let svcName = item.service.name\n            if ErrorLogManager.shared.hasLogsForSoftware(named: svcName) {\n                let logs = ErrorLogManager.shared.getLogsForSoftware(named: svcName, limit: 1)\n                if let lastLog = logs.first, lastLog.timestamp > fiveMinutesAgo {\n                    recentErrorIds.insert(item.index)\n                }\n            }\n        }\n\n        let brewPath = Self.cachedBrewPath ?? resolveBrewPath()\n\n        DispatchQueue.global(qos: .utility).async { [weak self] in\n            var results: [Int: StopReason] = [:]\n            for item in stoppedServices {\n                let paths = configPathsMap[item.index] ?? []\n                let hasRecentError = recentErrorIds.contains(item.index)\n                results[item.index] = Self.diagnoseServiceStopReason(\n                    service: item.service,\n                    brewPath: brewPath,\n                    homebrewPrefix: homebrewPrefix,\n                    configPaths: paths,\n                    hasRecentError: hasRecentError\n                )\n            }\n            DispatchQueue.main.async {\n                guard let self = self else { return }\n                for (index, reason) in results {\n                    if index < self.services.count {\n                        self.services[index].stopReason = reason\n                    }\n                }\n            }\n        }\n    }\n\n    /// 获取服务的配置文件路径列表（主线程调用）\n    private func getServiceConfigPaths(for service: ServiceStatus, homebrewPrefix: String) -> [String] {\n        let name = service.name.lowercased()\n        if name.contains(" = ")\n    }()\n\n    init() {\n        setupStatusBar()\n        // 启动全局服务状态监控器\n        ServiceStatusMonitor.shared.startMonitoring()\n        // 订阅状态变化\n        subscribeToStatusMonitor()\n        Task {\n            await loadServicesWithCache()\n        }\n    }\n\n    private func setupStatusBar() {\n        statusBarManager.setupStatusBar(appState: self)\n    }\n\n    /// 订阅全局状态监控器\n    private func subscribeToStatusMonitor() {\n        let monitor = ServiceStatusMonitor.shared\n\n        // 当运行状态变化时，更新仪表盘服务列表的运行状态\n        monitor.$runningServices\n            .dropFirst()\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] running in\n                self?.updateServicesRunningState(running)\n            }\n            .store(in: &cancellables)\n    }\n\n    /// 更新服务列表的运行状态\n    private func updateServicesRunningState(_ running: Set<String>) {\n        for i in services.indices {\n            // 跳过正在过渡中的服务（启动中/停止中）\n            if services[i].state == .starting || services[i].state == .stopping {\n                continue\n            }\n            if let brewName = services[i].brewName {\n                let newState: ServiceState = running.contains(brewName) ? .running : .stopped\n                if services[i].state != newState {\n                    services[i].state = newState\n                }\n                // 运行中的服务清除停止原因\n                if newState == .running {\n                    services[i].stopReason = nil\n                }\n            }\n        }\n\n        // 收集已停止的服务，在后台诊断停止原因\n        var stoppedServices: [(index: Int, service: ServiceStatus)] = []\n        for i in services.indices {\n            if services[i].installed && services[i].state == .stopped {\n                stoppedServices.append((i, services[i]))\n            }\n        }\n\n        guard !stoppedServices.isEmpty else { return }\n\n        // 预计算配置路径（ConfigPathProvider 是 @MainActor）\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        var configPathsMap: [Int: [String]] = [:]\n        for item in stoppedServices {\n            configPathsMap[item.index] = getServiceConfigPaths(\n                for: item.service, homebrewPrefix: homebrewPrefix\n            )\n        }\n\n        // 收集最近错误日志\n        let fiveMinutesAgo = Date().addingTimeInterval(-300)\n        var recentErrorIds: Set<Int> = []\n        for item in stoppedServices {\n            let svcName = item.service.name\n            if ErrorLogManager.shared.hasLogsForSoftware(named: svcName) {\n                let logs = ErrorLogManager.shared.getLogsForSoftware(named: svcName, limit: 1)\n                if let lastLog = logs.first, lastLog.timestamp > fiveMinutesAgo {\n                    recentErrorIds.insert(item.index)\n                }\n            }\n        }\n\n        let brewPath = Self.cachedBrewPath ?? resolveBrewPath()\n\n        DispatchQueue.global(qos: .utility).async { [weak self] in\n            var results: [Int: StopReason] = [:]\n            for item in stoppedServices {\n                let paths = configPathsMap[item.index] ?? []\n                let hasRecentError = recentErrorIds.contains(item.index)\n                results[item.index] = Self.diagnoseServiceStopReason(\n                    service: item.service,\n                    brewPath: brewPath,\n                    homebrewPrefix: homebrewPrefix,\n                    configPaths: paths,\n                    hasRecentError: hasRecentError\n                )\n            }\n            DispatchQueue.main.async {\n                guard let self = self else { return }\n                for (index, reason) in results {\n                    if index < self.services.count {\n                        self.services[index].stopReason = reason\n                    }\n                }\n            }\n        }\n    }\n\n    /// 获取服务的配置文件路径列表（主线程调用）\n    private func getServiceConfigPaths(for service: ServiceStatus, homebrewPrefix: String) -> [String] {\n        let name = service.name.lowercased()\n        if name.contains(";
") as? Int ?? 8080\n\n                // 定义服务检测顺序：Nginx/Apache/MySQL 排在前面\n                let serviceDefinitions: [(name: String, brewName: String, port: Int, priority: Int)] = [\n                    // 高优先级 - Web 服务器\n                    (" = ") como? Int ?? 8080\n\n                // Definir el orden de detección de servicios: Nginx/Apache/MySQL al principio\n                let serviceDefinitions: [(name: String, brewName: String, port: Int, priority: Int)] = [\n                    // Alta prioridad - Servidores web\n                    (";
"#\n                    if let regex = try? NSRegularExpression(pattern: datePattern),\n                       let match = regex.firstMatch(in: remainingText, range: NSRange(remainingText.startIndex..., in: remainingText)) {\n                        if let range = Range(match.range, in: remainingText) {\n                            startTime = String(remainingText[range])\n                            // 剩余部分是命令行\n                            let commandStartIndex = remainingText.index(range.upperBound, offsetBy: 0)\n                            if commandStartIndex < remainingText.endIndex {\n                                commandLine = String(remainingText[commandStartIndex...]).trimmingCharacters(in: .whitespaces)\n                            }\n                        }\n                    } else {\n                        // 如果无法解析时间，将所有剩余内容作为命令行\n                        commandLine = remainingText\n                    }\n                }\n            }\n        } catch {\n            // 忽略错误\n        }\n\n        // 使用 lsof 获取可执行文件路径和工作目录\n        if let lsofPath = findLsofPath() {\n            let lsofProcess = Process()\n            lsofProcess.executableURL = URL(fileURLWithPath: lsofPath)\n            lsofProcess.arguments = [" = "#\n                    if let regex = try? NSRegularExpression(pattern: datePattern),\n                       let match = regex.firstMatch(in: remainingText, range: NSRange(remainingText.startIndex..., in: remainingText)) {\n                        if let range = Range(match.range, in: remainingText) {\n                            startTime = String(remainingText[range])\n                            // La parte restante es la línea de comandos\n                            let commandStartIndex = remainingText.index(range.upperBound, offsetBy: 0)\n                            if commandStartIndex < remainingText.endIndex {\n                                commandLine = String(remainingText[commandStartIndex...]).trimmingCharacters(in: .whitespaces)\n                            }\n                        }\n                    } else {\n                        // Si no se puede analizar la hora, tomar todo el texto restante como línea de comandos\n                        commandLine = remainingText\n                    }\n                }\n            }\n        } catch {\n            // Ignorar errores\n        }\n\n        // Usar lsof para obtener la ruta del ejecutable y el directorio de trabajo\n        if let lsofPath = findLsofPath() {\n            let lsofProcess = Process()\n            lsofProcess.executableURL = URL(fileURLWithPath: lsofPath)\n            lsofProcess.arguments = [";
"]),\n\n        // ===== OPcache 配置 =====\n        " = "]),\n\n        // ===== Configuración de OPcache =====\n        ";
"  // 公开目录（用户浏览器可访问的子目录）\n    var webServer: WebServerType\n    var phpVersion: String\n    var sslStatus: SSLStatus\n    var status: SiteStatus\n    var isEnabled: Bool\n    var hasBackup: Bool\n    var backupCount: Int\n    var dailyTraffic: String\n    var expiryDate: String?\n    var note: String\n    var createdAt: Date\n    var isDeleted: Bool = false  // 是否在回收站\n    var deletedAt: Date?  // 删除时间\n    var advancedConfig: SiteAdvancedConfig = SiteAdvancedConfig()  // 高级设置配置\n    var directorySize: Int64 = 0  // 站点目录容量（字节）\n    var directorySizeUpdatedAt: Date?  // 容量更新时间\n    var certificateId: UUID?  // 关联的SSL证书ID\n    var forceHttps: Bool = false  // 是否强制HTTPS（HTTP自动跳转到HTTPS）\n    var sslMode: SSLMode = .nginxTermination  // SSL架构模式（默认Nginx SSL终止）\n    var ports: [Int] = []  // 监听端口列表\n\n    var primaryDomain: String {\n        domains.first ?? " = "  // Directorio público (subdirectorios accesibles desde el navegador del usuario)\n    var webServer: WebServerType\n    var phpVersion: String\n    var sslStatus: SSLStatus\n    var status: SiteStatus\n    var isEnabled: Bool\n    var hasBackup: Bool\n    var backupCount: Int\n    var dailyTraffic: String\n    var expiryDate: String?\n    var note: String\n    var createdAt: Date\n    var isDeleted: Bool = false  // ¿Está en la papelera de reciclaje?\n    var deletedAt: Date?  // Fecha de eliminación\n    var advancedConfig: SiteAdvancedConfig = SiteAdvancedConfig()  // Configuración avanzada\n    var directorySize: Int64 = 0  // Capacidad del directorio del sitio (en bytes)\n    var directorySizeUpdatedAt: Date?  // Fecha de actualización de la capacidad\n    var certificateId: UUID?  // ID del certificado SSL asociado\n    var forceHttps: Bool = false  // ¿Forzar HTTPS? (HTTP redirigirá automáticamente a HTTPS)\n    var sslMode: SSLMode = .nginxTermination  // Modo de arquitectura SSL (por defecto: terminación SSL en Nginx)\n    var ports: [Int] = []  // Lista de puertos escuchados\n\n    var primaryDomain: String {\n        domains.first ?? ";
"))\n                }) {\n                    return (installed: service.installed, running: service.isRunning)\n                }\n                \n                // 如果找不到服务，检查 installedWebServers\n                let isInstalled = siteManager.installedWebServers.contains(webServer)\n                return (installed: isInstalled, running: false)\n            }\n        }\n    }\n\n    // 标题栏\n    private var headerBar: some View {\n        HStack {\n            Text(" = "))\n                }) {\n                    return (installed: service.installed, running: service.isRunning)\n                }\n                \n                // Si no se encuentra el servicio, verificar installedWebServers\n                let isInstalled = siteManager.installedWebServers.contains(webServer)\n                return (installed: isInstalled, running: false)\n            }\n        }\n    }\n\n    // Barra de encabezado\n    private var headerBar: some View {\n        HStack {\n            Text(";
"检测到冲突：该域名在 \\(conflictServer.rawValue) 中也有配置" = "Se detectó un conflicto: este dominio también está configurado en \\(conflictServer.rawValue)";
":\n            // MySQL 配置文件可能在多个位置\n            configPaths = [\n                " = ":\n            // El archivo de configuración de MySQL podría estar en varias ubicaciones\n            configPaths = [\n                ";
")\n                    .foregroundColor(.secondary)\n            }\n\n            Divider()\n\n            // PHP 扩展管理\n            Text(" = ")\n                    .foregroundColor(.secondary)\n            }\n\n            Divider()\n\n            // Gestión de extensiones PHP\n            Text(";
")\n                        .frame(width: 80, alignment: .trailing)\n                    Text(username)\n                        .foregroundColor(.secondary)\n                    Spacer()\n                }\n\n                // 主机\n                HStack {\n                    Text(" = ")\n                        .frame(width: 80, alignment: .trailing)\n                    Text(username)\n                        .foregroundColor(.secondary)\n                    Spacer()\n                }\n\n                // Host\n                HStack {\n                    Text(";
"\n\n        // 加载 PHP 端口配置\n        if let data = UserDefaults.standard.data(forKey: " = "\n\n        // Cargar la configuración del puerto PHP\n        if let data = UserDefaults.standard.data(forKey: ";
"❌ 扫描配置目录失败: \\(error.localizedDescription)" = "❌ Error al escanear el directorio de configuración: \\(error.localizedDescription)";
"\n\n        Task {\n            // 设置已安装的 Web 服务器列表\n            siteManager.installedWebServers = installedWebServers\n\n            let configs = await siteManager.scanVhostConfigs { path in\n                // 更新当前扫描的目录\n                currentScanningPath = path\n            }\n            await MainActor.run {\n                foundConfigs = configs\n                isScanning = false\n                currentScanningPath = " = "\n\n        Task {\n            // Establecer la lista de servidores web instalados\n            siteManager.installedWebServers = installedWebServers\n\n            let configs = await siteManager.scanVhostConfigs { path in\n                // Actualizar el directorio que se está escaneando actualmente\n                currentScanningPath = path\n            }\n            await MainActor.run {\n                foundConfigs = configs\n                isScanning = false\n                currentScanningPath = ";
"端口 \\(conflictPort)" = "Puerto \\(conflictPort)";
"有効 \\(Self.allCases.count) / \\(Self.maxLanguageCapacity)" = "Activado \\(Self.allCases.count) / \\(Self.maxLanguageCapacity)";
"无法执行 nginx -t: \\(error.localizedDescription)" = "❌ No se pudo ejecutar nginx -t: \\(error.localizedDescription)";
"⚠️ 备份旧配置失败: \\(error.localizedDescription)" = "⚠️ Error al respaldar la configuración anterior: \\(error.localizedDescription)";
"\n        let userResult = await executeSQL(createUserSQL)\n\n        if !userResult.success {\n            // 如果用户创建失败，回滚数据库\n            _ = await executeSQL(" = "\n        let userResult = await executeSQL(createUserSQL)\n\n        if !userResult.success {\n            // Si falla la creación del usuario, revertir la base de datos\n            _ = await executeSQL(";
")\n                    .font(.system(size: 56))\n                    .foregroundStyle(.linearGradient(\n                        colors: [.blue, .purple],\n                        startPoint: .topLeading,\n                        endPoint: .bottomTrailing\n                    ))\n\n                // 欢迎文字\n                VStack(spacing: 12) {\n                    Text(" = ")\n                    .font(.system(size: 56))\n                    .foregroundStyle(.linearGradient(\n                        colors: [.blue, .purple],\n                        startPoint: .topLeading,\n                        endPoint: .bottomTrailing\n                    ))\n\n                // Texto de bienvenida\n                VStack(spacing: 12) {\n                    Text(";
", selection: Binding(\n                    get: { config.logConfig.rotationType },\n                    set: { config.logConfig.rotationType = $0 }\n                )) {\n                    ForEach(LogRotationType.allCases, id: \\.self) { type in\n                        Text(LocalizedStringKey(type.rawValue)).tag(type)\n                    }\n                }\n                .pickerStyle(.segmented)\n\n                Text(LocalizedStringKey(config.logConfig.rotationType.description))\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n\n                // 如果选择按大小切割，显示大小输入\n                if config.logConfig.rotationType == .size {\n                    VStack(alignment: .leading, spacing: 8) {\n                        Text(LocalizedStringKey(" = ", selection: Binding(\n                    get: { config.logConfig.rotationType },\n                    set: { config.logConfig.rotationType = $0 }\n                )) {\n                    ForEach(LogRotationType.allCases, id: \\.self) { type in\n                        Text(LocalizedStringKey(type.rawValue)).tag(type)\n                    }\n                }\n                .pickerStyle(.segmented)\n\n                Text(LocalizedStringKey(config.logConfig.rotationType.description))\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n\n                // Si se selecciona rotación por tamaño, mostrar el campo de entrada de tamaño\n                if config.logConfig.rotationType == .size {\n                    VStack(alignment: .leading, spacing: 8) {\n                        Text(LocalizedStringKey(";
", selection: $sslMode) {\n                                    ForEach(SSLMode.allCases, id: \\.self) { mode in\n                                        VStack(alignment: .leading, spacing: 2) {\n                                            Text(LocalizedStringKey(mode.rawValue))\n                                                .font(.system(size: 12))\n                                            Text(mode.description)\n                                                .font(.system(size: 10))\n                                                .foregroundColor(.secondary)\n                                        }\n                                        .tag(mode)\n                                    }\n                                }\n                                .pickerStyle(.radioGroup)\n                            }\n                            .padding(12)\n                            .background(Color.blue.opacity(AppTokens.Opacity.ultraLight))\n                            .cornerRadius(8)\n                            .overlay(\n                                RoundedRectangle(cornerRadius: 8)\n                                    .stroke(Color.blue.opacity(AppTokens.Opacity.border), lineWidth: 1)\n                            )\n                        }\n                    }\n                }\n\n                // 备注\n                VStack(alignment: .leading, spacing: 8) {\n                    TextField(" = ", selection: $sslMode) {\n                                    ForEach(SSLMode.allCases, id: \\.self) { mode in\n                                        VStack(alignment: .leading, spacing: 2) {\n                                            Text(LocalizedStringKey(mode.rawValue))\n                                                .font(.system(size: 12))\n                                            Text(mode.description)\n                                                .font(.system(size: 10))\n                                                .foregroundColor(.secondary)\n                                        }\n                                        .tag(mode)\n                                    }\n                                }\n                                .pickerStyle(.radioGroup)\n                            }\n                            .padding(12)\n                            .background(Color.blue.opacity(AppTokens.Opacity.ultraLight))\n                            .cornerRadius(8)\n                            .overlay(\n                                RoundedRectangle(cornerRadius: 8)\n                                    .stroke(Color.blue.opacity(AppTokens.Opacity.border), lineWidth: 1)\n                            )\n                        }\n                    }\n                }\n\n                // Notas\n                VStack(alignment: .leading, spacing: 8) {\n                    TextField(";
"安装 PHP \\(phpVersion) 时发现端口冲突" = "Port conflict detected while installing PHP (phpVersion)";
"\n        # 默认站点配置\n        # 由 JihePHPEnv 自动生成\n\n        server {\n            listen       \\(port);\n            server_name  localhost 127.0.0.1;\n\n            root   \\(homebrewPrefix)/var/www;\n            index  index.php index.html index.htm;\n\n            location / {\n                try_files $uri $uri/ /index.php?$query_string;\n            }\n\n            location ~ \\\\.php$ {\n                fastcgi_pass   127.0.0.1:9000;\n                fastcgi_index  index.php;\n                fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;\n                include        fastcgi_params;\n            }\n\n            location /phpmyadmin {\n                alias \\(homebrewPrefix)/share/phpmyadmin;\n                index jihe_signon.php;\n\n                location ~ \\\\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_param SCRIPT_FILENAME $request_filename;\n                }\n            }\n\n            location ~ /\\\\. {\n                deny all;\n            }\n        }\n        " = "\n        # Default site configuration\n        # Generated automatically by JihePHPEnv\n\n        server {\n            listen       (port);\n            server_name  localhost 127.0.0.1;\n\n            root   (homebrewPrefix)/var/www;\n            index  index.php index.html index.htm;\n\n            location / {\n                try_files $uri $uri/ /index.php?$query_string;\n            }\n\n            location ~ \\.php$ {\n                fastcgi_pass   127.0.0.1:9000;\n                fastcgi_index  index.php;\n                fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;\n                include        fastcgi_params;\n            }\n\n            location /phpmyadmin {\n                alias (homebrewPrefix)/share/phpmyadmin;\n                index jihe_signon.php;\n\n                location ~ \\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_param SCRIPT_FILENAME $request_filename;\n                }\n            }\n\n            location ~ /\\. {\n                deny all;\n            }\n        }\n        ";
"\n            }\n\n            // 生成证书文件（使用 mkcert 或 openssl）\n            let (certPath, keyPath) = try await CertificateManager.shared.generateCertificate(\n                domains: domainList,\n                outputDirectory: sslDir,\n                certificateName: finalName\n            )\n\n            // 解析证书信息以获取准确的有效期和颁发者\n            let certInfo = CertificateManager.shared.parseCertificateInfo(certPath: certPath)\n\n            let certificate = Certificate(\n                domains: domainList,\n                certificateType: .selfSigned,\n                certificatePath: certPath,\n                privateKeyPath: keyPath,\n                issuer: certInfo?.issuer ?? " = "\n            }\n\n            // Generate certificate files (using mkcert or openssl)\n            let (certPath, keyPath) = try await CertificateManager.shared.generateCertificate(\n                domains: domainList,\n                outputDirectory: sslDir,\n                certificateName: finalName\n            )\n\n            // Parse certificate information to obtain accurate validity period and issuer\n            let certInfo = CertificateManager.shared.parseCertificateInfo(certPath: certPath)\n\n            let certificate = Certificate(\n                domains: domainList,\n                certificateType: .selfSigned,\n                certificatePath: certPath,\n                privateKeyPath: keyPath,\n                issuer: certInfo?.issuer ?? ";
")\n                .font(.title3)\n                .fontWeight(.semibold)\n                .padding()\n\n            Divider()\n\n            ScrollView {\n                VStack(alignment: .leading, spacing: 4) {\n                    menuItems(isV2: false)\n                }\n                .padding(.vertical, 8)\n            }\n        }\n        .frame(width: 200)\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n    \n    // V2 左侧菜单\n    private var v2LeftMenu: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            Text(" = ")\n                .font(.title3)\n                .fontWeight(.semibold)\n                .padding()\n\n            Divider()\n\n            ScrollView {\n                VStack(alignment: .leading, spacing: 4) {\n                    menuItems(isV2: false)\n                }\n                .padding(.vertical, 8)\n            }\n        }\n        .frame(width: 200)\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n    \n    // V2 Left Menu\n    private var v2LeftMenu: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            Text(";
"JSON 解析失败: \\(error.localizedDescription)" = "JSON parsing failed: (error.localizedDescription)";
"\n    @Published var currentCheckingType: HealthCheckType? = nil\n    @Published var totalSites: Int = 0\n    @Published var completedSites: Int = 0\n    @Published var startTime: Date?\n    @Published var estimatedTimeRemaining: TimeInterval = 0\n\n    private let hostsManager = HostsManager()\n    private let fileManager = FileManager.default\n    private var siteCheckTimes: [TimeInterval] = []\n\n    // MARK: - 公共方法\n\n    /// 检查多个站点的健康状态（分阶段并行检查）\n    func checkSites(_ sites: [Site]) async {\n        isChecking = true\n        reports = []\n        totalSites = sites.count\n        completedSites = 0\n        startTime = Date()\n        siteCheckTimes = []\n\n        // 为每个站点创建初始报告（包含待检测的项目）\n        for site in sites {\n            let initialResults = createInitialResults(for: site)\n            let report = SiteHealthReport(site: site, results: initialResults, isChecking: true, startTime: Date())\n            reports.append(report)\n        }\n\n        // 阶段1: 所有站点并行检查 Hosts\n        currentCheckingType = .hosts\n        await checkAllSitesForType(.hosts, sites: sites)\n        currentCheckingType = nil\n\n        // 阶段2: 所有站点并行检查 Web服务器\n        currentCheckingType = .webServer\n        await checkAllSitesForType(.webServer, sites: sites)\n        currentCheckingType = nil\n\n        // 阶段3: 所有站点并行检查端口\n        currentCheckingType = .ports\n        await checkAllSitesForType(.ports, sites: sites)\n        currentCheckingType = nil\n\n        // 阶段4: 需要PHP检查的站点并行检查\n        currentCheckingType = .php\n        await checkAllSitesForType(.php, sites: sites)\n        currentCheckingType = nil\n\n        // 阶段5: 需要SSL检查的站点并行检查\n        currentCheckingType = .ssl\n        await checkAllSitesForType(.ssl, sites: sites)\n        currentCheckingType = nil\n\n        // 标记所有站点检查完成\n        for index in 0..<reports.count {\n            reports[index].isChecking = false\n            reports[index].endTime = Date()\n        }\n\n        isChecking = false\n        currentSiteName = " = "\n    @Published var currentCheckingType: HealthCheckType? = nil\n    @Published var totalSites: Int = 0\n    @Published var completedSites: Int = 0\n    @Published var startTime: Date?\n    @Published var estimatedTimeRemaining: TimeInterval = 0\n\n    private let hostsManager = HostsManager()\n    private let fileManager = FileManager.default\n    private var siteCheckTimes: [TimeInterval] = []\n\n    // MARK: - Public Methods\n\n    /// Check health status of multiple sites (parallel checks in stages)\n    func checkSites(_ sites: [Site]) async {\n        isChecking = true\n        reports = []\n        totalSites = sites.count\n        completedSites = 0\n        startTime = Date()\n        siteCheckTimes = []\n\n        // Create initial report for each site (including items to be checked)\n        for site in sites {\n            let initialResults = createInitialResults(for: site)\n            let report = SiteHealthReport(site: site, results: initialResults, isChecking: true, startTime: Date())\n            reports.append(report)\n        }\n\n        // Stage 1: Parallel hosts check for all sites\n        currentCheckingType = .hosts\n        await checkAllSitesForType(.hosts, sites: sites)\n        currentCheckingType = nil\n\n        // Stage 2: Parallel web server check for all sites\n        currentCheckingType = .webServer\n        await checkAllSitesForType(.webServer, sites: sites)\n        currentCheckingType = nil\n\n        // Stage 3: Parallel port check for all sites\n        currentCheckingType = .ports\n        await checkAllSitesForType(.ports, sites: sites)\n        currentCheckingType = nil\n\n        // Stage 4: Parallel PHP check for sites requiring PHP\n        currentCheckingType = .php\n        await checkAllSitesForType(.php, sites: sites)\n        currentCheckingType = nil\n\n        // Stage 5: Parallel SSL check for sites requiring SSL\n        currentCheckingType = .ssl\n        await checkAllSitesForType(.ssl, sites: sites)\n        currentCheckingType = nil\n\n        // Mark all site checks as complete\n        for index in 0..<reports.count {\n            reports[index].isChecking = false\n            reports[index].endTime = Date()\n        }\n\n        isChecking = false\n        currentSiteName = ";
")\n            }\n            .buttonStyle(.bordered)\n        }\n    }\n\n    // 应用默认站点配置\n    private func applyDefaultSiteConfig() {\n        isApplyingConfig = true\n        statusMessage = nil\n\n        Task {\n            let documentRoot = appSettings.defaultSiteRoot.isEmpty\n                ? " = ")\n            }\n            .buttonStyle(.bordered)\n        }\n\n        // Apply default site configuration\n        private func applyDefaultSiteConfig() {\n            isApplyingConfig = true\n            statusMessage = nil\n\n            Task {\n                let documentRoot = appSettings.defaultSiteRoot.isEmpty\n                    ? ";
")\n                        .font(.caption)\n                }\n            }\n            .buttonStyle(.bordered)\n\n            Spacer()\n\n            // 搜索框\n            HStack {\n                Image(systemName: " = ")\n                        .font(.caption)\n                }\n            }\n            .buttonStyle(.bordered)\n\n            Spacer()\n\n            // Search field\n            HStack {\n                Image(systemName: ";
",          // 不输出列名\n                    " = ",          // Do not output column names\n                    ";
")\n            }\n            .buttonStyle(.bordered)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 12)\n    }\n    \n    // 筛选后的数据库列表\n    private var filteredDatabases: [Database] {\n        var result = databaseManager.databases\n\n        // 按搜索文本筛选\n        if !searchText.isEmpty {\n            result = result.filter { database in\n                database.name.localizedCaseInsensitiveContains(searchText) ||\n                database.note.localizedCaseInsensitiveContains(searchText) ||\n                database.username.localizedCaseInsensitiveContains(searchText)\n            }\n        }\n\n        return result\n    }\n\n    // 表头\n    private var tableHeader: some View {\n        HStack(spacing: 8) {\n            Toggle(" = ")\n            }\n            .buttonStyle(.bordered)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 12)\n    }\n    \n    // Filtered database list\n    private var filteredDatabases: [Database] {\n        var result = databaseManager.databases\n\n        // Filter by search text\n        if !searchText.isEmpty {\n            result = result.filter { database in\n                database.name.localizedCaseInsensitiveContains(searchText) ||\n                database.note.localizedCaseInsensitiveContains(searchText) ||\n                database.username.localizedCaseInsensitiveContains(searchText)\n            }\n        }\n\n        return result\n    }\n\n    // Table header\n    private var tableHeader: some View {\n        HStack(spacing: 8) {\n            Toggle(";
"⚠️ 备份旧 Nginx 配置失败: \\(error.localizedDescription)" = "⚠️ Failed to back up old Nginx configuration: (error.localizedDescription)";
"\n    @State private var selectedAlgorithm: EncryptionAlgorithm = .aesGCM\n    @State private var showPassword = false\n    @State private var errorMessage: String?\n    @State private var showError = false\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 头部工具栏\n            HStack {\n                Text(" = "\n    @State private var selectedAlgorithm: EncryptionAlgorithm = .aesGCM\n    @State private var showPassword = false\n    @State private var errorMessage: String?\n    @State private var showError = false\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Header toolbar\n            HStack {\n                Text(";
",\n                configLocations: [\n                    // Nginx 的 vhosts 目录（反向代理配置）\n                    ConfigFileLocation(\n                        workspacePath: " = ",\n                configLocations: [\n                    // Nginx vhosts directory (reverse proxy configurations)\n                    ConfigFileLocation(\n                        workspacePath: ";
")\n                .foregroundColor(.orange)\n        }\n    }\n\n    // 状态颜色\n    private func statusColor(for type: StatusMessage.StatusType) -> Color {\n        switch type {\n        case .loading: return .secondary\n        case .success: return .green\n        case .error: return .red\n        case .info: return .primary\n        case .warning: return .orange\n        }\n    }\n\n    // 表单模式视图\n    private var formModeView: some View {\n        VStack(spacing: 0) {\n            // Tab 切换\n            configTypePicker\n\n            Divider()\n\n            // 配置列表\n            ScrollView {\n                LazyVStack(spacing: 16) {\n                    // 端口配置区域（如果支持）\n                    if supportsPortConfig {\n                        portConfigSection\n                    }\n\n                    // 使用 section.title 作为稳定标识符，避免因 UUID 变化导致的无限重渲染\n                    ForEach(filteredSections, id: \\.title) { section in\n                        ConfigSectionView(\n                            section: section,\n                            onValueChange: { itemIndex, newValue in\n                                // 找到原始 section 的索引\n                                if let originalIndex = configSections.firstIndex(where: { $0.title == section.title }) {\n                                    // 找到原始 item 的索引\n                                    let itemKey = section.items[itemIndex].key\n                                    if let originalItemIndex = configSections[originalIndex].items.firstIndex(where: { $0.key == itemKey }) {\n                                        configSections[originalIndex].items[originalItemIndex].value = newValue\n                                        hasChanges = true\n                                    }\n                                }\n                            },\n                            onEnabledChange: { itemIndex, newEnabled in\n                                // 找到原始 section 的索引\n                                if let originalIndex = configSections.firstIndex(where: { $0.title == section.title }) {\n                                    // 找到原始 item 的索引\n                                    let itemKey = section.items[itemIndex].key\n                                    if let originalItemIndex = configSections[originalIndex].items.firstIndex(where: { $0.key == itemKey }) {\n                                        configSections[originalIndex].items[originalItemIndex].isEnabled = newEnabled\n                                        hasChanges = true\n                                    }\n                                }\n                            }\n                        )\n                    }\n                }\n                .padding()\n            }\n        }\n    }\n\n    // 配置类型选择器\n    private var configTypePicker: some View {\n        HStack {\n            Picker(" = ")\n                .foregroundColor(.orange)\n        }\n    }\n\n    // Status color\n    private func statusColor(for type: StatusMessage.StatusType) -> Color {\n        switch type {\n        case .loading: return .secondary\n        case .success: return .green\n        case .error: return .red\n        case .info: return .primary\n        case .warning: return .orange\n        }\n    }\n\n    // Form mode view\n    private var formModeView: some View {\n        VStack(spacing: 0) {\n            // Tab switcher\n            configTypePicker\n\n            Divider()\n\n            // Configuration list\n            ScrollView {\n                LazyVStack(spacing: 16) {\n                    // Port configuration section (if supported)\n                    if supportsPortConfig {\n                        portConfigSection\n                    }\n\n                    // Use section.title as a stable identifier to avoid infinite re-renders caused by UUID changes\n                    ForEach(filteredSections, id: \\.title) { section in\n                        ConfigSectionView(\n                            section: section,\n                            onValueChange: { itemIndex, newValue in\n                                // Find the original section's index\n                                if let originalIndex = configSections.firstIndex(where: { $0.title == section.title }) {\n                                    // Find the original item's index\n                                    let itemKey = section.items[itemIndex].key\n                                    if let originalItemIndex = configSections[originalIndex].items.firstIndex(where: { $0.key == itemKey }) {\n                                        configSections[originalIndex].items[originalItemIndex].value = newValue\n                                        hasChanges = true\n                                    }\n                                }\n                            },\n                            onEnabledChange: { itemIndex, newEnabled in\n                                // Find the original section's index\n                                if let originalIndex = configSections.firstIndex(where: { $0.title == section.title }) {\n                                    // Find the original item's index\n                                    let itemKey = section.items[itemIndex].key\n                                    if let originalItemIndex = configSections[originalIndex].items.firstIndex(where: { $0.key == itemKey }) {\n                                        configSections[originalIndex].items[originalItemIndex].isEnabled = newEnabled\n                                        hasChanges = true\n                                    }\n                                }\n                            }\n                        )\n                    }\n                }\n                .padding()\n            }\n        }\n    }\n\n    // Configuration type picker\n    private var configTypePicker: some View {\n        HStack {\n            Picker(";
"默认路径: \\(workspaceManager.getDefaultPath())" = "Default path: (workspaceManager.getDefaultPath())";
"\n        }\n\n        // 报告当前扫描的目录\n        await MainActor.run {\n            onProgress?(apacheVhostPath)\n        }\n\n        return await scanConfigDirectory(path: apacheVhostPath, webServer: .apache)\n    }\n\n    /// 写入扫描日志到文件\n    private func writeVhostScanLog(_ message: String, clearFirst: Bool = false) {\n        // 同时输出到 Xcode 控制台，方便调试\n        NSLog(" = "\n        }\n\n        // Report currently scanned directory\n        await MainActor.run {\n            onProgress?(apacheVhostPath)\n        }\n\n        return await scanConfigDirectory(path: apacheVhostPath, webServer: .apache)\n    }\n\n    /// Write vhost scan log to file\n    private func writeVhostScanLog(_ message: String, clearFirst: Bool = false) {\n        // Also output to Xcode console for debugging convenience\n        NSLog(";
"\n                    var isDirectory: ObjCBool = false\n                    if fileManager.fileExists(atPath: fullPath, isDirectory: &isDirectory), isDirectory.boolValue {\n                        return fullPath\n                    }\n                }\n            }\n        }\n\n        return nil\n    }\n\n    /// 从配置文件导入站点\n    func importFromVhostConfig(_ config: FoundVhostConfig) async throws {\n        let parsed = config.parsed\n\n        // 生成站点名称（使用第一个域名）\n        let siteName = parsed.domains.first ?? parsed.fileName.replacingOccurrences(of: " = "\n                    var isDirectory: ObjCBool = false\n                    if fileManager.fileExists(atPath: fullPath, isDirectory: &isDirectory), isDirectory.boolValue {\n                        return fullPath\n                    }\n                }\n            }\n        }\n\n        return nil\n    }\n\n    /// Import sites from vhost configuration file\n    func importFromVhostConfig(_ config: FoundVhostConfig) async throws {\n        let parsed = config.parsed\n\n        // Generate site name (using first domain)\n        let siteName = parsed.domains.first ?? parsed.fileName.replacingOccurrences(of: ";
") || trimmed.isEmpty { continue }\n\n            // MySQL/Redis 格式: key = value 或 key=value\n            if trimmed.contains(" = ") || trimmed.isEmpty { continue }\n\n            // MySQL/Redis format: key = value or key=value\n            if trimmed.contains(";
")\n                    }\n                }\n            }\n            // Nginx 格式: key value;\n            else if trimmed.hasSuffix(" = ")\n                    }\n                }\n            }\n            // Nginx format: key value;\n            else if trimmed.hasSuffix(";
"\n        )\n\n        // 写回配置\n        do {\n            try config.write(toFile: configPath, atomically: true, encoding: .utf8)\n            return true\n        } catch {\n            return false\n        }\n    }\n\n    /// PureFTP 专用安装方法，支持选择数据库模式\n    func installPureFTP(databaseMode: PureFTPDatabaseMode) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(" = "\n        )\n\n        // Write back configuration\n        do {\n            try config.write(toFile: configPath, atomically: true, encoding: .utf8)\n            return true\n        } catch {\n            return false\n        }\n    }\n\n    /// PureFTP-specific installation method supporting database mode selection\n    func installPureFTP(databaseMode: PureFTPDatabaseMode) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(";
"\n        let result1 = await executeSQL(sql1)\n        if !result1.success {\n            errorMessage = result1.error\n            return false\n        }\n\n        // 3. 更新 mysql.db 表\n        let sql2 = " = "\n        let result1 = await executeSQL(sql1)\n        if !result1.success {\n            errorMessage = result1.error\n            return false\n        }\n\n        // 3. Actualizar la tabla mysql.db\n        let sql2 = ";
"\n        # 默认站点配置\n        # 由 JihePHPEnv 自动生成\n        # 生成时间: \\(ISO8601DateFormatter().string(from: Date()))\n\n        <VirtualHost *:\\(port)>\n            ServerName localhost\n            DocumentRoot " = "\n        # Configuración predeterminada del sitio\n        # Generado automáticamente por JihePHPEnv\n        # Fecha de generación: \\(ISO8601DateFormatter().string(from: Date()))\n\n        <VirtualHost *:\\(port)>\n            ServerName localhost\n            DocumentRoot ";
"解密失败: \\(error.localizedDescription)" = "Error al descifrar: \\(error.localizedDescription)";
")\n\n        // 确保日志目录存在\n        try? fileManager.createDirectory(atPath: logsDir, withIntermediateDirectories: true)\n\n        // 日志文件路径\n        let accessLog = (logsDir as NSString).appendingPathComponent(" = ")\n\n        // Asegurar que el directorio de registros exista\n        try? fileManager.createDirectory(atPath: logsDir, withIntermediateDirectories: true)\n\n        // Ruta del archivo de registros\n        let accessLog = (logsDir as NSString).appendingPathComponent(";
"显示 \\(displayPorts.count) 个用户端口" = "Mostrando \\(displayPorts.count) puertos de usuario";
",\n            password: password\n        )\n        Task {\n            await connector.fetchDatabases()\n        }\n    }\n\n    // 标签选择器\n    private var tabSelector: some View {\n        HStack(spacing: 0) {\n            tabButton(" = ",\n            password: password\n        )\n        Task {\n            await connector.fetchDatabases()\n        }\n    }\n\n    // Selector de pestañas\n    private var tabSelector: some View {\n        HStack(spacing: 0) {\n            tabButton(";
"]\n                startProcess.standardOutput = Pipe()\n                startProcess.standardError = Pipe()\n\n                do {\n                    try startProcess.run()\n                    startProcess.waitUntilExit()\n                } catch {\n                    // 即使启动失败，如果密码已重置成功，也返回成功\n                }\n\n                continuation.resume(returning: resetSuccess)\n            }\n        }\n    }\n\n    // 使用 init-file 方式强制重置密码\n    private func forceResetWithInitFile(brewPath: String) -> Bool {\n        // 创建临时 SQL 文件\n        let tempDir = FileManager.default.temporaryDirectory\n        let sqlFile = tempDir.appendingPathComponent(" = "]\n                startProcess.standardOutput = Pipe()\n                startProcess.standardError = Pipe()\n\n                do {\n                    try startProcess.run()\n                    startProcess.waitUntilExit()\n                } catch {\n                    // Incluso si el inicio falla, se devuelve éxito si la contraseña ya se restableció correctamente\n                }\n\n                continuation.resume(returning: resetSuccess)\n            }\n        }\n    }\n\n    // Restablecer forzadamente la contraseña mediante el archivo init-file\n    private func forceResetWithInitFile(brewPath: String) -> Bool {\n        // Crear archivo SQL temporal\n        let tempDir = FileManager.default.temporaryDirectory\n        let sqlFile = tempDir.appendingPathComponent(";
"\n\n        // 检查目录是否存在\n        guard fileManager.fileExists(atPath: fullPath) else {\n            return false\n        }\n\n        // 检查是否有 .php 文件\n        do {\n            let contents = try fileManager.contentsOfDirectory(atPath: fullPath)\n            return contents.contains { $0.hasSuffix(" = "\n\n        // Verificar si el directorio existe\n        guard fileManager.fileExists(atPath: fullPath) else {\n            return false\n        }\n\n        // Verificar si hay archivos .php\n        do {\n            let contents = try fileManager.contentsOfDirectory(atPath: fullPath)\n            return contents.contains { $0.hasSuffix(";
" : phpVersion\n        let fm = FileManager.default\n\n        // 优先使用工作空间路径\n        let workspaceManager = WorkspaceManager.shared\n        if let config = workspaceManager.config, config.isInitialized {\n            // 确保工作空间目录存在并创建符号链接\n            try await ensurePHPWorkspaceConfig(version: version, workspaceConfig: config)\n\n            // 检查工作空间中的配置文件\n            // PHP 7.x/8.x 使用 www.conf，PHP 5.x 使用 php-fpm.conf\n            let phpConfDir = config.phpConfPath(version: version)\n            let workspaceWwwPath = " = " : phpVersion\n        let fm = FileManager.default\n\n        // Priorizar la ruta del espacio de trabajo\n        let workspaceManager = WorkspaceManager.shared\n        if let config = workspaceManager.config, config.isInitialized {\n            // Asegurar que el directorio del espacio de trabajo exista y crear enlaces simbólicos\n            try await ensurePHPWorkspaceConfig(version: version, workspaceConfig: config)\n\n            // Verificar el archivo de configuración en el espacio de trabajo\n            // PHP 7.x/8.x usa www.conf, PHP 5.x usa php-fpm.conf\n            let phpConfDir = config.phpConfPath(version: version)\n            let workspaceWwwPath = ";
".contains(searchText) ||\n            port.processName.localizedCaseInsensitiveContains(searchText)\n        }\n    }\n\n    private var displayPorts: [PortItem] {\n        let userFiltered: [PortItem]\n        if currentUserOnly {\n            let currentUser = NSUserName()\n            userFiltered = filteredPorts.filter { $0.user == currentUser }\n        } else {\n            userFiltered = filteredPorts\n        }\n\n        switch portFilterMode {\n        case .all:\n            return userFiltered\n        case .userOnly:\n            return userFiltered.filter { $0.port >= 1024 }\n        }\n    }\n\n    // MARK: - 功能函数\n\n    /// 刷新端口列表\n    private func refreshPorts() {\n        isLoading = true\n        ports.removeAll()\n\n        DispatchQueue.global(qos: .userInitiated).async {\n            let detectedPorts = self.scanPorts()\n\n            DispatchQueue.main.async {\n                self.ports = detectedPorts\n                self.isLoading = false\n            }\n        }\n    }\n\n    /// 扫描常用服务端口\n    private func scanPorts() -> [PortItem] {\n        switch scanScope {\n        case .common:\n            return scanCommonPorts()\n        case .allListening:\n            return scanAllListeningPorts()\n        }\n    }\n\n    private func scanCommonPorts() -> [PortItem] {\n        var result: [PortItem] = []\n\n        // 定义要扫描的服务和端口\n        let servicePorts: [(service: String, port: Int)] = [\n            (" = ".contains(searchText) ||\n            port.processName.localizedCaseInsensitiveContains(searchText)\n        }\n    }\n\n    private var displayPorts: [PortItem] {\n        let userFiltered: [PortItem]\n        if currentUserOnly {\n            let currentUser = NSUserName()\n            userFiltered = filteredPorts.filter { $0.user == currentUser }\n        } else {\n            userFiltered = filteredPorts\n        }\n\n        switch portFilterMode {\n        case .all:\n            return userFiltered\n        case .userOnly:\n            return userFiltered.filter { $0.port >= 1024 }\n        }\n    }\n\n    // MARK: - Funciones de utilidad\n\n    /// Actualizar la lista de puertos\n    private func refreshPorts() {\n        isLoading = true\n        ports.removeAll()\n\n        DispatchQueue.global(qos: .userInitiated).async {\n            let detectedPorts = self.scanPorts()\n\n            DispatchQueue.main.async {\n                self.ports = detectedPorts\n                self.isLoading = false\n            }\n        }\n    }\n\n    /// Escanear puertos comunes de servicios\n    private func scanPorts() -> [PortItem] {\n        switch scanScope {\n        case .common:\n            return scanCommonPorts()\n        case .allListening:\n            return scanAllListeningPorts()\n        }\n    }\n\n    private func scanCommonPorts() -> [PortItem] {\n        var result: [PortItem] = []\n\n        // Definir los servicios y puertos a escanear\n        let servicePorts: [(service: String, port: Int)] = [\n            (";
"创建运行目录失败: \\(error.localizedDescription)\\n请检查路径权限或修改运行目录" = "Error al crear el directorio de ejecución: \\(error.localizedDescription)\\nVerifique los permisos de la ruta o modifique el directorio de ejecución";
"❌ 加载证书数据失败: \\(error)" = "❌ Error al cargar los datos del certificado: \\(error)";
")\n                        .foregroundColor(.secondary)\n                        .frame(width: 70, alignment: .trailing)\n                    Text(username)\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                }\n\n                // 访问权限\n                HStack(spacing: 16) {\n                    Text(" = ")\n                        .foregroundColor(.secondary)\n                        .frame(width: 70, alignment: .trailing)\n                    Text(username)\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                }\n\n                // Permisos de acceso\n                HStack(spacing: 16) {\n                    Text(";
"\n    }\n\n    /// Nginx 配置目录\n    private var nginxConfigDir: String {\n        // 优先使用工作空间路径\n        let workspaceManager = WorkspaceManager.shared\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.nginxVhostsPath\n        }\n\n        // 回退到 Homebrew 默认路径\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        let paths = [\n            " = "\n    }\n\n    /// Directorio de configuración de Nginx\n    private var nginxConfigDir: String {\n        // Priorizar la ruta del espacio de trabajo\n        let workspaceManager = WorkspaceManager.shared\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.nginxVhostsPath\n        }\n\n        // Alternativa: ruta predeterminada de Homebrew\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        let paths = [\n            ";
"\n    var corsMethods: [String] = []\n    var corsHeaders: [String] = []\n    var corsMaxAge: Int = 3600\n\n    // 错误页面\n    var error404Type: String = " = "\n    var corsMethods: [String] = []\n    var corsHeaders: [String] = []\n    var corsMaxAge: Int = 3600\n\n    // Página de error\n    var error404Type: String = ";
") && service.installed {\n                servers.append(.apache)\n            }\n        }\n        return servers\n    }\n\n    // 更换Web服务器\n    private func changeWebServer(_ newServer: WebServerType) {\n        guard newServer != site.webServer else { return }\n        Task {\n            do {\n                try await siteManager.updateSite(\n                    site: site,\n                    name: site.name,\n                    domains: site.domains,\n                    documentRoot: site.documentRoot,\n                    publicDirectory: site.publicDirectory,\n                    webServer: newServer,\n                    phpVersion: site.phpVersion,\n                    enableSSL: site.sslEnabled,\n                    forceHttps: site.forceHttps,\n                    expiryDate: site.expiryDate,\n                    note: site.note\n                )\n            } catch {\n                let alert = NSAlert()\n                alert.messageText = " = ") && service.installed {\n                servers.append(.apache)\n            }\n        }\n        return servers\n    }\n\n    // Cambiar servidor web\n    private func changeWebServer(_ newServer: WebServerType) {\n        guard newServer != site.webServer else { return }\n        Task {\n            do {\n                try await siteManager.updateSite(\n                    site: site,\n                    name: site.name,\n                    domains: site.domains,\n                    documentRoot: site.documentRoot,\n                    publicDirectory: site.publicDirectory,\n                    webServer: newServer,\n                    phpVersion: site.phpVersion,\n                    enableSSL: site.sslEnabled,\n                    forceHttps: site.forceHttps,\n                    expiryDate: site.expiryDate,\n                    note: site.note\n                )\n            } catch {\n                let alert = NSAlert()\n                alert.messageText = ";
")\n                }\n            }\n            .buttonStyle(.borderedProminent)\n            .disabled(isApplyingConfig)\n\n            Spacer()\n\n            // 刷新状态\n            Button(action: {\n                Task { await defaultSiteManager.refreshStatus() }\n            }) {\n                Image(systemName: " = ")\n                }\n            }\n            .buttonStyle(.borderedProminent)\n            .disabled(isApplyingConfig)\n\n            Spacer()\n\n            // Actualizar estado\n            Button(action: {\n                Task { await defaultSiteManager.refreshStatus() }\n            }) {\n                Image(systemName: ";
"\n            ]\n        case .custom:\n            return [:]\n        }\n    }\n\n    /// 计算预估内存使用量（MB）\n    static func calculateEstimatedMemory(configs: [String: String]) -> Double {\n        let innodbBufferPool = Double(configs[" = "\n            ]\n        case .custom:\n            return [:]\n        }\n    }\n\n    /// Calcular el uso estimado de memoria (MB)\n    static func calculateEstimatedMemory(configs: [String: String]) -> Double {\n        let innodbBufferPool = Double(configs[";
", Color(red: 0.0, green: 0.48, blue: 0.65)) // MySQL蓝色\n        } else if name.contains(" = ", Color(red: 0.0, green: 0.48, blue: 0.65)) // Azul MySQL\n        } else if name.contains(";
"\n        }\n    }\n\n    // 全部连接概览\n    private var allConnectionsOverview: some View {\n        ScrollView {\n            VStack(spacing: 0) {\n                // 表头\n                tableHeader\n\n                // 本地 MySQL 数据库列表（从 MySQL 实时获取）\n                if !databaseManager.localMySQLDatabases.isEmpty {\n                    localMySQLDatabasesSection\n                }\n\n                // 用户添加的数据库\n                if filteredDatabases.isEmpty && databaseManager.localMySQLDatabases.isEmpty {\n                    emptyStateView\n                } else if !filteredDatabases.isEmpty {\n                    ForEach(filteredDatabases) { database in\n                        DatabaseTableRow(\n                            database: database,\n                            isSelected: selectedDatabases.contains(database.id),\n                            onToggleSelection: {\n                                if selectedDatabases.contains(database.id) {\n                                    selectedDatabases.remove(database.id)\n                                } else {\n                                    selectedDatabases.insert(database.id)\n                                }\n                            },\n                            onEdit: {\n                                editingDatabase = database\n                            }\n                        )\n                        Divider()\n                    }\n                }\n            }\n        }\n    }\n\n    // 空状态视图\n    private var emptyStateView: some View {\n        VStack(spacing: 12) {\n            Image(systemName: " = "\n        }\n    }\n\n    // Resumen de todas las conexiones\n    private var allConnectionsOverview: some View {\n        ScrollView {\n            VStack(spacing: 0) {\n                // Encabezado de tabla\n                tableHeader\n\n                // Lista de bases de datos MySQL locales (obtenida en tiempo real desde MySQL)\n                if !databaseManager.localMySQLDatabases.isEmpty {\n                    localMySQLDatabasesSection\n                }\n\n                // Bases de datos añadidas por el usuario\n                if filteredDatabases.isEmpty && databaseManager.localMySQLDatabases.isEmpty {\n                    emptyStateView\n                } else if !filteredDatabases.isEmpty {\n                    ForEach(filteredDatabases) { database in\n                        DatabaseTableRow(\n                            database: database,\n                            isSelected: selectedDatabases.contains(database.id),\n                            onToggleSelection: {\n                                if selectedDatabases.contains(database.id) {\n                                    selectedDatabases.remove(database.id)\n                                } else {\n                                    selectedDatabases.insert(database.id)\n                                }\n                            },\n                            onEdit: {\n                                editingDatabase = database\n                            }\n                        )\n                        Divider()\n                    }\n                }\n            }\n        }\n    }\n\n    // Vista de estado vacío\n    private var emptyStateView: some View {\n        VStack(spacing: 12) {\n            Image(systemName: ";
"]),\n        // HTTP 配置\n        " = "]),\n        // Configuración HTTP\n        ";
")\n                                .frame(maxWidth: .infinity)\n                        }\n                        .buttonStyle(.bordered)\n                        .controlSize(.large)\n                    }\n\n                    // 输出区\n                    VStack(alignment: .leading, spacing: 8) {\n                        HStack {\n                            Text(" = ")\n                                .frame(maxWidth: .infinity)\n                        }\n                        .buttonStyle(.bordered)\n                        .controlSize(.large)\n                    }\n\n                    // Área de salida\n                    VStack(alignment: .leading, spacing: 8) {\n                        HStack {\n                            Text(";
")\n                                    .foregroundColor(.secondary)\n                            }\n                            .buttonStyle(.plain)\n                        }\n\n                        Text(result.summary)\n                            .font(.caption)\n                            .foregroundColor(.secondary)\n                            .textSelection(.enabled)\n                    }\n                    .padding(.top, 4)\n                }\n            }\n            .padding(12)\n        }\n        .padding(.horizontal)\n        .padding(.top, 8)\n    }\n\n    // 根据显示类型过滤配置分组\n    private var filteredSections: [ConfigSection] {\n        let isCommon = displayType == .common\n        return configSections.compactMap { section in\n            let filteredItems = section.items.filter { $0.isCommon == isCommon }\n            if filteredItems.isEmpty {\n                return nil\n            }\n            return ConfigSection(title: section.title, items: filteredItems)\n        }\n    }\n\n    // 原始模式视图\n    private var rawModeView: some View {\n        TextEditor(text: $configContent)\n            .font(.system(.body, design: .monospaced))\n            .macTextEditor()\n            .onChange(of: configContent) { _ in\n                hasChanges = true\n            }\n    }\n\n    // 配置工具栏\n    private var configToolbar: some View {\n        HStack(spacing: 12) {\n            Text(currentConfigPath.isEmpty ? (getConfigPath() ?? " = ")\n                                    .foregroundColor(.secondary)\n                            }\n                            .buttonStyle(.plain)\n                        }\n\n                        Text(result.summary)\n                            .font(.caption)\n                            .foregroundColor(.secondary)\n                            .textSelection(.enabled)\n                    }\n                    .padding(.top, 4)\n                }\n            }\n            .padding(12)\n        }\n        .padding(.horizontal)\n        .padding(.top, 8)\n    }\n\n    // Filtrar grupos de configuración según el tipo de visualización\n    private var filteredSections: [ConfigSection] {\n        let isCommon = displayType == .common\n        return configSections.compactMap { section in\n            let filteredItems = section.items.filter { $0.isCommon == isCommon }\n            if filteredItems.isEmpty {\n                return nil\n            }\n            return ConfigSection(title: section.title, items: filteredItems)\n        }\n    }\n\n    // Vista en modo sin formato\n    private var rawModeView: some View {\n        TextEditor(text: $configContent)\n            .font(.system(.body, design: .monospaced))\n            .macTextEditor()\n            .onChange(of: configContent) { _ in\n                hasChanges = true\n            }\n    }\n\n    // Barra de herramientas de configuración\n    private var configToolbar: some View {\n        HStack(spacing: 12) {\n            Text(currentConfigPath.isEmpty ? (getConfigPath() ?? ";
"\n            )\n        }\n\n        try content.write(toFile: path, atomically: true, encoding: .utf8)\n    }\n\n    /// 修改 PHP 端口\n    func changePHPPort(_ software: SoftwareVersion, newPort: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(" = "\n            )\n        }\n\n        try content.write(toFile: path, atomically: true, encoding: .utf8)\n    }\n\n    /// Cambiar el puerto de PHP\n    func changePHPPort(_ software: SoftwareVersion, newPort: Int) async throws {\n        guard let brewPath = brewPath else {\n            throw SoftwareError.operationFailed(";
"\n        ]\n        initProcess.standardOutput = Pipe()\n        initProcess.standardError = Pipe()\n\n        do {\n            try initProcess.run()\n            // 等待初始化完成\n            Thread.sleep(forTimeInterval: 5)\n            initProcess.terminate()\n        } catch {\n            return false\n        }\n\n        // 重新启动 MySQL 服务\n        let startProcess = Process()\n        startProcess.executableURL = URL(fileURLWithPath: brewPath)\n        startProcess.arguments = [" = "\n        ]\n        initProcess.standardOutput = Pipe()\n        initProcess.standardError = Pipe()\n\n        do {\n            try initProcess.run()\n            // Esperar a que finalice la inicialización\n            Thread.sleep(forTimeInterval: 5)\n            initProcess.terminate()\n        } catch {\n            return false\n        }\n\n        // Reiniciar el servicio MySQL\n        let startProcess = Process()\n        startProcess.executableURL = URL(fileURLWithPath: brewPath)\n        startProcess.arguments = [";
"\n        return formatter.string(from: date)\n    }\n}\n\n// MARK: - 辅助视图\nstruct AnalysisFeatureRow: View {\n    let icon: String\n    let title: LocalizedStringKey\n    let description: LocalizedStringKey\n    \n    var body: some View {\n        HStack(spacing: 12) {\n            Image(systemName: icon)\n                .font(.system(size: 16))\n                .foregroundColor(.blue)\n                .frame(width: 24)\n\n            VStack(alignment: .leading, spacing: 2) {\n                Text(title)\n                    .font(.system(size: 13, weight: .medium))\n\n                Text(description)\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n            }\n        }\n    }\n}\n\nstruct SecurityStatCard: View {\n    let title: LocalizedStringKey\n    let count: Int\n    let color: Color\n    let icon: String\n\n    var body: some View {\n        VStack(spacing: 12) {\n            Image(systemName: icon)\n                .font(.system(size: 24))\n                .foregroundColor(color)\n\n            Text(" = "\n        return formatter.string(from: date)\n    }\n}\n\n// MARK: - Vistas auxiliares\nstruct AnalysisFeatureRow: View {\n    let icon: String\n    let title: LocalizedStringKey\n    let description: LocalizedStringKey\n    \n    var body: some View {\n        HStack(spacing: 12) {\n            Image(systemName: icon)\n                .font(.system(size: 16))\n                .foregroundColor(.blue)\n                .frame(width: 24)\n\n            VStack(alignment: .leading, spacing: 2) {\n                Text(title)\n                    .font(.system(size: 13, weight: .medium))\n\n                Text(description)\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n            }\n        }\n    }\n}\n\nstruct SecurityStatCard: View {\n    let title: LocalizedStringKey\n    let count: Int\n    let color: Color\n    let icon: String\n\n    var body: some View {\n        VStack(spacing: 12) {\n            Image(systemName: icon)\n                .font(.system(size: 24))\n                .foregroundColor(color)\n\n            Text(";
"JSON 格式错误: \\(error.localizedDescription)" = "Error de formato JSON: \\(error.localizedDescription)";
"确定要删除数据库「\\(database.name)」吗？此操作不可恢复！" = "¿Realmente desea eliminar la base de datos «\\(database.name)»? Esta acción no se puede deshacer.";
")\n                            .foregroundColor(.red)\n                    }\n                }\n                .font(.caption)\n\n                // 展开/收起按钮\n                Button {\n                    withAnimation {\n                        isExpanded.toggle()\n                    }\n                } label: {\n                    Image(systemName: isExpanded ? " = ")\n                            .foregroundColor(.red)\n                    }\n                }\n                .font(.caption)\n\n                // Botón para expandir/colapsar\n                Button {\n                    withAnimation {\n                        isExpanded.toggle()\n                    }\n                } label: {\n                    Image(systemName: isExpanded ? ";
"\n        let result = await executeSQL(sql)\n\n        if result.success {\n            await fetchDatabases()\n        } else {\n            errorMessage = result.error\n        }\n\n        return result.success\n    }\n\n    /// 创建数据库并创建用户授权\n    func createDatabaseWithUser(\n        databaseName: String,\n        charset: String,\n        username: String,\n        password: String,\n        accessHost: String\n    ) async -> (success: Bool, error: String?) {\n        // 1. 创建数据库\n        let collation = charset == " = "\n        let result = await executeSQL(sql)\n\n        if result.success {\n            await fetchDatabases()\n        } else {\n            errorMessage = result.error\n        }\n\n        return result.success\n    }\n\n    /// Crear una base de datos y asignar permisos al usuario\n    func createDatabaseWithUser(\n        databaseName: String,\n        charset: String,\n        username: String,\n        password: String,\n        accessHost: String\n    ) async -> (success: Bool, error: String?) {\n        // 1. Crear la base de datos\n        let collation = charset == ";
")\n                case .all:\n                    return true\n                }\n            }\n        }\n\n        // 按搜索文本过滤\n        if !searchText.isEmpty {\n            filtered = filtered.filter { line in\n                line.localizedCaseInsensitiveContains(searchText)\n            }\n        }\n\n        return filtered\n    }\n\n    // MARK: - 功能函数\n\n    /// 加载日志文件\n    private func loadLog(for service: ServiceLog) {\n        isLoading = true\n        logContent = " = ")\n                case .all:\n                    return true\n                }\n            }\n        }\n\n        // Filtrar por texto de búsqueda\n        if !searchText.isEmpty {\n            filtered = filtered.filter { line in\n                line.localizedCaseInsensitiveContains(searchText)\n            }\n        }\n\n        return filtered\n    }\n\n    // MARK: - Funciones de funcionalidad\n\n    /// Cargar archivo de registro\n    private func loadLog(for service: ServiceLog) {\n        isLoading = true\n        logContent = ";
"剩余天数: \\(extractDays(from: site.sslStatus.rawValue)) 天" = "Días restantes: \\(extractDays(from: site.sslStatus.rawValue)) días";
")\n            return (domain, .auto)\n        }\n\n        // 规则3: 检查是否为 IPv4 地址\n        let ipv4Pattern = " = ")\n            return (domain, .auto)\n        }\n\n        // Regla 3: Verificar si es una dirección IPv4\n        let ipv4Pattern = ";
"已修复 \\(activeSites.count) 个站点的配置文件，并补齐缺失的 hosts 记录" = "Se han corregido los archivos de configuración de \\(activeSites.count) sitios y se han completado los registros faltantes en hosts";
", Double(sizeBytes) / (1024.0 * 1024.0 * 1024.0))\n        }\n    }\n\n    // 容量使用百分比（假设最大 10GB）\n    var sizePercentage: Double {\n        let maxSize: Double = 10 * 1024 * 1024 * 1024  // 10GB\n        return min(Double(sizeBytes) / maxSize, 1.0)\n    }\n}\n\n// MARK: - MySQL 用户信息\nstruct MySQLUserInfo: Identifiable {\n    let id = UUID()\n    let user: String\n    let host: String\n\n    var displayName: String {\n        " = ", Double(sizeBytes) / (1024.0 * 1024.0 * 1024.0))\n        }\n    }\n\n    // Porcentaje de uso de capacidad (suponiendo un máximo de 10 GB)\n    var sizePercentage: Double {\n        let maxSize: Double = 10 * 1024 * 1024 * 1024  // 10 GB\n        return min(Double(sizeBytes) / maxSize, 1.0)\n    }\n}\n\n// MARK: - Información del usuario de MySQL\nstruct MySQLUserInfo: Identifiable {\n    let id = UUID()\n    let user: String\n    let host: String\n\n    var displayName: String {\n        ";
"JSON 格式有误: \\(error.localizedDescription)。已尝试强制格式化，请检查红线标注处。" = "El formato JSON es incorrecto: \\(error.localizedDescription). Se ha intentado formatear forzadamente; revise las partes resaltadas en rojo.";
", path: nginxPath))\n        }\n\n        // Apache 目录（优先使用自定义路径）\n        let apachePath: String\n        if let customPath = customApachePath {\n            apachePath = customPath\n        } else if let config = workspaceManager.config, config.isInitialized {\n            apachePath = config.apacheVhostsPath\n        } else {\n            apachePath = " = ", path: nginxPath))\n        }\n\n        // Directorio de Apache (priorizar ruta personalizada)\n        let apachePath: String\n        if let customPath = customApachePath {\n            apachePath = customPath\n        } else if let config = workspaceManager.config, config.isInitialized {\n            apachePath = config.apacheVhostsPath\n        } else {\n            apachePath = ";
")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            // 表单内容\n            VStack(spacing: 16) {\n                // 数据库名（只读）\n                HStack {\n                    Text(" = ")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            // Contenido del formulario\n            VStack(spacing: 16) {\n                // Nombre de la base de datos (solo lectura)\n                HStack {\n                    Text(";
"\n}\n\n/// SSL证书状态\nenum CertificateStatus: Codable {\n    case valid          // 有效\n    case expiringSoon   // 即将过期（30天内）\n    case expired        // 已过期\n\n    var displayText: String {\n        switch self {\n        case .valid:\n            return " = "\n}\n\n/// Estado del certificado SSL\nenum CertificateStatus: Codable {\n    case valid          // Válido\n    case expiringSoon   // Caducará pronto (dentro de 30 días)\n    case expired        // Caducado\n\n    var displayText: String {\n        switch self {\n        case .valid:\n            return ";
"\n        default:\n            return nil\n        }\n    }\n\n    // 获取默认配置内容\n    private func getDefaultConfigContent() -> String {\n        switch service.name {\n        case " = "\n        default:\n            return nil\n        }\n    }\n\n    // Obtener contenido de configuración predeterminado\n    private func getDefaultConfigContent() -> String {\n        switch service.name {\n        case ";
"✗ 端口 \\(port) 未监听" = "✗ Port \\(port) is not listening";
")\n                            .font(.caption)\n                            .foregroundColor(.secondary)\n                    }\n                }\n\n                // 提示信息\n                VStack(alignment: .leading, spacing: 8) {\n                    HStack {\n                        Image(systemName: " = ")\n                            .font(.caption)\n                            .foregroundColor(.secondary)\n                    }\n                }\n\n                // Hint message\n                VStack(alignment: .leading, spacing: 8) {\n                    HStack {\n                        Image(systemName: ";
"安装证书失败：\\(output)" = "Failed to install certificate: \\(output)";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // MARK: - 内容区域\n    @ViewBuilder\n    private var contentArea: some View {\n        if isAnalyzing {\n            analyzingView\n        } else if let error = errorMessage {\n            errorView(message: error)\n        } else if !isGoAccessInstalled {\n            notInstalledView\n        } else if let report = report {\n            reportView(report: report)\n        } else {\n            fileSelectionView\n        }\n    }\n\n    // MARK: - 文件选择视图\n    private var fileSelectionView: some View {\n        VStack(spacing: 24) {\n            Image(systemName: " = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // MARK: - Content Area\n    @ViewBuilder\n    private var contentArea: some View {\n        if isAnalyzing {\n            analyzingView\n        } else if let error = errorMessage {\n            errorView(message: error)\n        } else if !isGoAccessInstalled {\n            notInstalledView\n        } else if let report = report {\n            reportView(report: report)\n        } else {\n            fileSelectionView\n        }\n    }\n\n    // MARK: - File Selection View\n    private var fileSelectionView: some View {\n        VStack(spacing: 24) {\n            Image(systemName: ";
"端口 \\(port) 已被 \\(conflictSoftware) 占用，请先停止该服务" = "Port \\(port) is occupied by \\(conflictSoftware). Please stop that service first.";
"]),\n        // 日志配置\n        " = "]),\n        // Log configuration\n        ";
")\n        }\n        \n        // 1. 复制整个 _errorpage_ 目录到站点运行目录\n        let errorPageTargetDir = (documentRoot as NSString).appendingPathComponent(" = ")\n        }\n        \n        // 1. Copy the entire _errorpage_ directory to the site's runtime directory\n        let errorPageTargetDir = (documentRoot as NSString).appendingPathComponent(";
"  // 公开目录\n    @State private var siteDirectory = " = "  // Public directory\n    @State private var siteDirectory = ";
"]),\n    ]\n\n    // MARK: - PHP 扩展字典\n    static let phpExtensions: [String: PHPExtensionMeta] = [\n        // ===== 脚本解密 =====\n        " = "]),\n    ]\n\n    // MARK: - PHP Extension Dictionary\n    static let phpExtensions: [String: PHPExtensionMeta] = [\n        // ===== Script Decryption =====\n        ";
" ? Color.white.opacity(0.1) : Color(NSColor.controlBackgroundColor).opacity(AppTokens.Opacity.border))\n    }\n\n    // 状态图标\n    @ViewBuilder\n    private func statusIcon(for type: StatusMessage.StatusType) -> some View {\n        switch type {\n        case .loading:\n            ProgressView()\n                .scaleEffect(0.6)\n                .frame(width: 16, height: 16)\n        case .success:\n            Image(systemName: " = " ? Color.white.opacity(0.1) : Color(NSColor.controlBackgroundColor).opacity(AppTokens.Opacity.border))\n    }\n\n    // Status icon\n    @ViewBuilder\n    private func statusIcon(for type: StatusMessage.StatusType) -> some View {\n        switch type {\n        case .loading:\n            ProgressView()\n                .scaleEffect(0.6)\n                .frame(width: 16, height: 16)\n        case .success:\n            Image(systemName: ";
"\n\n    // 获取可用的 PHP 版本列表\n    private var availablePHPVersions: [String] {\n        softwareManager.phpVersions\n            .filter { $0.installed }\n            .map { $0.version }\n            .sorted()\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            headerView\n            Divider()\n\n            if isScanning {\n                scanningView\n            } else if candidates.isEmpty {\n                emptyView\n            } else {\n                candidateListView\n            }\n\n            Divider()\n            footerView\n        }\n        .frame(width: 900, height: 700)\n        .onAppear {\n            // 初始化默认 PHP 版本\n            if defaultPHPVersion.isEmpty && !availablePHPVersions.isEmpty {\n                defaultPHPVersion = availablePHPVersions.first ?? " = "\n\n    // Retrieve the list of available PHP versions\n    private var availablePHPVersions: [String] {\n        softwareManager.phpVersions\n            .filter { $0.installed }\n            .map { $0.version }\n            .sorted()\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            headerView\n            Divider()\n\n            if isScanning {\n                scanningView\n            } else if candidates.isEmpty {\n                emptyView\n            } else {\n                candidateListView\n            }\n\n            Divider()\n            footerView\n        }\n        .frame(width: 900, height: 700)\n        .onAppear {\n            // Initialize default PHP version\n            if defaultPHPVersion.isEmpty && !availablePHPVersions.isEmpty {\n                defaultPHPVersion = availablePHPVersions.first ?? ";
" && !software.installed {\n            await MainActor.run {\n                showPureFTPInstallSheet = true\n            }\n            return\n        }\n\n        // PHP 端口冲突检测\n        if software.name.lowercased().contains(" = " && !software.installed {\n            await MainActor.run {\n                showPureFTPInstallSheet = true\n            }\n            return\n        }\n\n        // PHP port conflict detection\n        if software.name.lowercased().contains(";
"站点统计 - \\(site.name)" = "Site Statistics - \\(site.name)";
"\n            )\n        }\n    }\n\n    // MARK: - 站点容量计算\n\n    // 定时器任务\n    private var sizeCalculationTask: Task<Void, Never>?\n\n    // 启动定时计算容量（每5分钟计算一次）\n    func startPeriodicSizeCalculation(interval: TimeInterval = 300) {\n        stopPeriodicSizeCalculation()\n        sizeCalculationTask = Task {\n            while !Task.isCancelled {\n                await calculateAllSiteSizes()\n                try? await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000))\n            }\n        }\n    }\n\n    // 停止定时计算\n    func stopPeriodicSizeCalculation() {\n        sizeCalculationTask?.cancel()\n        sizeCalculationTask = nil\n    }\n\n    // 计算所有站点的容量（异步）\n    func calculateAllSiteSizes() async {\n        for index in sites.indices {\n            guard !sites[index].isDeleted else { continue }\n            let path = sites[index].documentRoot\n            let size = await calculateDirectorySize(path: path)\n            sites[index].directorySize = size\n            sites[index].directorySizeUpdatedAt = Date()\n        }\n        saveSites()\n    }\n\n    // 计算单个站点容量\n    func calculateSiteSize(for site: Site) async {\n        guard let index = sites.firstIndex(where: { $0.id == site.id }) else { return }\n        let size = await calculateDirectorySize(path: site.documentRoot)\n        sites[index].directorySize = size\n        sites[index].directorySizeUpdatedAt = Date()\n        saveSites()\n    }\n\n    // 异步计算目录大小（在后台线程执行）\n    private func calculateDirectorySize(path: String) async -> Int64 {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .utility).async {\n                let size = self.getDirectorySizeSync(path: path)\n                continuation.resume(returning: size)\n            }\n        }\n    }\n\n    // 同步计算目录大小\n    private func getDirectorySizeSync(path: String) -> Int64 {\n        let fileManager = FileManager.default\n        var totalSize: Int64 = 0\n\n        guard let enumerator = fileManager.enumerator(\n            at: URL(fileURLWithPath: path),\n            includingPropertiesForKeys: [.fileSizeKey, .isDirectoryKey],\n            options: [.skipsHiddenFiles]\n        ) else {\n            return 0\n        }\n\n        for case let fileURL as URL in enumerator {\n            do {\n                let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey, .isDirectoryKey])\n                if resourceValues.isDirectory == false {\n                    totalSize += Int64(resourceValues.fileSize ?? 0)\n                }\n            } catch {\n                continue\n            }\n        }\n\n        return totalSize\n    }\n\n    // MARK: - 配置文件同步功能\n\n    /// 扫描并解析 vhost 配置文件\n    /// 返回找到的配置列表，包含冲突信息\n    /// - Parameter onProgress: 进度回调，报告当前扫描的目录\n    func scanVhostConfigs(onProgress: ((String) -> Void)? = nil) async -> [FoundVhostConfig] {\n        var foundConfigs: [FoundVhostConfig] = []\n\n        // 扫描 Nginx 配置\n        if installedWebServers.contains(.nginx) {\n            let nginxConfigs = await scanNginxConfigs(onProgress: onProgress)\n            foundConfigs.append(contentsOf: nginxConfigs)\n        }\n\n        // 扫描 Apache 配置\n        if installedWebServers.contains(.apache) {\n            let apacheConfigs = await scanApacheConfigs(onProgress: onProgress)\n            foundConfigs.append(contentsOf: apacheConfigs)\n        }\n\n        // 检测冲突（相同域名在不同服务器中）\n        foundConfigs = detectConflicts(foundConfigs)\n\n        // 检查配置完整性\n        foundConfigs = checkConfigCompleteness(foundConfigs)\n\n        // 不再过滤掉完整的 Nginx 代理配置，让用户可以看到所有配置\n        // 用户可以自行决定是否导入\n\n        return foundConfigs\n    }\n\n    /// 扫描 Nginx 配置文件\n    private func scanNginxConfigs(onProgress: ((String) -> Void)? = nil) async -> [FoundVhostConfig] {\n        // 获取 Nginx 配置目录路径\n        let workspaceManager = WorkspaceManager.shared\n        let nginxVhostPath: String\n\n        if let config = workspaceManager.config, config.isInitialized {\n            nginxVhostPath = config.nginxVhostsPath\n        } else {\n            let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n            nginxVhostPath = " = "\n            )\n        }\n    }\n\n    // MARK: - Site Capacity Calculation\n\n    // Timer task\n    private var sizeCalculationTask: Task<Void, Never>?\n\n    // Start periodic capacity calculation (every 5 minutes)\n    func startPeriodicSizeCalculation(interval: TimeInterval = 300) {\n        stopPeriodicSizeCalculation()\n        sizeCalculationTask = Task {\n            while !Task.isCancelled {\n                await calculateAllSiteSizes()\n                try? await Task.sleep(nanoseconds: UInt64(interval * 1_000_000_000))\n            }\n        }\n    }\n\n    // Stop periodic calculation\n    func stopPeriodicSizeCalculation() {\n        sizeCalculationTask?.cancel()\n        sizeCalculationTask = nil\n    }\n\n    // Calculate capacity for all sites (asynchronously)\n    func calculateAllSiteSizes() async {\n        for index in sites.indices {\n            guard !sites[index].isDeleted else { continue }\n            let path = sites[index].documentRoot\n            let size = await calculateDirectorySize(path: path)\n            sites[index].directorySize = size\n            sites[index].directorySizeUpdatedAt = Date()\n        }\n        saveSites()\n    }\n\n    // Calculate capacity for a single site\n    func calculateSiteSize(for site: Site) async {\n        guard let index = sites.firstIndex(where: { $0.id == site.id }) else { return }\n        let size = await calculateDirectorySize(path: site.documentRoot)\n        sites[index].directorySize = size\n        sites[index].directorySizeUpdatedAt = Date()\n        saveSites()\n    }\n\n    // Asynchronously calculate directory size (executed on background thread)\n    private func calculateDirectorySize(path: String) async -> Int64 {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .utility).async {\n                let size = self.getDirectorySizeSync(path: path)\n                continuation.resume(returning: size)\n            }\n        }\n    }\n\n    // Synchronously calculate directory size\n    private func getDirectorySizeSync(path: String) -> Int64 {\n        let fileManager = FileManager.default\n        var totalSize: Int64 = 0\n\n        guard let enumerator = fileManager.enumerator(\n            at: URL(fileURLWithPath: path),\n            includingPropertiesForKeys: [.fileSizeKey, .isDirectoryKey],\n            options: [.skipsHiddenFiles]\n        ) else {\n            return 0\n        }\n\n        for case let fileURL as URL in enumerator {\n            do {\n                let resourceValues = try fileURL.resourceValues(forKeys: [.fileSizeKey, .isDirectoryKey])\n                if resourceValues.isDirectory == false {\n                    totalSize += Int64(resourceValues.fileSize ?? 0)\n                }\n            } catch {\n                continue\n            }\n        }\n\n        return totalSize\n    }\n\n    // MARK: - Configuration File Synchronization Feature\n\n    /// Scan and parse vhost configuration files\n    /// Returns a list of found configurations, including conflict information\n    /// - Parameter onProgress: Progress callback reporting the currently scanned directory\n    func scanVhostConfigs(onProgress: ((String) -> Void)? = nil) async -> [FoundVhostConfig] {\n        var foundConfigs: [FoundVhostConfig] = []\n\n        // Scan Nginx configurations\n        if installedWebServers.contains(.nginx) {\n            let nginxConfigs = await scanNginxConfigs(onProgress: onProgress)\n            foundConfigs.append(contentsOf: nginxConfigs)\n        }\n\n        // Scan Apache configurations\n        if installedWebServers.contains(.apache) {\n            let apacheConfigs = await scanApacheConfigs(onProgress: onProgress)\n            foundConfigs.append(contentsOf: apacheConfigs)\n        }\n\n        // Detect conflicts (same domain across different servers)\n        foundConfigs = detectConflicts(foundConfigs)\n\n        // Check configuration completeness\n        foundConfigs = checkConfigCompleteness(foundConfigs)\n\n        // No longer filter out complete Nginx proxy configurations; let users see all configurations\n        // Users can decide whether to import them\n\n        return foundConfigs\n    }\n\n    /// Scan Nginx configuration files\n    private func scanNginxConfigs(onProgress: ((String) -> Void)? = nil) async -> [FoundVhostConfig] {\n        // Get Nginx configuration directory path\n        let workspaceManager = WorkspaceManager.shared\n        let nginxVhostPath: String\n\n        if let config = workspaceManager.config, config.isInitialized {\n            nginxVhostPath = config.nginxVhostsPath\n        } else {\n            let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n            nginxVhostPath = ";
" {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(width: 1000, height: 600)\n    }\n\n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            HStack {\n                Text(" = " {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(width: 1000, height: 600)\n    }\n\n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // Title bar\n            HStack {\n                Text(";
"\n\n        // 检查目录是否存在\n        guard fileManager.fileExists(atPath: fullPath) else {\n            return HealthCheckResult(\n                type: .php,\n                status: .error,\n                message: " = "\n\n        // Check if directory exists\n        guard fileManager.fileExists(atPath: fullPath) else {\n            return HealthCheckResult(\n                type: .php,\n                status: .error,\n                message: ";
"❌ 解析证书失败: \\(error)" = "❌ Failed to parse certificate: \\(error)";
")\n        }\n\n        // 读取握手包\n        try readHandshake()\n\n        // 发送认证响应\n        try sendAuthResponse()\n\n        // 读取认证结果\n        try readAuthResult()\n    }\n\n    /// 断开连接\n    func disconnect() {\n        inputStream?.close()\n        outputStream?.close()\n        inputStream = nil\n        outputStream = nil\n    }\n\n    // MARK: - MySQL 协议实现\n\n    /// 读取握手包\n    private func readHandshake() throws {\n        let packet = try readPacket()\n        guard packet.count > 0 else {\n            throw MySQLError.invalidResponse\n        }\n\n        var offset = 0\n\n        // 协议版本\n        let protocolVersion = packet[offset]\n        offset += 1\n\n        guard protocolVersion == MySQLConstants.protocolVersion else {\n            throw MySQLError.connectionFailed(" = ")\n        }\n\n        // Read handshake packet\n        try readHandshake()\n\n        // Send authentication response\n        try sendAuthResponse()\n\n        // Read authentication result\n        try readAuthResult()\n    }\n\n    /// Disconnect\n    func disconnect() {\n        inputStream?.close()\n        outputStream?.close()\n        inputStream = nil\n        outputStream = nil\n    }\n\n    // MARK: - MySQL Protocol Implementation\n\n    /// Read handshake packet\n    private func readHandshake() throws {\n        let packet = try readPacket()\n        guard packet.count > 0 else {\n            throw MySQLError.invalidResponse\n        }\n\n        var offset = 0\n\n        // Protocol version\n        let protocolVersion = packet[offset]\n        offset += 1\n\n        guard protocolVersion == MySQLConstants.protocolVersion else {\n            throw MySQLError.connectionFailed(";
"\n        case bandwidth\n    }\n}\n\nstruct VisitorsStats: Codable {\n    let data: [VisitorData]?\n}\n\nstruct VisitorData: Codable {\n    let date: String?\n    let hits: Int?\n    let visitors: Int?\n}\n\nstruct RequestItem: Codable {\n    let data: String?\n    let hits: Int?\n    let visitors: Int?\n    let percent: Double?\n}\n\nstruct HostItem: Codable {\n    let data: String?\n    let hits: Int?\n    let visitors: Int?\n}\n\nstruct OSItem: Codable {\n    let data: String?\n    let hits: Int?\n    let visitors: Int?\n}\n\nstruct BrowserItem: Codable {\n    let data: String?\n    let hits: Int?\n    let visitors: Int?\n}\n\nstruct StatusCodeItem: Codable {\n    let data: String?\n    let hits: Int?\n}\n\n// MARK: - GoAccess 服务\n@MainActor\nclass GoAccessService {\n    static let shared = GoAccessService()\n\n    private init() {}\n\n    // MARK: - 检查 GoAccess 是否安装\n    func isGoAccessInstalled() async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: " = "\n        case bandwidth\n    }\n}\n\nstruct VisitorsStats: Codable {\n    let data: [VisitorData]?\n}\n\nstruct VisitorData: Codable {\n    let date: String?\n    let hits: Int?\n    let visitors: Int?\n}\n\nstruct RequestItem: Codable {\n    let data: String?\n    let hits: Int?\n    let visitors: Int?\n    let percent: Double?\n}\n\nstruct HostItem: Codable {\n    let data: String?\n    let hits: Int?\n    let visitors: Int?\n}\n\nstruct OSItem: Codable {\n    let data: String?\n    let hits: Int?\n    let visitors: Int?\n}\n\nstruct BrowserItem: Codable {\n    let data: String?\n    let hits: Int?\n    let visitors: Int?\n}\n\nstruct StatusCodeItem: Codable {\n    let data: String?\n    let hits: Int?\n}\n\n// MARK: - GoAccess Service\n@MainActor\nclass GoAccessService {\n    static let shared = GoAccessService()\n\n    private init() {}\n\n    // MARK: - Check if GoAccess is installed\n    func isGoAccessInstalled() async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: ";
"保存配置失败: \\(error)" = "Failed to save configuration: \\(error)";
"已导出 \\(sitesToExport.count) 个站点配置到: \\(url.path)" = "Se han exportado \\(sitesToExport.count) configuraciones de sitios a: \\(url.path)";
"\n        do {\n            try config.write(toFile: configPath, atomically: true, encoding: .utf8)\n            return true\n        } catch {\n            return false\n        }\n    }\n\n    /// 生成 Nginx 配置\n    private func generateNginxConfig(port: String) -> String {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        return " = "\n        do {\n            try config.write(toFile: configPath, atomically: true, encoding: .utf8)\n            return true\n        } catch {\n            return false\n        }\n    }\n\n    /// Generar la configuración de Nginx\n    private func generateNginxConfig(port: String) -> String {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        return ";
") {\n                inMysqldSection = false\n                continue\n            }\n\n            // 在 [mysqld] 段内查找并更新配置\n            if inMysqldSection {\n                if trimmed.hasPrefix(key + " = ") {\n                inMysqldSection = false\n                continue\n            }\n\n            // Buscar y actualizar la configuración dentro de la sección [mysqld]\n            if inMysqldSection {\n                if trimmed.hasPrefix(key + ";
")\n                                    .font(.system(size: 14))\n                                    .foregroundColor(.red.opacity(AppTokens.Opacity.textSecondary))\n                            }\n                            .buttonStyle(.plain)\n                            .frame(width: 60, alignment: .center)\n                        }\n                        .macTextEditor()\n                        .padding(.vertical, 12)\n                        \n                        if rule.id != config.accessRules.last?.id {\n                            Divider()\n                        }\n                    }\n                }\n            .glass(.input)\n                .overlay(\n                    RoundedRectangle(cornerRadius: 12)\n                        .stroke(Color.gray.opacity(AppTokens.Opacity.hover), lineWidth: 1)\n                )\n            }\n            \n            // 添加规则表单\n            if showAddRuleForm {\n                VStack(alignment: .leading, spacing: 20) {\n                    HStack {\n                        Text(LocalizedStringKey(" = ")\n                                    .font(.system(size: 14))\n                                    .foregroundColor(.red.opacity(AppTokens.Opacity.textSecondary))\n                            }\n                            .buttonStyle(.plain)\n                            .frame(width: 60, alignment: .center)\n                        }\n                        .macTextEditor()\n                        .padding(.vertical, 12)\n                        \n                        if rule.id != config.accessRules.last?.id {\n                            Divider()\n                        }\n                    }\n                }\n            .glass(.input)\n                .overlay(\n                    RoundedRectangle(cornerRadius: 12)\n                        .stroke(Color.gray.opacity(AppTokens.Opacity.hover), lineWidth: 1)\n                )\n            }\n            \n            // Formulario para agregar regla\n            if showAddRuleForm {\n                VStack(alignment: .leading, spacing: 20) {\n                    HStack {\n                        Text(LocalizedStringKey(";
"已导出到: \\(url.path)" = "Exportado a: \\(url.path)";
", text: $port)\n                            .macInput()\n                    }\n                    .frame(width: 120) // 固定端口宽度\n                }\n\n                // 管理员名称 & 密码\n                HStack(spacing: 20) {\n                    FormField(" = ", text: $port)\n                            .macInput()\n                    }\n                    .frame(width: 120) // Ancho fijo para el puerto\n                }\n\n                // Nombre y contraseña del administrador\n                HStack(spacing: 20) {\n                    FormField(";
":\n            // 提取主次版本号\n            let majorMinor = extractMajorMinorVersion(version)\n            configPaths = [\n                " = ":\n            // Extraer las versiones principal y secundaria\n            let majorMinor = extractMajorMinorVersion(version)\n            configPaths = [\n                ";
"\n\n        // 解析 RewriteRule 格式: RewriteRule pattern replacement [flags]\n        let parts = rule.components(separatedBy: .whitespaces).filter { !$0.isEmpty }\n        guard parts.count >= 3 else {\n            warnings.append(" = "\n\n        // Analizar la regla RewriteRule: RewriteRule patrón reemplazo [banderas]\n        let parts = rule.components(separatedBy: .whitespaces).filter { !$0.isEmpty }\n        guard parts.count >= 3 else {\n            warnings.append(";
"\\(service.displayName) 可执行文件不存在：\\(binaryPath)" = "El ejecutable de \\(service.displayName) no existe: \\(binaryPath)";
"\n\n    init() {\n        loadHosts()\n    }\n\n    // 加载 hosts 文件\n    func loadHosts() {\n        isLoading = true\n        errorMessage = nil\n\n        DispatchQueue.global(qos: .userInitiated).async {\n            do {\n                let content = try String(contentsOfFile: self.hostsPath, encoding: .utf8)\n                let entries = self.parseHostsContent(content)\n\n                DispatchQueue.main.async {\n                    self.entries = entries\n                    self.isLoading = false\n                }\n            } catch {\n                DispatchQueue.main.async {\n                    self.errorMessage = " = "\n\n    init() {\n        loadHosts()\n    }\n\n    // Cargar el archivo hosts\n    func loadHosts() {\n        isLoading = true\n        errorMessage = nil\n\n        DispatchQueue.global(qos: .userInitiated).async {\n            do {\n                let content = try String(contentsOfFile: self.hostsPath, encoding: .utf8)\n                let entries = self.parseHostsContent(content)\n\n                DispatchQueue.main.async {\n                    self.entries = entries\n                    self.isLoading = false\n                }\n            } catch {\n                DispatchQueue.main.async {\n                    self.errorMessage = ";
"⚠ \\(domain) 已禁用" = "⚠ \\(domain) está deshabilitado";
"❌ 更新失败 \\(failedFiles.count) 个配置文件:" = "❌ Error al actualizar \\(failedFiles.count) archivos de configuración:";
"\n        # MySQL 数据库认证\n        MySQLConfigFile             \\(configDir)/pureftpd-mysql.conf\n\n        " = "\n        # Autenticación de base de datos MySQL\n        Archivo de configuración MySQL             \\(configDir)/pureftpd-mysql.conf\n\n        ";
"端口 \\(port) 已被 \\(process) 占用\\n\\n建议：\\(suggestion)" = "El puerto \\(port) ya está ocupado por \\(process)\\n\\nSugerencia: \\(suggestion)";
")\n                    .font(.system(size: 13))\n                TextField(String(currentPort), text: $customPort)\n                    .macInput()\n                    .frame(width: 80)\n                    .onChange(of: customPort) { newValue in\n                        portAvailable = nil\n                        isCurrentPort = false\n                        suggestedPort = nil\n                        // 自动检测端口\n                        Task {\n                            try? await Task.sleep(nanoseconds: 500_000_000) // 延迟0.5秒\n                            await checkPortAvailability()\n                        }\n                    }\n\n                portStatusIndicator\n\n                Spacer()\n\n                Button(" = ")\n                    .font(.system(size: 13))\n                TextField(String(currentPort), text: $customPort)\n                    .macInput()\n                    .frame(width: 80)\n                    .onChange(of: customPort) { newValue in\n                        portAvailable = nil\n                        isCurrentPort = false\n                        suggestedPort = nil\n                        // Detección automática del puerto\n                        Task {\n                            try? await Task.sleep(nanoseconds: 500_000_000) // Retraso de 0,5 segundos\n                            await checkPortAvailability()\n                        }\n                    }\n\n                portStatusIndicator\n\n                Spacer()\n\n                Button(";
"\n        var hasSSL = false\n        var ports: [Int] = []\n        var isProxy = false\n        var proxyBackendPort: Int? = nil\n\n        // 解析配置文件\n        let lines = content.components(separatedBy: .newlines)\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n\n            // 解析 server_name\n            if trimmed.hasPrefix(" = "\n        var hasSSL = false\n        var ports: [Int] = []\n        var isProxy = false\n        var proxyBackendPort: Int? = nil\n\n        // Analizar el archivo de configuración\n        let lines = content.components(separatedBy: .newlines)\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n\n            // Analizar server_name\n            if trimmed.hasPrefix(";
")\n                if !path.isEmpty {\n                    return path\n                }\n            }\n        }\n        return nil\n    }\n\n    // MARK: - 打开默认站点\n\n    /// 在浏览器中打开默认站点\n    func openDefaultSiteInBrowser() {\n        let settings = AppSettings.shared\n        let port = settings.defaultSitePort\n\n        var urlString = " = ")\n                if !path.isEmpty {\n                    return path\n                }\n            }\n        }\n        return nil\n    }\n\n    // MARK: - Abrir el sitio predeterminado\n\n    /// Abrir el sitio predeterminado en el navegador\n    func openDefaultSiteInBrowser() {\n        let settings = AppSettings.shared\n        let port = settings.defaultSitePort\n\n        var urlString = ";
"\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 表单内容\n            formContent\n\n            Divider()\n\n            // 底部按钮\n            bottomButtons\n        }\n        .frame(width: 450, height: 350)\n        .alert(" = "\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Barra de título\n            headerBar\n\n            Divider()\n\n            // Contenido del formulario\n            formContent\n\n            Divider()\n\n            // Botones inferiores\n            bottomButtons\n        }\n        .frame(width: 450, height: 350)\n        .alert(";
"\n        }\n\n        // 生成证书文件\n        let (certPath, keyPath) = try await CertificateManager.shared.generateCertificate(\n            domains: site.domains,\n            outputDirectory: sslDir,\n            certificateName: site.name\n        )\n\n        // 解析证书信息\n        let certInfo = CertificateManager.shared.parseCertificateInfo(certPath: certPath)\n\n        let certificate = Certificate(\n            domains: site.domains,\n            certificateType: .selfSigned,\n            certificatePath: certPath,\n            privateKeyPath: keyPath,\n            issuer: certInfo?.issuer ?? " = "\n        }\n\n        // Generar los archivos de certificado\n        let (certPath, keyPath) = try await CertificateManager.shared.generateCertificate(\n            domains: site.domains,\n            outputDirectory: sslDir,\n            certificateName: site.name\n        )\n\n        // Analizar la información del certificado\n        let certInfo = CertificateManager.shared.parseCertificateInfo(certPath: certPath)\n\n        let certificate = Certificate(\n            domains: site.domains,\n            certificateType: .selfSigned,\n            certificatePath: certPath,\n            privateKeyPath: keyPath,\n            issuer: certInfo?.issuer ?? ";
"\n    @State private var scanScope: ScanScope = .common\n    @State private var portFilterMode: PortFilterMode = .userOnly\n    @State private var currentUserOnly = false\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 头部工具栏\n            HStack {\n                Text(" = "\n    @State private var scanScope: ScanScope = .common\n    @State private var portFilterMode: PortFilterMode = .userOnly\n    @State private var currentUserOnly = false\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Barra de herramientas superior\n            HStack {\n                Text(";
")\n        }\n        \n        return (true, true, nil)\n    }\n    \n    // 检查路径链的权限（从根目录开始检查每一级）\n    func validatePathChain(_ path: String) -> (exists: Bool, writable: Bool, error: String?, firstMissingPath: String?) {\n        let fileManager = FileManager.default\n        let normalizedPath = (path as NSString).standardizingPath\n        var isDirectory: ObjCBool = false\n        \n        // 如果路径本身存在，检查是否有写权限\n        if fileManager.fileExists(atPath: normalizedPath, isDirectory: &isDirectory) {\n            if !isDirectory.boolValue {\n                return (true, false, " = ")\n        }\n        \n        return (true, true, nil)\n    }\n    \n    // Check permissions for the path chain (check each level starting from root)\n    func validatePathChain(_ path: String) -> (exists: Bool, writable: Bool, error: String?, firstMissingPath: String?) {\n        let fileManager = FileManager.default\n        let normalizedPath = (path as NSString).standardizingPath\n        var isDirectory: ObjCBool = false\n        \n        // If the path itself exists, check write permission\n        if fileManager.fileExists(atPath: normalizedPath, isDirectory: &isDirectory) {\n            if !isDirectory.boolValue {\n                return (true, false, ";
"读取目录失败: \\(error.localizedDescription)" = "Failed to read directory: \\(error.localizedDescription)";
" {\n                PageHeader(title: LocalizedStringKey(selectedTab.rawValue))\n            }\n\n            // 内容区域\n            contentView\n        }\n    }\n\n    @ViewBuilder\n    private var contentView: some View {\n        switch selectedTab {\n        case .dashboard:\n            DashboardView()\n        case .sites:\n            SitesListView()\n        case .logs:\n            LogsManagementView()\n        case .database:\n            DatabaseListView()\n        case .tools:\n            ToolsGridView(selectedTool: $selectedTool)\n        case .software:\n            SoftwareListView()\n        case .settings:\n            SettingsView()\n        }\n    }\n}\n\nstruct PageHeader: View {\n    let title: LocalizedStringKey\n\n    var body: some View {\n        HStack {\n            Text(title)\n                .font(.system(size: 20, weight: .semibold))\n            Spacer()\n            Button(action: {}) {\n                Image(systemName: " = " {\n                PageHeader(title: LocalizedStringKey(selectedTab.rawValue))\n            }\n\n            // Content area\n            contentView\n        }\n    }\n\n    @ViewBuilder\n    private var contentView: some View {\n        switch selectedTab {\n        case .dashboard:\n            DashboardView()\n        case .sites:\n            SitesListView()\n        case .logs:\n            LogsManagementView()\n        case .database:\n            DatabaseListView()\n        case .tools:\n            ToolsGridView(selectedTool: $selectedTool)\n        case .software:\n            SoftwareListView()\n        case .settings:\n            SettingsView()\n        }\n    }\n}\n\nstruct PageHeader: View {\n    let title: LocalizedStringKey\n\n    var body: some View {\n        HStack {\n            Text(title)\n                .font(.system(size: 20, weight: .semibold))\n            Spacer()\n            Button(action: {}) {\n                Image(systemName: ";
"]\n\n    private var isSystemDatabase: Bool {\n        systemDatabases.contains(name)\n    }\n\n    var body: some View {\n        HStack(spacing: 8) {\n            // 复选框\n            Toggle(" = "]\n\n    private var isSystemDatabase: Bool {\n        systemDatabases.contains(name)\n    }\n\n    var body: some View {\n        HStack(spacing: 8) {\n            // Checkbox\n            Toggle(";
"\n        default:\n            return defaultValue\n        }\n    }\n\n    /// Apache 动态路径\n    private static func getApacheDynamicPath(key: String, config: WorkspaceConfig, defaultValue: String) -> String {\n        switch key {\n        case " = "\n        default:\n            return defaultValue\n        }\n    }\n\n    /// Apache dynamic path\n    private static func getApacheDynamicPath(key: String, config: WorkspaceConfig, defaultValue: String) -> String {\n        switch key {\n        case ";
"\n    }\n\n    /// 剩余天数\n    var daysRemaining: Int {\n        let calendar = Calendar.current\n        let now = Date()\n        let components = calendar.dateComponents([.day], from: now, to: validUntil)\n        return components.day ?? 0\n    }\n\n    /// 证书状态\n    var status: CertificateStatus {\n        let days = daysRemaining\n        if days < 0 {\n            return .expired\n        } else if days < 30 {\n            return .expiringSoon\n        } else {\n            return .valid\n        }\n    }\n\n    /// 状态显示文本（包含剩余天数）\n    var statusDisplayText: String {\n        let days = daysRemaining\n        if days < 0 {\n            return " = "\n    }\n\n    /// Days remaining\n    var daysRemaining: Int {\n        let calendar = Calendar.current\n        let now = Date()\n        let components = calendar.dateComponents([.day], from: now, to: validUntil)\n        return components.day ?? 0\n    }\n\n    /// Certificate status\n    var status: CertificateStatus {\n        let days = daysRemaining\n        if days < 0 {\n            return .expired\n        } else if days < 30 {\n            return .expiringSoon\n        } else {\n            return .valid\n        }\n    }\n\n    /// Status display text (including days remaining)\n    var statusDisplayText: String {\n        let days = daysRemaining\n        if days < 0 {\n            return ";
") {\n                    // 提取域名: subject=CN = www.example.com\n                    if let cnRange = line.range(of: " = ") {\n                    // Extract domain name: subject=CN = www.example.com\n                    if let cnRange = line.range(of: ";
"PHP \\(version) 运行正常" = "PHP \\(version) is running normally";
"导入配置失败: \\(error.localizedDescription)" = "Failed to import configuration: \\(error.localizedDescription)";
"#\n\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {\n            return nil\n        }\n\n        let nsString = line as NSString\n        let range = NSRange(location: 0, length: nsString.length)\n\n        guard let match = regex.firstMatch(in: line, options: [], range: range) else {\n            return nil\n        }\n\n        let timestampStr = nsString.substring(with: match.range(at: 1))\n        let levelStr = nsString.substring(with: match.range(at: 2))\n        let message = nsString.substring(with: match.range(at: 3))\n\n        // 提取level（格式：module:level）\n        let levelComponents = levelStr.components(separatedBy: " = "#\n\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {\n            return nil\n        }\n\n        let nsString = line as NSString\n        let range = NSRange(location: 0, length: nsString.length)\n\n        guard let match = regex.firstMatch(in: line, options: [], range: range) else {\n            return nil\n        }\n\n        let timestampStr = nsString.substring(with: match.range(at: 1))\n        let levelStr = nsString.substring(with: match.range(at: 2))\n        let message = nsString.substring(with: match.range(at: 3))\n\n        // Extract level (format: module:level)\n        let levelComponents = levelStr.components(separatedBy: ";
"\\(filteredLogLines.count) 行" = "\\(filteredLogLines.count) lines";
"\n                    }\n                    return parts\n                }\n            }\n        }\n        return nil\n    }\n\n    /// 解析 Apache 配置中的 DocumentRoot\n    private func parseApacheDocumentRoot() -> String? {\n        guard let content = try? String(contentsOfFile: apacheConfigPath, encoding: .utf8) else {\n            return nil\n        }\n\n        let lines = content.components(separatedBy: .newlines)\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed.hasPrefix(" = "\n                    }\n                    return parts\n                }\n            }\n        }\n        return nil\n    }\n\n    /// Parse DocumentRoot from Apache configuration\n    private func parseApacheDocumentRoot() -> String? {\n        guard let content = try? String(contentsOfFile: apacheConfigPath, encoding: .utf8) else {\n            return nil\n        }\n\n        let lines = content.components(separatedBy: .newlines)\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed.hasPrefix(";
", isOn: $isEnabled)\n                .toggleStyle(.checkbox)\n                .labelsHidden()\n                .frame(width: 30)\n                .padding(.leading, 12)\n                .onChange(of: isEnabled) { newValue in\n                    onEnabledChange(newValue)\n                }\n\n            // 配置项（中文名）\n            HStack(spacing: 4) {\n                if item.isUserDefined {\n                    Image(systemName: " = ", isOn: $isEnabled)\n                .toggleStyle(.checkbox)\n                .labelsHidden()\n                .frame(width: 30)\n                .padding(.leading, 12)\n                .onChange(of: isEnabled) { newValue in\n                    onEnabledChange(newValue)\n                }\n\n            // Configuration item (Chinese name)\n            HStack(spacing: 4) {\n                if item.isUserDefined {\n                    Image(systemName: ";
"创建配置文件失败: \\(error.localizedDescription)" = "Failed to create configuration file: \\(error.localizedDescription)";
"\n    @State private var isEnabled: Bool = true\n\n    var body: some View {\n        HStack(spacing: 0) {\n            // 启用/禁用勾选框\n            Toggle(" = "\n    @State private var isEnabled: Bool = true\n\n    var body: some View {\n        HStack(spacing: 0) {\n            // Enable/Disable checkbox\n            Toggle(";
", brewName]\n\n            let pipe = Pipe()\n            process.standardOutput = pipe\n            process.standardError = pipe\n\n            do {\n                try process.run()\n                process.waitUntilExit()\n\n                DispatchQueue.main.async {\n                    if process.terminationStatus == 0 {\n                        // 更新重启消息为成功状态\n                        self.updateLastStatusMessage(" = ", brewName]\n\n            let pipe = Pipe()\n            process.standardOutput = pipe\n            process.standardError = pipe\n\n            do {\n                try process.run()\n                process.waitUntilExit()\n\n                DispatchQueue.main.async {\n                    if process.terminationStatus == 0 {\n                        // Update restart message to success state\n                        self.updateLastStatusMessage(";
"\n\n            // 使用 nginx -t 获取实际加载的配置文件\n            let process = Process()\n            process.executableURL = URL(fileURLWithPath: nginxBin)\n            process.arguments = [" = "\n\n            // Use nginx -t to get the actual loaded configuration file\n            let process = Process()\n            process.executableURL = URL(fileURLWithPath: nginxBin)\n            process.arguments = [";
"确定要终止进程 \\(port.processName) (PID: \\(port.pid)) 吗？\\n\\n这将停止占用端口 \\(port.port) 的服务。" = "Are you sure you want to terminate process \\(port.processName) (PID: \\(port.pid))?\\n\\nThis will stop the service occupying port \\(port.port).";
"\n\n    private init() {\n        loadLogs()\n    }\n\n    // MARK: - 添加错误日志\n\n    func addLog(\n        softwareId: String,\n        softwareName: String,\n        version: String,\n        operation: String,\n        errorMessage: String,\n        errorDetail: String? = nil\n    ) {\n        let entry = ErrorLogEntry(\n            softwareId: softwareId,\n            softwareName: softwareName,\n            version: version,\n            operation: operation,\n            errorMessage: errorMessage,\n            errorDetail: errorDetail\n        )\n\n        // 使用软件ID作为分组键（包含版本号，如 php@7.4）\n        let key = softwareId\n\n        if logsBySoftware[key] == nil {\n            logsBySoftware[key] = []\n        }\n\n        // 插入到最前面\n        logsBySoftware[key]?.insert(entry, at: 0)\n\n        // 限制数量\n        if let count = logsBySoftware[key]?.count, count > maxLogsPerSoftware {\n            logsBySoftware[key] = Array(logsBySoftware[key]!.prefix(maxLogsPerSoftware))\n        }\n\n        saveLogs()\n    }\n\n    // MARK: - 获取错误日志\n\n    /// 获取指定软件的错误日志\n    func getLogs(for softwareId: String, limit: Int = 20) -> [ErrorLogEntry] {\n        guard let logs = logsBySoftware[softwareId] else { return [] }\n        return Array(logs.prefix(limit))\n    }\n\n    /// 获取指定软件的所有错误日志\n    func getAllLogs(for softwareId: String) -> [ErrorLogEntry] {\n        return logsBySoftware[softwareId] ?? []\n    }\n\n    /// 检查软件是否有错误日志\n    func hasLogs(for softwareId: String) -> Bool {\n        return !(logsBySoftware[softwareId]?.isEmpty ?? true)\n    }\n\n    /// 获取软件的错误日志数量\n    func logCount(for softwareId: String) -> Int {\n        return logsBySoftware[softwareId]?.count ?? 0\n    }\n\n    // MARK: - 按软件名称获取日志（所有版本）\n\n    /// 检查某个软件名称下是否有任何版本的错误日志\n    func hasLogsForSoftware(named softwareName: String) -> Bool {\n        for (_, logs) in logsBySoftware {\n            if logs.first(where: { $0.softwareName == softwareName }) != nil {\n                return true\n            }\n        }\n        return false\n    }\n\n    /// 获取某个软件名称下所有版本的错误日志\n    func getLogsForSoftware(named softwareName: String, limit: Int = 20) -> [ErrorLogEntry] {\n        var allLogs: [ErrorLogEntry] = []\n        for (_, logs) in logsBySoftware {\n            allLogs.append(contentsOf: logs.filter { $0.softwareName == softwareName })\n        }\n        // 按时间排序\n        allLogs.sort { $0.timestamp > $1.timestamp }\n        return Array(allLogs.prefix(limit))\n    }\n\n    /// 获取某个软件名称下所有版本的全部错误日志\n    func getAllLogsForSoftware(named softwareName: String) -> [ErrorLogEntry] {\n        var allLogs: [ErrorLogEntry] = []\n        for (_, logs) in logsBySoftware {\n            allLogs.append(contentsOf: logs.filter { $0.softwareName == softwareName })\n        }\n        allLogs.sort { $0.timestamp > $1.timestamp }\n        return allLogs\n    }\n\n    /// 获取某个软件名称下所有版本的错误日志数量\n    func logCountForSoftware(named softwareName: String) -> Int {\n        var count = 0\n        for (_, logs) in logsBySoftware {\n            count += logs.filter { $0.softwareName == softwareName }.count\n        }\n        return count\n    }\n\n    /// 清除某个软件名称下所有版本的日志\n    func clearLogsForSoftware(named softwareName: String) {\n        for key in logsBySoftware.keys {\n            logsBySoftware[key]?.removeAll { $0.softwareName == softwareName }\n            if logsBySoftware[key]?.isEmpty == true {\n                logsBySoftware.removeValue(forKey: key)\n            }\n        }\n        saveLogs()\n    }\n\n    // MARK: - 清除日志\n\n    /// 清除指定软件的所有日志\n    func clearLogs(for softwareId: String) {\n        logsBySoftware.removeValue(forKey: softwareId)\n        saveLogs()\n    }\n\n    /// 清除所有日志\n    func clearAllLogs() {\n        logsBySoftware.removeAll()\n        saveLogs()\n    }\n\n    // MARK: - 持久化\n\n    private func saveLogs() {\n        do {\n            let data = try JSONEncoder().encode(logsBySoftware)\n            UserDefaults.standard.set(data, forKey: userDefaultsKey)\n        } catch {\n            print(" = "\n\n    private init() {\n        loadLogs()\n    }\n\n    // MARK: - Add error log\n\n    func addLog(\n        softwareId: String,\n        softwareName: String,\n        version: String,\n        operation: String,\n        errorMessage: String,\n        errorDetail: String? = nil\n    ) {\n        let entry = ErrorLogEntry(\n            softwareId: softwareId,\n            softwareName: softwareName,\n            version: version,\n            operation: operation,\n            errorMessage: errorMessage,\n            errorDetail: errorDetail\n        )\n\n        // Use software ID as grouping key (including version, e.g., php@7.4)\n        let key = softwareId\n\n        if logsBySoftware[key] == nil {\n            logsBySoftware[key] = []\n        }\n\n        // Insert at the beginning\n        logsBySoftware[key]?.insert(entry, at: 0)\n\n        // Limit count\n        if let count = logsBySoftware[key]?.count, count > maxLogsPerSoftware {\n            logsBySoftware[key] = Array(logsBySoftware[key]!.prefix(maxLogsPerSoftware))\n        }\n\n        saveLogs()\n    }\n\n    // MARK: - Get error logs\n\n    /// Get error logs for a specified software\n    func getLogs(for softwareId: String, limit: Int = 20) -> [ErrorLogEntry] {\n        guard let logs = logsBySoftware[softwareId] else { return [] }\n        return Array(logs.prefix(limit))\n    }\n\n    /// Get all error logs for a specified software\n    func getAllLogs(for softwareId: String) -> [ErrorLogEntry] {\n        return logsBySoftware[softwareId] ?? []\n    }\n\n    /// Check whether a software has error logs\n    func hasLogs(for softwareId: String) -> Bool {\n        return !(logsBySoftware[softwareId]?.isEmpty ?? true)\n    }\n\n    /// Get the number of error logs for a software\n    func logCount(for softwareId: String) -> Int {\n        return logsBySoftware[softwareId]?.count ?? 0\n    }\n\n    // MARK: - Get logs by software name (all versions)\n\n    /// Check whether any version of a given software name has error logs\n    func hasLogsForSoftware(named softwareName: String) -> Bool {\n        for (_, logs) in logsBySoftware {\n            if logs.first(where: { $0.softwareName == softwareName }) != nil {\n                return true\n            }\n        }\n        return false\n    }\n\n    /// Get all error logs for a given software name across all versions\n    func getLogsForSoftware(named softwareName: String, limit: Int = 20) -> [ErrorLogEntry] {\n        var allLogs: [ErrorLogEntry] = []\n        for (_, logs) in logsBySoftware {\n            allLogs.append(contentsOf: logs.filter { $0.softwareName == softwareName })\n        }\n        // Sort by time\n        allLogs.sort { $0.timestamp > $1.timestamp }\n        return Array(allLogs.prefix(limit))\n    }\n\n    /// Get all error logs for a given software name across all versions\n    func getAllLogsForSoftware(named softwareName: String) -> [ErrorLogEntry] {\n        var allLogs: [ErrorLogEntry] = []\n        for (_, logs) in logsBySoftware {\n            allLogs.append(contentsOf: logs.filter { $0.softwareName == softwareName })\n        }\n        allLogs.sort { $0.timestamp > $1.timestamp }\n        return allLogs\n    }\n\n    /// Get total number of error logs for a given software name across all versions\n    func logCountForSoftware(named softwareName: String) -> Int {\n        var count = 0\n        for (_, logs) in logsBySoftware {\n            count += logs.filter { $0.softwareName == softwareName }.count\n        }\n        return count\n    }\n\n    /// Clear logs for all versions of a given software name\n    func clearLogsForSoftware(named softwareName: String) {\n        for key in logsBySoftware.keys {\n            logsBySoftware[key]?.removeAll { $0.softwareName == softwareName }\n            if logsBySoftware[key]?.isEmpty == true {\n                logsBySoftware.removeValue(forKey: key)\n            }\n        }\n        saveLogs()\n    }\n\n    // MARK: - Clear logs\n\n    /// Clear all logs for a specified software\n    func clearLogs(for softwareId: String) {\n        logsBySoftware.removeValue(forKey: softwareId)\n        saveLogs()\n    }\n\n    /// Clear all logs\n    func clearAllLogs() {\n        logsBySoftware.removeAll()\n        saveLogs()\n    }\n\n    // MARK: - Persistence\n\n    private func saveLogs() {\n        do {\n            let data = try JSONEncoder().encode(logsBySoftware)\n            UserDefaults.standard.set(data, forKey: userDefaultsKey)\n        } catch {\n            print(";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n            .padding(.leading, 12)\n        }\n    }\n\n    // 表单内容\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // 通用名称\n                FormField(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n            .padding(.leading, 12)\n        }\n    }\n\n    // Form content\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // Common Name\n                FormField(";
"[SiteManager] 添加 hosts 条目失败: \\(domain) - \\(error)" = "[SiteManager] Error al agregar la entrada de hosts: \\(domain) - \\(error)";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n            .padding(.leading, 12)\n        }\n        .padding(20)\n        .frame(height: 70)\n    }\n\n    // 标题栏\n    private var titleBar: some View {\n        HStack {\n            VStack(alignment: .leading, spacing: 4) {\n                Text(LocalizedStringKey(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n            .padding(.leading, 12)\n        }\n        .padding(20)\n        .frame(height: 70)\n    }\n\n    // Barra de título\n    private var titleBar: some View {\n        HStack {\n            VStack(alignment: .leading, spacing: 4) {\n                Text(LocalizedStringKey(";
"\n        # phpMyAdmin Nginx 配置\n        # 由 JihePHPEnv 自动生成\n        # 生成时间: \\(ISO8601DateFormatter().string(from: Date()))\n\n        location /phpmyadmin {\n            alias \\(phpMyAdminPath);\n            index index.php;\n\n            # IP 访问限制\n            \\(allowRules)\n            deny all;\n\n            location ~ \\\\.php$ {\n                include fastcgi_params;\n                fastcgi_pass 127.0.0.1:9000;\n                fastcgi_index index.php;\n                fastcgi_param SCRIPT_FILENAME $request_filename;\n            }\n\n            # 禁止访问敏感文件\n            location ~ /\\\\. {\n                deny all;\n            }\n        }\n        " = "\n        # Configuración de Nginx para phpMyAdmin\n        # Generada automáticamente por JihePHPEnv\n        # Fecha de generación: \\(ISO8601DateFormatter().string(from: Date()))\n\n        location /phpmyadmin {\n            alias \\(phpMyAdminPath);\n            index index.php;\n\n            # Restricción de acceso por IP\n            \\(allowRules)\n            deny all;\n\n            location ~ \\\\.php$ {\n                include fastcgi_params;\n                fastcgi_pass 127.0.0.1:9000;\n                fastcgi_index index.php;\n                fastcgi_param SCRIPT_FILENAME $request_filename;\n            }\n\n            # Prohibir el acceso a archivos sensibles\n            location ~ /\\\\. {\n                deny all;\n            }\n        }\n        ";
" {\n                // V2 上一页按钮\n                MacButton(icon: " = " {\n                // Botón «Página anterior» para V2\n                MacButton(icon: ";
")\n                    .font(.caption)\n                    .foregroundColor(.secondary)\n                Text(certificate.validUntil)\n                    .font(.caption)\n            }\n\n            // 操作按钮\n            Menu {\n                Button(" = ")\n                    .font(.caption)\n                    .foregroundColor(.secondary)\n                Text(certificate.validUntil)\n                    .font(.caption)\n            }\n\n            // Botones de acción\n            Menu {\n                Button(";
")\n                        .font(.caption)\n                }\n            }\n            .buttonStyle(.bordered)\n            .disabled(selectedDatabases.isEmpty)\n\n            // 隐藏系统数据库复选框\n            Toggle(isOn: $hideSystemDatabases) {\n                Text(" = ")\n                        .font(.caption)\n                }\n            }\n            .buttonStyle(.bordered)\n            .disabled(selectedDatabases.isEmpty)\n\n            // Casilla de verificación para ocultar bases de datos del sistema\n            Toggle(isOn: $hideSystemDatabases) {\n                Text(";
")\n                    .font(.system(size: 10, weight: .semibold))\n                    .foregroundColor(.secondary)\n\n                Text(log.errorMessage)\n                    .font(.system(size: 11))\n                    .foregroundColor(.primary)\n                    .textSelection(.enabled)\n                    .padding(8)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n                    .background(Color.white.opacity(AppTokens.Opacity.disabled))\n                    .cornerRadius(4)\n            }\n\n            // 详细信息（如果有）\n            if let detail = log.errorDetail, !detail.isEmpty {\n                VStack(alignment: .leading, spacing: 4) {\n                    Text(" = ")\n                    .font(.system(size: 10, weight: .semibold))\n                    .foregroundColor(.secondary)\n\n                Text(log.errorMessage)\n                    .font(.system(size: 11))\n                    .foregroundColor(.primary)\n                    .textSelection(.enabled)\n                    .padding(8)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n                    .background(Color.white.opacity(AppTokens.Opacity.disabled))\n                    .cornerRadius(4)\n            }\n\n            // Información detallada (si existe)\n            if let detail = log.errorDetail, !detail.isEmpty {\n                VStack(alignment: .leading, spacing: 4) {\n                    Text(";
"✅ 已为 Apache 配置生成 Nginx 代理: \\(siteName)" = "✅ Se ha generado un proxy de Nginx para Apache: \\(siteName)";
"\n        default: return service.name.lowercased()\n        }\n    }\n\n    // 在外部编辑器中打开\n    private func openInEditor() {\n        let path = currentConfigPath.isEmpty ? getConfigPathWithWorkspace() : currentConfigPath\n        guard !path.isEmpty else { return }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = "\n        default: return service.name.lowercased()\n        }\n    }\n\n    // Abrir en editor externo\n    private func openInEditor() {\n        let path = currentConfigPath.isEmpty ? getConfigPathWithWorkspace() : currentConfigPath\n        guard !path.isEmpty else { return }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: ";
"\n        var inString = false\n        var isEscape = false\n        \n        var errors: [NSRange] = []\n        var stack: [(Character, Int)] = [] // 记录左括号和它在 inputCode 中的位置\n        \n        let chars = Array(inputCode)\n        for (i, char) in chars.enumerated() {\n            if inString {\n                formatted.append(char)\n                if isEscape {\n                    isEscape = false\n                } else if char == " = "\n        var inString = false\n        var isEscape = false\n        \n        var errors: [NSRange] = []\n        var stack: [(Character, Int)] = [] // Registra los paréntesis izquierdos y su posición en inputCode\n        \n        let chars = Array(inputCode)\n        for (i, char) in chars.enumerated() {\n            if inString {\n                formatted.append(char)\n                if isEscape {\n                    isEscape = false\n                } else if char == ";
"安装独立的 \\(serverType.displayName) 实例，使用其他可用端口" = "Instalar una instancia independiente de \\(serverType.displayName) utilizando otros puertos disponibles";
", selection: $selectedVersion) {\n                    ForEach(installedVersions, id: \\.self) { version in\n                        Text(version).tag(version)\n                    }\n                }\n                .frame(width: 80)\n            }\n\n            Divider().frame(height: 20)\n\n            // 显示模式切换\n            Picker(" = ", selection: $selectedVersion) {\n                    ForEach(installedVersions, id: \\.self) { version in\n                        Text(version).tag(version)\n                    }\n                }\n                .frame(width: 80)\n            }\n\n            Divider().frame(height: 20)\n\n            // Cambio de modo de visualización\n            Picker(";
", query])\n                process.arguments = arguments\n\n                let outputPipe = Pipe()\n                process.standardOutput = outputPipe\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    if process.terminationStatus == 0 {\n                        let data = outputPipe.fileHandleForReading.readDataToEndOfFile()\n                        if let output = String(data: data, encoding: .utf8)?\n                            .trimmingCharacters(in: .whitespacesAndNewlines),\n                           !output.isEmpty {\n                            continuation.resume(returning: output)\n                            return\n                        }\n                    }\n                    // 如果没找到，返回数据库名作为默认用户名\n                    continuation.resume(returning: self.name)\n                } catch {\n                    continuation.resume(returning: " = ", query])\n                process.arguments = arguments\n\n                let outputPipe = Pipe()\n                process.standardOutput = outputPipe\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    if process.terminationStatus == 0 {\n                        let data = outputPipe.fileHandleForReading.readDataToEndOfFile()\n                        if let output = String(data: data, encoding: .utf8)?\n                            .trimmingCharacters(in: .whitespacesAndNewlines),\n                           !output.isEmpty {\n                            continuation.resume(returning: output)\n                            return\n                        }\n                    }\n                    // Si no se encuentra, devolver el nombre de la base de datos como nombre de usuario predeterminado\n                    continuation.resume(returning: self.name)\n                } catch {\n                    continuation.resume(returning: ";
"\n    }\n\n    // 生成随机用户名（随机字母）\n    private func generateRandomUsername() -> String {\n        let letters = " = "\n    }\n\n    // Generar un nombre de usuario aleatorio (letras aleatorias)\n    private func generateRandomUsername() -> String {\n        let letters = ";
")\n    }\n\n    /// 最大备份数量（每个服务/站点）\n    private let maxBackupsPerService = 5\n\n    private init() {}\n\n    // MARK: - 备份操作\n\n    /// 创建配置文件备份\n    func createBackup(serviceName: String, configPath: String, note: String? = nil) throws -> ConfigBackup {\n        let fileManager = FileManager.default\n\n        // 检查源文件是否存在\n        guard fileManager.fileExists(atPath: configPath) else {\n            throw BackupError.sourceFileNotFound\n        }\n\n        // 获取文件大小\n        let attributes = try fileManager.attributesOfItem(atPath: configPath)\n        let fileSize = attributes[.size] as? Int64 ?? 0\n\n        // 生成备份文件名：服务名_时间戳.bak\n        let timestamp = ISO8601DateFormatter().string(from: Date())\n            .replacingOccurrences(of: " = ")\n    }\n\n    /// Número máximo de copias de seguridad (por servicio o sitio)\n    private let maxBackupsPerService = 5\n\n    private init() {}\n\n    // MARK: - Operaciones de copia de seguridad\n\n    /// Crear una copia de seguridad del archivo de configuración\n    func createBackup(serviceName: String, configPath: String, note: String? = nil) throws -> ConfigBackup {\n        let fileManager = FileManager.default\n\n        // Verificar si el archivo de origen existe\n        guard fileManager.fileExists(atPath: configPath) else {\n            throw BackupError.sourceFileNotFound\n        }\n\n        // Obtener el tamaño del archivo\n        let attributes = try fileManager.attributesOfItem(atPath: configPath)\n        let fileSize = attributes[.size] as? Int64 ?? 0\n\n        // Generar el nombre del archivo de copia de seguridad: nombreServicio_marcaDeTiempo.bak\n        let timestamp = ISO8601DateFormatter().string(from: Date())\n            .replacingOccurrences(of: ";
").map { String($0) }\n            guard parts.count >= 4 else { return nil }\n\n            let name = parts[0]\n            let charset = parts[1]\n            let tableCount = Int(parts[2]) ?? 0\n            let sizeBytes = Int64(parts[3]) ?? 0\n\n            return MySQLDatabaseInfo(\n                name: name,\n                charset: charset,\n                tableCount: tableCount,\n                sizeBytes: sizeBytes\n            )\n        }\n    }\n\n    /// 创建数据库\n    func createDatabase(name: String) async -> Bool {\n        let sql = " = ").map { String($0) }\n            guard parts.count >= 4 else { return nil }\n\n            let name = parts[0]\n            let charset = parts[1]\n            let tableCount = Int(parts[2]) ?? 0\n            let sizeBytes = Int64(parts[3]) ?? 0\n\n            return MySQLDatabaseInfo(\n                name: name,\n                charset: charset,\n                tableCount: tableCount,\n                sizeBytes: sizeBytes\n            )\n        }\n    }\n\n    /// Crear una base de datos\n    func createDatabase(name: String) async -> Bool {\n        let sql = ";
")\n        let majorMinor = extractMajorMinorVersion(version)\n\n        // 检查版本是否在支持列表中\n        return versions.contains { supportedVer in\n            majorMinor.hasPrefix(supportedVer) || supportedVer == majorMinor\n        }\n    }\n\n    /// 提取主次版本号\n    private func extractMajorMinorVersion(_ version: String) -> String {\n        // 处理带下划线的版本号 (如 " = ")\n        let majorMinor = extractMajorMinorVersion(version)\n\n        // Verificar si la versión está en la lista de versiones compatibles\n        return versions.contains { supportedVer in\n            majorMinor.hasPrefix(supportedVer) || supportedVer == majorMinor\n        }\n    }\n\n    /// Extraer la versión principal y secundaria\n    private func extractMajorMinorVersion(_ version: String) -> String {\n        // Manejar versiones con guiones bajos (por ejemplo, ";
")\n                        .font(.system(size: 10))\n                }\n            }\n            .menuStyle(.borderlessButton)\n            .disabled(true)\n            .foregroundColor(.secondary)\n\n            // 隐藏系统数据库\n            Toggle(isOn: $hideSystemDatabases) {\n                Text(" = ")\n                        .font(.system(size: 10))\n                }\n            }\n            .menuStyle(.borderlessButton)\n            .disabled(true)\n            .foregroundColor(.secondary)\n\n            // Ocultar bases de datos del sistema\n            Toggle(isOn: $hideSystemDatabases) {\n                Text(";
"✅ 已备份旧配置: \\(configFilePath)" = "✅ Copia de seguridad creada de la configuración anterior: \\(configFilePath)";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 表单内容\n    private var formContent: some View {\n        ScrollView {\n            VStack(spacing: 20) {\n                // 数据库名 + 字符集\n                databaseNameField\n\n                // 用户名\n                usernameField\n\n                // 密码\n                passwordField\n\n                // 访问权限\n                accessHostField\n\n                // 添加至\n                targetServerField\n\n                // 错误信息\n                if let error = errorMessage {\n                    errorView(error)\n                }\n            }\n            .padding(24)\n        }\n    }\n\n    // 数据库名字段\n    private var databaseNameField: some View {\n        HStack(alignment: .center, spacing: 12) {\n            Text(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // Contenido del formulario\n    private var formContent: some View {\n        ScrollView {\n            VStack(spacing: 20) {\n                // Nombre de la base de datos + conjunto de caracteres\n                databaseNameField\n\n                // Nombre de usuario\n                usernameField\n\n                // Contraseña\n                passwordField\n\n                // Permiso de acceso\n                accessHostField\n\n                // Agregar a\n                targetServerField\n\n                // Mensaje de error\n                if let error = errorMessage {\n                    errorView(error)\n                }\n            }\n            .padding(24)\n        }\n    }\n\n    // Campo del nombre de la base de datos\n    private var databaseNameField: some View {\n        HStack(alignment: .center, spacing: 12) {\n            Text(";
") {\n            // Nginx 配置文件信息\n            let nginxBin = " = ") {\n            // Información del archivo de configuración de Nginx\n            let nginxBin = ";
")\n                .font(.system(size: 20))\n                .foregroundColor(.blue)\n                .frame(width: 32)\n\n            // 信息\n            VStack(alignment: .leading, spacing: 4) {\n                Text(dateFormatter.string(from: backup.createdAt))\n                    .font(.system(size: 13, weight: .medium))\n\n                HStack(spacing: 8) {\n                    if let note = backup.note {\n                        Text(note)\n                            .font(.caption)\n                            .foregroundColor(.secondary)\n                    }\n                    Text(formatFileSize(backup.fileSize))\n                        .font(.caption)\n                        .foregroundColor(.secondary)\n                }\n            }\n\n            Spacer()\n\n            // 操作按钮\n            if isHovering {\n                HStack(spacing: 8) {\n                    Button(" = ")\n                .font(.system(size: 20))\n                .foregroundColor(.blue)\n                .frame(width: 32)\n\n            // Información\n            VStack(alignment: .leading, spacing: 4) {\n                Text(dateFormatter.string(from: backup.createdAt))\n                    .font(.system(size: 13, weight: .medium))\n\n                HStack(spacing: 8) {\n                    if let note = backup.note {\n                        Text(note)\n                            .font(.caption)\n                            .foregroundColor(.secondary)\n                    }\n                    Text(formatFileSize(backup.fileSize))\n                        .font(.caption)\n                        .foregroundColor(.secondary)\n                }\n            }\n\n            Spacer()\n\n            // Botones de acción\n            if isHovering {\n                HStack(spacing: 8) {\n                    Button(";
")\n        return result.isEmpty ? nil : result\n    }\n\n    /// 获取配置文件信息（用于调试启动失败问题）\n    private func getConfigFileInfo(software: SoftwareVersion) -> String? {\n        let name = software.name.lowercased()\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n\n        var info: [String] = []\n\n        // 检查工作空间状态\n        if let config = WorkspaceManager.shared.config, config.isInitialized {\n            info.append(" = ")\n        return result.isEmpty ? nil : result\n    }\n\n    /// Obtener información del archivo de configuración (para depurar fallos al iniciar)\n    private func getConfigFileInfo(software: SoftwareVersion) -> String? {\n        let name = software.name.lowercased()\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n\n        var info: [String] = []\n\n        // Verificar el estado del espacio de trabajo\n        if let config = WorkspaceManager.shared.config, config.isInitialized {\n            info.append(";
")\n                        .font(.system(size: 10, weight: .semibold))\n                        .foregroundColor(.secondary)\n\n                    ScrollView(.vertical, showsIndicators: true) {\n                        Text(detail)\n                            .font(.system(size: 10, design: .monospaced))\n                            .foregroundColor(.secondary)\n                            .textSelection(.enabled)\n                            .frame(maxWidth: .infinity, alignment: .leading)\n                            .padding(8)\n                    }\n                    .frame(maxHeight: 150)\n                    .background(Color.black.opacity(AppTokens.Opacity.ultraLight))\n                    .cornerRadius(4)\n                }\n            }\n\n            // 操作信息\n            HStack(spacing: 16) {\n                Label {\n                    Text(" = ")\n                        .font(.system(size: 10, weight: .semibold))\n                        .foregroundColor(.secondary)\n\n                    ScrollView(.vertical, showsIndicators: true) {\n                        Text(detail)\n                            .font(.system(size: 10, design: .monospaced))\n                            .foregroundColor(.secondary)\n                            .textSelection(.enabled)\n                            .frame(maxWidth: .infinity, alignment: .leading)\n                            .padding(8)\n                    }\n                    .frame(maxHeight: 150)\n                    .background(Color.black.opacity(AppTokens.Opacity.ultraLight))\n                    .cornerRadius(4)\n                }\n            }\n\n            // Información de la operación\n            HStack(spacing: 16) {\n                Label {\n                    Text(";
") {\n                    onEdit()\n                }\n                .buttonStyle(.bordered)\n                .controlSize(.small)\n            }\n            \n            // 权限按钮（仅本地数据库显示）\n            if database.type == .local {\n                Button(" = ") {\n                    onEdit()\n                }\n                .buttonStyle(.bordered)\n                .controlSize(.small)\n            }\n            \n            // Botón de permisos (solo para bases de datos locales)\n            if database.type == .local {\n                Button(";
"  // 密码检测状态\n    @State private var detectedMySQLVersion: String = " = "  // Estado de la detección de contraseña\n    @State private var detectedMySQLVersion: String = ";
")\n                                        .font(.system(size: 10))\n                                        .foregroundColor(.blue)\n                                    Text(domain)\n                                        .font(.system(size: 12))\n                                        .foregroundColor(.primary)\n                                        .frame(maxWidth: .infinity, alignment: .leading)\n                                }\n                                .padding(.horizontal, 8)\n                                .padding(.vertical, 6)\n                                .background(domain == site.primaryDomain ? Color.blue.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n                            }\n                            .buttonStyle(.plain)\n                        }\n                    }\n                    .frame(minWidth: 150)\n            .glass(.input)\n                    .shadow(color: .black.opacity(AppTokens.Opacity.hover), radius: 8, x: 0, y: 4)\n                    .overlay(\n                        RoundedRectangle(cornerRadius: 4)\n                            .stroke(AppColors.border, lineWidth: 1)\n                    )\n                    .offset(y: 20)\n                    .zIndex(10000)\n                    .transition(.opacity.combined(with: .scale(scale: 0.95)))\n                }\n            }\n\n            // 状态\n            statusView\n                .frame(width: 60, alignment: .leading)\n\n            // 备份\n            backupView\n                .frame(width: 50, alignment: .leading)\n\n            // 根目录\n            HStack(spacing: 4) {\n                Text(site.documentRoot)\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n                    .lineLimit(1)\n                Button(action: {\n                    openSiteDirectory()\n                }) {\n                    Image(systemName: " = ")\n                                        .font(.system(size: 10))\n                                        .foregroundColor(.blue)\n                                    Text(domain)\n                                        .font(.system(size: 12))\n                                        .foregroundColor(.primary)\n                                        .frame(maxWidth: .infinity, alignment: .leading)\n                                }\n                                .padding(.horizontal, 8)\n                                .padding(.vertical, 6)\n                                .background(domain == site.primaryDomain ? Color.blue.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n                            }\n                            .buttonStyle(.plain)\n                        }\n                    }\n                    .frame(minWidth: 150)\n            .glass(.input)\n                    .shadow(color: .black.opacity(AppTokens.Opacity.hover), radius: 8, x: 0, y: 4)\n                    .overlay(\n                        RoundedRectangle(cornerRadius: 4)\n                            .stroke(AppColors.border, lineWidth: 1)\n                    )\n                    .offset(y: 20)\n                    .zIndex(10000)\n                    .transition(.opacity.combined(with: .scale(scale: 0.95)))\n                }\n            }\n\n            // Estado\n            statusView\n                .frame(width: 60, alignment: .leading)\n\n            // Copia de seguridad\n            backupView\n                .frame(width: 50, alignment: .leading)\n\n            // Directorio raíz\n            HStack(spacing: 4) {\n                Text(site.documentRoot)\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n                    .lineLimit(1)\n                Button(action: {\n                    openSiteDirectory()\n                }) {\n                    Image(systemName: ";
"]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            if process.terminationStatus == 0 {\n                let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                if let path = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),\n                   !path.isEmpty {\n                    return path\n                }\n            }\n        } catch {\n            return nil\n        }\n\n        return nil\n    }\n\n    // MARK: - 运行 GoAccess 命令\n    nonisolated private func runGoAccess(logPath: String) throws -> String {\n        guard let goAccessPath = getGoAccessPath() else {\n            throw GoAccessError.notInstalled\n        }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: goAccessPath)\n\n        // GoAccess 参数：\n        // -f: 日志文件路径\n        // -o: 输出格式 (json)\n        // --log-format=COMBINED: 使用 Combined 日志格式\n        process.arguments = [\n            " = "]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            if process.terminationStatus == 0 {\n                let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                if let path = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),\n                   !path.isEmpty {\n                    return path\n                }\n            }\n        } catch {\n            return nil\n        }\n\n        return nil\n    }\n\n    // MARK: - Ejecutar el comando GoAccess\n    nonisolated private func runGoAccess(logPath: String) throws -> String {\n        guard let goAccessPath = getGoAccessPath() else {\n            throw GoAccessError.notInstalled\n        }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: goAccessPath)\n\n        // Parámetros de GoAccess:\n        // -f: ruta del archivo de registro\n        // -o: formato de salida (json)\n        // --log-format=COMBINED: usar el formato de registro Combined\n        process.arguments = [\n            ";
"\n                }\n\n                // 使用较低的优先级（140+）\n                additionalVersions.append((displayName, packageName, 6379, 140 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// 预加载已安装包列表缓存（使用 brew list --versions）\n    private nonisolated func preloadInstalledPackagesCache() {\n        guard AppState.cachedInstalledPackages == nil else { return }\n        guard let brewPath = resolveBrewPath() else { return }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [" = "\n                }\n\n                // Usar una prioridad más baja (140+)\n                additionalVersions.append((displayName, packageName, 6379, 140 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// Precargar la caché de la lista de paquetes instalados (usando \"brew list --versions\")\n    private nonisolated func preloadInstalledPackagesCache() {\n        guard AppState.cachedInstalledPackages == nil else { return }\n        guard let brewPath = resolveBrewPath() else { return }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [";
") && service.installed {\n                servers.append(.apache)\n            }\n        }\n        return servers\n    }\n\n    // 检查Web服务器是否正在运行\n    private func isWebServerRunning(_ serverType: WebServerType) -> Bool {\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if serverType == .nginx && name == " = ") && service.installed {\n                servers.append(.apache)\n            }\n        }\n        return servers\n    }\n\n    // Verificar si el servidor web está en ejecución\n    private func isWebServerRunning(_ serverType: WebServerType) -> Bool {\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if serverType == .nginx && name == ";
"))\n                            .font(.caption)\n                            .foregroundColor(.secondary)\n                    }\n                }\n\n                ZStack {\n                    TextEditor(text: .constant(convertedRules))\n                        .font(.system(.body, design: .monospaced))\n                        .macTextEditor()\n                        \n                    if convertedRules.isEmpty {\n                        // 空状态提示\n                        VStack(spacing: 12) {\n                            Image(systemName: " = "))\n                            .font(.caption)\n                            .foregroundColor(.secondary)\n                    }\n                }\n\n                ZStack {\n                    TextEditor(text: .constant(convertedRules))\n                        .font(.system(.body, design: .monospaced))\n                        .macTextEditor()\n                        \n                    if convertedRules.isEmpty {\n                        // Indicador de estado vacío\n                        VStack(spacing: 12) {\n                            Image(systemName: ";
") {\n                    comment = String(parts[2].dropFirst()).trimmingCharacters(in: .whitespaces)\n                }\n                \n                entries.append(HostEntry(\n                    ip: ip,\n                    hostname: hostname,\n                    comment: comment,\n                    isEnabled: true,\n                    isManaged: inManagedSection\n                ))\n            }\n        }\n        \n        return entries\n    }\n\n    // 添加 hosts 条目\n    func addEntry(_ entry: HostEntry) async throws {\n        // 检查是否已存在相同的条目\n        if entries.contains(where: { $0.hostname == entry.hostname && $0.ip == entry.ip }) {\n            // 已存在，不重复添加\n            return\n        }\n\n        // 检查冲突（相同域名但不同IP）\n        if entries.contains(where: { $0.hostname == entry.hostname && $0.ip != entry.ip }) {\n            throw HostsError.conflict(" = ") {\n                    comment = String(parts[2].dropFirst()).trimmingCharacters(in: .whitespaces)\n                }\n                \n                entries.append(HostEntry(\n                    ip: ip,\n                    hostname: hostname,\n                    comment: comment,\n                    isEnabled: true,\n                    isManaged: inManagedSection\n                ))\n            }\n        }\n        \n        return entries\n    }\n\n    // Agregar una entrada a hosts\n    func addEntry(_ entry: HostEntry) async throws {\n        // Verificar si ya existe una entrada idéntica\n        if entries.contains(where: { $0.hostname == entry.hostname && $0.ip == entry.ip }) {\n            // Ya existe; no se agrega nuevamente\n            return\n        }\n\n        // Verificar conflictos (mismo nombre de dominio pero distinta IP)\n        if entries.contains(where: { $0.hostname == entry.hostname && $0.ip != entry.ip }) {\n            throw HostsError.conflict(";
"]\n                stopProcess.standardOutput = Pipe()\n                stopProcess.standardError = Pipe()\n\n                do {\n                    try stopProcess.run()\n                    stopProcess.waitUntilExit()\n                } catch {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                // 等待服务完全停止\n                Thread.sleep(forTimeInterval: 2)\n\n                // 步骤2: 以安全模式启动 MySQL（跳过权限验证）\n                let mysqldSafePaths = [\n                    " = "]\n                stopProcess.standardOutput = Pipe()\n                stopProcess.standardError = Pipe()\n\n                do {\n                    try stopProcess.run()\n                    stopProcess.waitUntilExit()\n                } catch {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                // Esperar a que el servicio se detenga completamente\n                Thread.sleep(forTimeInterval: 2)\n\n                // Paso 2: Iniciar MySQL en modo seguro (omitir verificación de permisos)\n                let mysqldSafePaths = [\n                    ";
"\n            if FileManager.default.fileExists(atPath: brewPath) {\n                try updateNginxListenPort(at: brewPath, port: port)\n                modified = true\n            }\n\n            // 修改 servers 目录中的 default.conf（被主配置包含的文件）\n            let defaultConfPath = " = "\n            if FileManager.default.fileExists(atPath: brewPath) {\n                try updateNginxListenPort(at: brewPath, port: port)\n                modified = true\n            }\n\n            // Modificar default.conf en el directorio servers (archivo incluido por la configuración principal)\n            let defaultConfPath = ";
"\n            )\n            return\n        }\n\n        // 依次重启所有Web服务\n        for service in webServices {\n            restartService(service)\n        }\n\n        // 显示成功提示\n        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {\n            let alert = NSAlert()\n            alert.messageText = " = "\n            )\n            return\n        }\n\n        // Reiniciar secuencialmente todos los servicios web\n        for service in webServices {\n            restartService(service)\n        }\n\n        // Mostrar mensaje de éxito\n        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {\n            let alert = NSAlert()\n            alert.messageText = ";
"错误: \\(error.localizedDescription)" = "Error: \\(error.localizedDescription)";
" {\n            return .crashExit\n        }\n\n        // 5. 检查错误日志是否有最近的错误\n        if hasRecentError {\n            return .crashExit\n        }\n\n        // 默认认为是手动停止\n        return .manual\n    }\n\n    /// 检查端口是否被其他进程占用（nonisolated）\n    private nonisolated static func isPortOccupiedByOther(port: Int) -> Bool {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = " {\n            return .crashExit\n        }\n\n        // 5. Verificar si hay errores recientes en los registros de errores\n        if hasRecentError {\n            return .crashExit\n        }\n\n        // Por defecto, se considera una detención manual\n        return .manual\n    }\n\n    /// Verificar si un puerto está ocupado por otro proceso (nonisolated)\n    private nonisolated static func isPortOccupiedByOther(port: Int) -> Bool {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: ";
"\n\n        // 确保目录存在\n        let fileManager = FileManager.default\n        try fileManager.createDirectory(atPath: apacheVhostsDir, withIntermediateDirectories: true)\n\n        let publicRoot = sitePublicRoot(documentRoot: documentRoot, publicDirectory: site.publicDirectory)\n        let phpPort = phpFpmPort(for: site.phpVersion)\n\n        // 日志目录路径\n        let logsDir = (documentRoot as NSString).appendingPathComponent(" = "\n\n        // Asegurarse de que el directorio exista\n        let fileManager = FileManager.default\n        try fileManager.createDirectory(atPath: apacheVhostsDir, withIntermediateDirectories: true)\n\n        let publicRoot = sitePublicRoot(documentRoot: documentRoot, publicDirectory: site.publicDirectory)\n        let phpPort = phpFpmPort(for: site.phpVersion)\n\n        // Ruta del directorio de registros\n        let logsDir = (documentRoot as NSString).appendingPathComponent(";
", tap]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n            return process.terminationStatus == 0\n        } catch {\n            return false\n        }\n    }\n\n    /// 确保 PHP tap 已添加（用于安装旧版本 PHP）\n    private func ensurePHPTapInstalled() async -> Bool {\n        if await isTapInstalled(phpTap) {\n            return true\n        }\n        return await addTap(phpTap)\n    }\n\n    /// 获取 PHP 版本的完整 brew 包名\n    private func getFullPHPBrewName(_ packageId: String) -> String {\n        if legacyPHPVersions.contains(packageId) {\n            return " = ", tap]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n            return process.terminationStatus == 0\n        } catch {\n            return false\n        }\n    }\n\n    /// Asegurar que el tap de PHP esté instalado (para instalar versiones antiguas de PHP)\n    private func ensurePHPTapInstalled() async -> Bool {\n        if await isTapInstalled(phpTap) {\n            return true\n        }\n        return await addTap(phpTap)\n    }\n\n    /// Obtener el nombre completo del paquete brew para una versión de PHP\n    private func getFullPHPBrewName(_ packageId: String) -> String {\n        if legacyPHPVersions.contains(packageId) {\n            return ";
"检查 \\(sites.count) 个站点" = "Verificando \\(sites.count) sitios";
"字符数: \\(convertedRules.count)" = "字符数: \\(convertedRules.count)";
"\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerView\n\n            Divider()\n\n            // 内容区域\n            ScrollView {\n                VStack(alignment: .leading, spacing: 20) {\n                    // 检测状态\n                    if isDetecting {\n                        detectingView\n                    } else {\n                        // 端口状态\n                        portStatusView\n\n                        // 安装模式选择\n                        installModeView\n\n                        // 端口配置\n                        if selectedMode == .coexist {\n                            portConfigView\n                        }\n                    }\n                }\n                .padding(20)\n            }\n\n            Divider()\n\n            // 底部按钮\n            footerView\n        }\n        .frame(width: 500, height: 480)\n        .onAppear {\n            Task { await detectPorts() }\n        }\n    }\n\n    // MARK: - Header View\n    private var headerView: some View {\n        HStack {\n            Image(systemName: serverType.icon)\n                .font(.title2)\n                .foregroundColor(serverType.iconColor)\n            Text(" = "\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerView\n\n            Divider()\n\n            // 内容区域\n            ScrollView {\n                VStack(alignment: .leading, spacing: 20) {\n                    // 检测状态\n                    if isDetecting {\n                        detectingView\n                    } else {\n                        // 端口状态\n                        portStatusView\n\n                        // 安装模式选择\n                        installModeView\n\n                        // 端口配置\n                        if selectedMode == .coexist {\n                            portConfigView\n                        }\n                    }\n                }\n                .padding(20)\n            }\n\n            Divider()\n\n            // 底部按钮\n            footerView\n        }\n        .frame(width: 500, height: 480)\n        .onAppear {\n            Task { await detectPorts() }\n        }\n    }\n\n    // MARK: - Header View\n    private var headerView: some View {\n        HStack {\n            Image(systemName: serverType.icon)\n                .font(.title2)\n                .foregroundColor(serverType.iconColor)\n            Text(";
") {\n            return [pathProvider.getRedisConfigPath()]\n        }\n        return []\n    }\n\n    /// 检查 Nginx 配置语法（nonisolated，可在后台线程调用）\n    private nonisolated static func checkNginxConfigSyntax(homebrewPrefix: String) -> Bool {\n        let nginxBin = " = ") {\n            return [pathProvider.getRedisConfigPath()]\n        }\n        return []\n    }\n\n    /// 检查 Nginx 配置语法（nonisolated，可在后台线程调用）\n    private nonisolated static func checkNginxConfigSyntax(homebrewPrefix: String) -> Bool {\n        let nginxBin = ";
")\n                                .font(.system(size: 10))\n                                .foregroundColor(.secondary)\n                        }\n                        .padding(.horizontal, 12)\n                        .padding(.vertical, 8)\n            .glass(.input)\n                        .overlay(\n                            RoundedRectangle(cornerRadius: 6)\n                                .stroke(Color.green, lineWidth: 1)\n                        )\n                    }\n                    .frame(width: 200)\n                }\n\n                // 指定IP输入框\n                if selectedAccessType == .customIP {\n                    HStack(spacing: 16) {\n                        Text(" = ")\n                                .font(.system(size: 10))\n                                .foregroundColor(.secondary)\n                        }\n                        .padding(.horizontal, 12)\n                        .padding(.vertical, 8)\n            .glass(.input)\n                        .overlay(\n                            RoundedRectangle(cornerRadius: 6)\n                                .stroke(Color.green, lineWidth: 1)\n                        )\n                    }\n                    .frame(width: 200)\n                }\n\n                // 指定IP输入框\n                if selectedAccessType == .customIP {\n                    HStack(spacing: 16) {\n                        Text(";
",\n                            isEnabled: true,\n                            isManaged: true\n                        )\n                        try await hostsManager.addEntry(entry)\n                    }\n                    // 保存 hosts 文件\n                    try await hostsManager.saveHosts()\n                }\n                \n                // 检查 Web 服务器是否运行，如果未运行则给出提示\n                await checkWebServerStatus(webServer: selectedWebServer)\n                \n                dismiss()\n            } catch {\n                errorMessage = error.localizedDescription\n                showError = true\n                isSubmitting = false\n            }\n        }\n    }\n}\n\n// MARK: - Feature Card Component\nstruct FeatureCard: View {\n    let icon: String\n    let title: LocalizedStringKey\n    let subtitle: LocalizedStringKey\n    @Binding var isSelected: Bool\n    var isDisabled: Bool = false\n    var disabledReason: LocalizedStringKey? = nil\n\n    private var iconBackground: Color {\n        if isDisabled {\n            return Color.gray.opacity(AppTokens.Opacity.light)\n        } else if isSelected {\n            return Color.white\n        } else {\n            return BuildInfo.uiVersion == " = ",\n                            isEnabled: true,\n                            isManaged: true\n                        )\n                        try await hostsManager.addEntry(entry)\n                    }\n                    // 保存 hosts 文件\n                    try await hostsManager.saveHosts()\n                }\n                \n                // 检查 Web 服务器是否运行，如果未运行则给出提示\n                await checkWebServerStatus(webServer: selectedWebServer)\n                \n                dismiss()\n            } catch {\n                errorMessage = error.localizedDescription\n                showError = true\n                isSubmitting = false\n            }\n        }\n    }\n}\n\n// MARK: - Feature Card Component\nstruct FeatureCard: View {\n    let icon: String\n    let title: LocalizedStringKey\n    let subtitle: LocalizedStringKey\n    @Binding var isSelected: Bool\n    var isDisabled: Bool = false\n    var disabledReason: LocalizedStringKey? = nil\n\n    private var iconBackground: Color {\n        if isDisabled {\n            return Color.gray.opacity(AppTokens.Opacity.light)\n        } else if isSelected {\n            return Color.white\n        } else {\n            return BuildInfo.uiVersion == ";
")\n                    .foregroundColor(.secondary)\n                Spacer()\n            }\n            .padding(.horizontal, 8)\n            .padding(.vertical, 6)\n            .glass(.input)\n        }\n    }\n\n    // 错误视图\n    private func errorView(_ error: String) -> some View {\n        HStack {\n            Image(systemName: " = ")\n                    .foregroundColor(.secondary)\n                Spacer()\n            }\n            .padding(.horizontal, 8)\n            .padding(.vertical, 6)\n            .glass(.input)\n        }\n    }\n\n    // 错误视图\n    private func errorView(_ error: String) -> some View {\n        HStack {\n            Image(systemName: ";
"\n        ]\n        for path in brewPaths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n        // 默认路径\n        return " = "\n        ]\n        for path in brewPaths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n        // 默认路径\n        return ";
")\n                            .font(.system(size: 12, weight: .bold))\n                            .foregroundColor(.blue)\n                    }\n                }\n            }\n            .buttonStyle(.plain)\n            .frame(width: 30)\n\n            // 网站名（可点击打开，多域名时显示下拉）\n            HStack(spacing: 2) {\n                Image(systemName: " = ")\n                            .font(.system(size: 12, weight: .bold))\n                            .foregroundColor(.blue)\n                    }\n                }\n            }\n            .buttonStyle(.plain)\n            .frame(width: 30)\n\n            // 网站名（可点击打开，多域名时显示下拉）\n            HStack(spacing: 2) {\n                Image(systemName: ";
") }\n        } catch {\n            // 如果无法读取目录，默认检查 PHP\n            return true\n        }\n    }\n\n    // MARK: - Hosts 检查\n\n    private func checkHosts(for site: Site) async -> HealthCheckResult {\n        var details: [String] = []\n        var missingDomains: [String] = []\n\n        // 读取 hosts 文件\n        do {\n            try await hostsManager.loadHostsAsync()\n        } catch {\n            return HealthCheckResult(\n                type: .hosts,\n                status: .error,\n                message: " = ") }\n        } catch {\n            // 如果无法读取目录，默认检查 PHP\n            return true\n        }\n    }\n\n    // MARK: - Hosts 检查\n\n    private func checkHosts(for site: Site) async -> HealthCheckResult {\n        var details: [String] = []\n        var missingDomains: [String] = []\n\n        // 读取 hosts 文件\n        do {\n            try await hostsManager.loadHostsAsync()\n        } catch {\n            return HealthCheckResult(\n                type: .hosts,\n                status: .error,\n                message: ";
")\n                }\n            }\n\n            // 检测 proxy_pass（反向代理配置）\n            if trimmed.hasPrefix(" = ")\n                }\n            }\n\n            // 检测 proxy_pass（反向代理配置）\n            if trimmed.hasPrefix(";
"\\(displayName) 错误日志" = "\\(displayName) 错误日志";
")\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n            } else {\n                // 通过应用创建的数据库，有保存密码\n                HStack(spacing: 4) {\n                    Text(showPassword ? database.userPassword : " = ")\n                    .font(.system(size: 11))\n                    .foregroundColor(.secondary)\n            } else {\n                // 通过应用创建的数据库，有保存密码\n                HStack(spacing: 4) {\n                    Text(showPassword ? database.userPassword : ";
"\n        let result = await executeSQL(sql)\n\n        if !result.success {\n            errorMessage = result.error\n        }\n        return result.success\n    }\n\n    /// 修改用户主机（访问权限）\n    func changeUserHost(username: String, newHost: String) async -> Bool {\n        // 1. 先删除目标 Host 的旧记录（如果存在），避免主键冲突\n        let deleteSql1 = " = "\n        let result = await executeSQL(sql)\n\n        if !result.success {\n            errorMessage = result.error\n        }\n        return result.success\n    }\n\n    /// 修改用户主机（访问权限）\n    func changeUserHost(username: String, newHost: String) async -> Bool {\n        // 1. 先删除目标 Host 的旧记录（如果存在），避免主键冲突\n        let deleteSql1 = ";
"\n            )\n        }\n\n        // 恢复备份\n        try? fileManager.removeItem(atPath: backup.originalPath)\n        try fileManager.copyItem(atPath: backup.backupPath, toPath: backup.originalPath)\n    }\n\n    // MARK: - 查询操作\n\n    /// 获取指定服务的所有备份（按时间倒序）\n    func getBackups(for serviceName: String) -> [ConfigBackup] {\n        let records = loadBackupRecords()\n        return records\n            .filter { $0.serviceName == serviceName }\n            .sorted { $0.createdAt > $1.createdAt }\n    }\n\n    /// 获取指定站点的所有备份（按时间倒序）\n    func getBackups(for siteId: UUID) -> [ConfigBackup] {\n        let records = loadBackupRecords()\n        return records\n            .filter { $0.siteId == siteId }\n            .sorted { $0.createdAt > $1.createdAt }\n    }\n\n    /// 获取所有备份（按时间倒序）\n    func getAllBackups() -> [ConfigBackup] {\n        return loadBackupRecords().sorted { $0.createdAt > $1.createdAt }\n    }\n\n    // MARK: - 删除操作\n\n    /// 删除指定备份\n    func deleteBackup(_ backup: ConfigBackup) {\n        // 删除文件\n        try? FileManager.default.removeItem(atPath: backup.backupPath)\n\n        // 更新记录\n        var records = loadBackupRecords()\n        records.removeAll { $0.id == backup.id }\n        saveBackupRecords(records)\n    }\n\n    /// 删除指定服务的所有备份\n    func deleteAllBackups(for serviceName: String) {\n        let backups = getBackups(for: serviceName)\n        for backup in backups {\n            deleteBackup(backup)\n        }\n    }\n\n    // MARK: - 私有方法\n\n    private func loadBackupRecords() -> [ConfigBackup] {\n        guard FileManager.default.fileExists(atPath: recordsFilePath.path),\n              let data = try? Data(contentsOf: recordsFilePath),\n              let records = try? JSONDecoder().decode([ConfigBackup].self, from: data) else {\n            return []\n        }\n        return records\n    }\n\n    private func saveBackupRecords(_ records: [ConfigBackup]) {\n        guard let data = try? JSONEncoder().encode(records) else { return }\n        try? data.write(to: recordsFilePath)\n    }\n\n    private func cleanupOldBackups(for serviceName: String) {\n        var records = loadBackupRecords()\n        let serviceBackups = records\n            .filter { $0.serviceName == serviceName && $0.siteId == nil }\n            .sorted { $0.createdAt > $1.createdAt }\n\n        // 如果超过最大数量，删除最旧的\n        if serviceBackups.count > maxBackupsPerService {\n            let toDelete = serviceBackups.suffix(from: maxBackupsPerService)\n            for backup in toDelete {\n                try? FileManager.default.removeItem(atPath: backup.backupPath)\n                records.removeAll { $0.id == backup.id }\n            }\n            saveBackupRecords(records)\n        }\n    }\n\n    /// 清理指定站点的旧备份\n    private func cleanupOldBackups(for siteId: UUID) {\n        var records = loadBackupRecords()\n        let siteBackups = records\n            .filter { $0.siteId == siteId }\n            .sorted { $0.createdAt > $1.createdAt }\n\n        // 如果超过最大数量，删除最旧的\n        if siteBackups.count > maxBackupsPerService {\n            let toDelete = siteBackups.suffix(from: maxBackupsPerService)\n            for backup in toDelete {\n                try? FileManager.default.removeItem(atPath: backup.backupPath)\n                records.removeAll { $0.id == backup.id }\n            }\n            saveBackupRecords(records)\n        }\n    }\n}\n\n// MARK: - 错误类型\n\nenum BackupError: LocalizedError {\n    case sourceFileNotFound\n    case backupFileNotFound\n    case restoreFailed\n\n    var errorDescription: String? {\n        switch self {\n        case .sourceFileNotFound:\n            return " = "\n            )\n        }\n\n        // 恢复备份\n        try? fileManager.removeItem(atPath: backup.originalPath)\n        try fileManager.copyItem(atPath: backup.backupPath, toPath: backup.originalPath)\n    }\n\n    // MARK: - 查询操作\n\n    /// 获取指定服务的所有备份（按时间倒序）\n    func getBackups(for serviceName: String) -> [ConfigBackup] {\n        let records = loadBackupRecords()\n        return records\n            .filter { $0.serviceName == serviceName }\n            .sorted { $0.createdAt > $1.createdAt }\n    }\n\n    /// 获取指定站点的所有备份（按时间倒序）\n    func getBackups(for siteId: UUID) -> [ConfigBackup] {\n        let records = loadBackupRecords()\n        return records\n            .filter { $0.siteId == siteId }\n            .sorted { $0.createdAt > $1.createdAt }\n    }\n\n    /// 获取所有备份（按时间倒序）\n    func getAllBackups() -> [ConfigBackup] {\n        return loadBackupRecords().sorted { $0.createdAt > $1.createdAt }\n    }\n\n    // MARK: - 删除操作\n\n    /// 删除指定备份\n    func deleteBackup(_ backup: ConfigBackup) {\n        // 删除文件\n        try? FileManager.default.removeItem(atPath: backup.backupPath)\n\n        // 更新记录\n        var records = loadBackupRecords()\n        records.removeAll { $0.id == backup.id }\n        saveBackupRecords(records)\n    }\n\n    /// 删除指定服务的所有备份\n    func deleteAllBackups(for serviceName: String) {\n        let backups = getBackups(for: serviceName)\n        for backup in backups {\n            deleteBackup(backup)\n        }\n    }\n\n    // MARK: - 私有方法\n\n    private func loadBackupRecords() -> [ConfigBackup] {\n        guard FileManager.default.fileExists(atPath: recordsFilePath.path),\n              let data = try? Data(contentsOf: recordsFilePath),\n              let records = try? JSONDecoder().decode([ConfigBackup].self, from: data) else {\n            return []\n        }\n        return records\n    }\n\n    private func saveBackupRecords(_ records: [ConfigBackup]) {\n        guard let data = try? JSONEncoder().encode(records) else { return }\n        try? data.write(to: recordsFilePath)\n    }\n\n    private func cleanupOldBackups(for serviceName: String) {\n        var records = loadBackupRecords()\n        let serviceBackups = records\n            .filter { $0.serviceName == serviceName && $0.siteId == nil }\n            .sorted { $0.createdAt > $1.createdAt }\n\n        // 如果超过最大数量，删除最旧的\n        if serviceBackups.count > maxBackupsPerService {\n            let toDelete = serviceBackups.suffix(from: maxBackupsPerService)\n            for backup in toDelete {\n                try? FileManager.default.removeItem(atPath: backup.backupPath)\n                records.removeAll { $0.id == backup.id }\n            }\n            saveBackupRecords(records)\n        }\n    }\n\n    /// 清理指定站点的旧备份\n    private func cleanupOldBackups(for siteId: UUID) {\n        var records = loadBackupRecords()\n        let siteBackups = records\n            .filter { $0.siteId == siteId }\n            .sorted { $0.createdAt > $1.createdAt }\n\n        // 如果超过最大数量，删除最旧的\n        if siteBackups.count > maxBackupsPerService {\n            let toDelete = siteBackups.suffix(from: maxBackupsPerService)\n            for backup in toDelete {\n                try? FileManager.default.removeItem(atPath: backup.backupPath)\n                records.removeAll { $0.id == backup.id }\n            }\n            saveBackupRecords(records)\n        }\n    }\n}\n\n// MARK: - 错误类型\n\nenum BackupError: LocalizedError {\n    case sourceFileNotFound\n    case backupFileNotFound\n    case restoreFailed\n\n    var errorDescription: String? {\n        switch self {\n        case .sourceFileNotFound:\n            return ";
"确定要删除备份 \\(backup.name) 吗？" = "确定要删除备份 \\(backup.name) 吗？";
"\\(webServerDisplayName(webServer)) 配置校验失败：\\(message)" = "\\(webServerDisplayName(webServer)) 配置校验失败：\\(message)";
" {\n            content\n                .background(Color.white.opacity(0.1))\n                .cornerRadius(12)\n                .overlay(\n                    RoundedRectangle(cornerRadius: 12)\n                        .stroke(Color.white.opacity(0.1), lineWidth: 1)\n                )\n        } else {\n            content\n                .background(Color.white)\n                .cornerRadius(12)\n                .overlay(\n                    RoundedRectangle(cornerRadius: 12)\n                        .stroke(AppColors.border, lineWidth: 1)\n                )\n                .shadow(color: Color.black.opacity(AppTokens.Opacity.ultraLight), radius: 2, y: 1)\n        }\n    }\n}\n\nstruct DatabaseTabItem: View {\n    let title: LocalizedStringKey\n    let isActive: Bool\n    let isLocal: Bool\n    let onSelect: () -> Void\n    let onClose: (() -> Void)?\n\n    @State private var isHovered = false\n\n    var body: some View {\n        HStack(spacing: 6) {\n            // 类型图标\n            Image(systemName: isLocal ? " = " {\n            content\n                .background(Color.white.opacity(0.1))\n                .cornerRadius(12)\n                .overlay(\n                    RoundedRectangle(cornerRadius: 12)\n                        .stroke(Color.white.opacity(0.1), lineWidth: 1)\n                )\n        } else {\n            content\n                .background(Color.white)\n                .cornerRadius(12)\n                .overlay(\n                    RoundedRectangle(cornerRadius: 12)\n                        .stroke(AppColors.border, lineWidth: 1)\n                )\n                .shadow(color: Color.black.opacity(AppTokens.Opacity.ultraLight), radius: 2, y: 1)\n        }\n    }\n}\n\nstruct DatabaseTabItem: View {\n    let title: LocalizedStringKey\n    let isActive: Bool\n    let isLocal: Bool\n    let onSelect: () -> Void\n    let onClose: (() -> Void)?\n\n    @State private var isHovered = false\n\n    var body: some View {\n        HStack(spacing: 6) {\n            // 类型图标\n            Image(systemName: isLocal ? ";
")\n        try? FileManager.default.createDirectory(at: backupDir, withIntermediateDirectories: true)\n        return backupDir\n    }\n\n    /// 获取数据库备份数量\n    func getBackupCount(for databaseName: String) -> Int {\n        let backupDir = getBackupDirectory()\n        let pattern = " = ")\n        try? FileManager.default.createDirectory(at: backupDir, withIntermediateDirectories: true)\n        return backupDir\n    }\n\n    /// 获取数据库备份数量\n    func getBackupCount(for databaseName: String) -> Int {\n        let backupDir = getBackupDirectory()\n        let pattern = ";
"\n        let length = 8\n        return String((0..<length).map { _ in characters.randomElement()! })\n    }\n    \n    private func saveNewRule() {\n        let details: String\n        var username: String? = nil\n        var password: String? = nil\n        var ipList: String? = nil\n        var ipMode: String? = nil\n\n        if newRuleType == .password {\n            // 验证用户名\n            if newRuleUsername.count < 2 {\n                // TODO: 显示错误提示\n                return\n            }\n\n            // 验证密码\n            if newRulePassword.count < 6 || newRulePassword.count > 32 {\n                // TODO: 显示错误提示\n                return\n            }\n\n            details = " = "\n        let length = 8\n        return String((0..<length).map { _ in characters.randomElement()! })\n    }\n    \n    private func saveNewRule() {\n        let details: String\n        var username: String? = nil\n        var password: String? = nil\n        var ipList: String? = nil\n        var ipMode: String? = nil\n\n        if newRuleType == .password {\n            // 验证用户名\n            if newRuleUsername.count < 2 {\n                // TODO: 显示错误提示\n                return\n            }\n\n            // 验证密码\n            if newRulePassword.count < 6 || newRulePassword.count > 32 {\n                // TODO: 显示错误提示\n                return\n            }\n\n            details = ";
")\n                    .foregroundColor(.white)\n            }\n            .buttonStyle(.borderedProminent)\n            .tint(.green)\n\n            // 同步配置按钮\n            Button(action: { showVhostSyncSheet = true }) {\n                HStack(spacing: 4) {\n                    Image(systemName: " = ")\n                    .foregroundColor(.white)\n            }\n            .buttonStyle(.borderedProminent)\n            .tint(.green)\n\n            // 同步配置按钮\n            Button(action: { showVhostSyncSheet = true }) {\n                HStack(spacing: 4) {\n                    Image(systemName: ";
")\n                                    .font(.system(size: 10))\n                                    .foregroundColor(.blue)\n                                Text(domain)\n                                    .font(.system(size: 12))\n                                    .foregroundColor(Color.primary)\n                                    .frame(maxWidth: .infinity, alignment: .leading)\n                            }\n                            .padding(.horizontal, 12)\n                            .padding(.vertical, 8)\n                            .background(domain == site.primaryDomain ? Color.blue.opacity(AppTokens.Opacity.light) : Color.clear)\n                        }\n                        .buttonStyle(.plain)\n                    }\n                }\n                .frame(minWidth: 150)\n                .padding(.vertical, 4)\n            }\n            \n            // 状态\n            HStack(spacing: 4) {\n                Circle()\n                    .fill(site.status == .running ? Color.green : Color.gray)\n                    .frame(width: 6, height: 6)\n                Text(site.status == .running ? LocalizedStringKey(" = ")\n                                    .font(.system(size: 10))\n                                    .foregroundColor(.blue)\n                                Text(domain)\n                                    .font(.system(size: 12))\n                                    .foregroundColor(Color.primary)\n                                    .frame(maxWidth: .infinity, alignment: .leading)\n                            }\n                            .padding(.horizontal, 12)\n                            .padding(.vertical, 8)\n                            .background(domain == site.primaryDomain ? Color.blue.opacity(AppTokens.Opacity.light) : Color.clear)\n                        }\n                        .buttonStyle(.plain)\n                    }\n                }\n                .frame(minWidth: 150)\n                .padding(.vertical, 4)\n            }\n            \n            // 状态\n            HStack(spacing: 4) {\n                Circle()\n                    .fill(site.status == .running ? Color.green : Color.gray)\n                    .frame(width: 6, height: 6)\n                Text(site.status == .running ? LocalizedStringKey(";
"确定要删除 \\(version.name) \\(version.version) 吗？此操作不可恢复。" = "确定要删除 \\(version.name) \\(version.version) 吗？此操作不可恢复。";
"通义" = "通义";
" },\n                                set: { newValue in\n                                    configValues[definition.key] = newValue\n                                    updateEstimatedMemory()\n                                }\n                            ),\n                            description: definition.description,\n                            unit: definition.unit,\n                            recommendedValue: getRecommendedValue(for: definition.key)\n                        )\n\n                        if index < PerformanceConfigItem.definitions.count - 1 {\n                            Divider()\n                                .padding(.leading, 16)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    // 表头\n    private var configTableHeader: some View {\n        HStack(spacing: 0) {\n            Text(" = " },\n                                set: { newValue in\n                                    configValues[definition.key] = newValue\n                                    updateEstimatedMemory()\n                                }\n                            ),\n                            description: definition.description,\n                            unit: definition.unit,\n                            recommendedValue: getRecommendedValue(for: definition.key)\n                        )\n\n                        if index < PerformanceConfigItem.definitions.count - 1 {\n                            Divider()\n                                .padding(.leading, 16)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    // 表头\n    private var configTableHeader: some View {\n        HStack(spacing: 0) {\n            Text(";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // Tab 栏\n    private var tabBar: some View {\n        HStack(spacing: 0) {\n            ForEach(CertificateTab.allCases, id: \\.self) { tab in\n                Button(action: { selectedTab = tab }) {\n                    Text(LocalizedStringKey(tab.rawValue))\n                        .font(.system(size: 13))\n                        .padding(.horizontal, 20)\n                        .padding(.vertical, 10)\n                        .background(selectedTab == tab ? Color.accentColor.opacity(AppTokens.Opacity.hover) : Color.clear)\n                        .foregroundColor(selectedTab == tab ? .accentColor : .primary)\n                }\n                .buttonStyle(.plain)\n            }\n            Spacer()\n        }\n        .padding(.horizontal, 16)\n        .background(Color(NSColor.controlBackgroundColor).opacity(AppTokens.Opacity.disabled))\n    }\n\n    // 内容区域\n    @ViewBuilder\n    private var contentArea: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                switch selectedTab {\n                case .ca:\n                    caCertificateView\n                case .sites:\n                    siteCertificatesView\n                }\n            }\n            .padding()\n        }\n    }\n\n    // CA 证书视图\n    private var caCertificateView: some View {\n        VStack(alignment: .leading, spacing: 16) {\n            HStack {\n                Text(LocalizedStringKey(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // Tab 栏\n    private var tabBar: some View {\n        HStack(spacing: 0) {\n            ForEach(CertificateTab.allCases, id: \\.self) { tab in\n                Button(action: { selectedTab = tab }) {\n                    Text(LocalizedStringKey(tab.rawValue))\n                        .font(.system(size: 13))\n                        .padding(.horizontal, 20)\n                        .padding(.vertical, 10)\n                        .background(selectedTab == tab ? Color.accentColor.opacity(AppTokens.Opacity.hover) : Color.clear)\n                        .foregroundColor(selectedTab == tab ? .accentColor : .primary)\n                }\n                .buttonStyle(.plain)\n            }\n            Spacer()\n        }\n        .padding(.horizontal, 16)\n        .background(Color(NSColor.controlBackgroundColor).opacity(AppTokens.Opacity.disabled))\n    }\n\n    // 内容区域\n    @ViewBuilder\n    private var contentArea: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                switch selectedTab {\n                case .ca:\n                    caCertificateView\n                case .sites:\n                    siteCertificatesView\n                }\n            }\n            .padding()\n        }\n    }\n\n    // CA 证书视图\n    private var caCertificateView: some View {\n        VStack(alignment: .leading, spacing: 16) {\n            HStack {\n                Text(LocalizedStringKey(";
"\n        let result2 = await executeSQL(sql2)\n        if !result2.success {\n            errorMessage = result2.error\n            return false\n        }\n\n        // 4. 刷新权限\n        let sql3 = " = "\n        let result2 = await executeSQL(sql2)\n        if !result2.success {\n            errorMessage = result2.error\n            return false\n        }\n\n        // 4. 刷新权限\n        let sql3 = ";
"\\(error.localizedDescription)。已尝试回滚，但仍有问题：\\(rollbackErrors.joined(separator: " = "\\(error.localizedDescription)。已尝试回滚，但仍有问题：\\(rollbackErrors.joined(separator: ";
")\n                }\n            }\n            return false\n        } catch {\n            return false\n        }\n    }\n\n    /// 更新单个软件的运行状态（不重新加载整个列表）\n    func updateSoftwareRunningState(_ softwareId: String, isRunning: Bool) {\n        // 更新 PHP 版本列表\n        if let index = phpVersions.firstIndex(where: { $0.id == softwareId }) {\n            phpVersions[index].isRunning = isRunning\n            return\n        }\n        // 更新 Web 服务器列表\n        if let index = webServers.firstIndex(where: { $0.id == softwareId }) {\n            webServers[index].isRunning = isRunning\n            return\n        }\n        // 更新数据库列表\n        if let index = databases.firstIndex(where: { $0.id == softwareId }) {\n            databases[index].isRunning = isRunning\n            return\n        }\n        // 更新缓存列表\n        if let index = caches.firstIndex(where: { $0.id == softwareId }) {\n            caches[index].isRunning = isRunning\n            return\n        }\n        // 更新工具列表\n        if let index = tools.firstIndex(where: { $0.id == softwareId }) {\n            tools[index].isRunning = isRunning\n            return\n        }\n    }\n\n    // MARK: - 构建版本列表\n\n    private nonisolated func buildPHPVersions(installed: [String: String], running: Set<String>, phpPorts: [String: Int]) -> [SoftwareVersion] {\n        var versions: [SoftwareVersion] = []\n\n        // 检测当前默认的 PHP 版本（通过符号链接）\n        let defaultPHPPackage = detectDefaultPHPPackage()\n\n        // 检查已安装的 PHP（无版本号的包）\n        if let ver = installed[" = ")\n                }\n            }\n            return false\n        } catch {\n            return false\n        }\n    }\n\n    /// 更新单个软件的运行状态（不重新加载整个列表）\n    func updateSoftwareRunningState(_ softwareId: String, isRunning: Bool) {\n        // 更新 PHP 版本列表\n        if let index = phpVersions.firstIndex(where: { $0.id == softwareId }) {\n            phpVersions[index].isRunning = isRunning\n            return\n        }\n        // 更新 Web 服务器列表\n        if let index = webServers.firstIndex(where: { $0.id == softwareId }) {\n            webServers[index].isRunning = isRunning\n            return\n        }\n        // 更新数据库列表\n        if let index = databases.firstIndex(where: { $0.id == softwareId }) {\n            databases[index].isRunning = isRunning\n            return\n        }\n        // 更新缓存列表\n        if let index = caches.firstIndex(where: { $0.id == softwareId }) {\n            caches[index].isRunning = isRunning\n            return\n        }\n        // 更新工具列表\n        if let index = tools.firstIndex(where: { $0.id == softwareId }) {\n            tools[index].isRunning = isRunning\n            return\n        }\n    }\n\n    // MARK: - 构建版本列表\n\n    private nonisolated func buildPHPVersions(installed: [String: String], running: Set<String>, phpPorts: [String: Int]) -> [SoftwareVersion] {\n        var versions: [SoftwareVersion] = []\n\n        // 检测当前默认的 PHP 版本（通过符号链接）\n        let defaultPHPPackage = detectDefaultPHPPackage()\n\n        // 检查已安装的 PHP（无版本号的包）\n        if let ver = installed[";
"✅ 已强力修复站点配置与 hosts: \\(site.name)" = "✅ 已强力修复站点配置与 hosts: \\(site.name)";
")\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 60, alignment: .leading)\n            \n            // 备注\n            Text(verbatim: site.note.isEmpty ? " = ")\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 60, alignment: .leading)\n            \n            // 备注\n            Text(verbatim: site.note.isEmpty ? ";
")\n    ]\n}\n\n/// 性能方案预设值\nenum PerformancePresets {\n\n    /// 根据内存方案获取推荐配置\n    static func getRecommendedValues(for plan: MemoryPlan) -> [String: String] {\n        switch plan {\n        case .mem1_2GB:\n            return [\n                " = ")\n    ]\n}\n\n/// 性能方案预设值\nenum PerformancePresets {\n\n    /// 根据内存方案获取推荐配置\n    static func getRecommendedValues(for plan: MemoryPlan) -> [String: String] {\n        switch plan {\n        case .mem1_2GB:\n            return [\n                ";
"正在导入 \\(candidate.inferredDomain) (\\(currentIndex)/\\(totalCount))" = "正在导入 \\(candidate.inferredDomain) (\\(currentIndex)/\\(totalCount))";
"⚠ 版本不匹配，期望: \\(site.phpVersion)" = "⚠ 版本不匹配，期望: \\(site.phpVersion)";
"]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = Pipe()\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            guard let output = String(data: data, encoding: .utf8),\n                  !output.isEmpty else {\n                return PortInfo(port: port, isAvailable: true,\n                              processName: nil, processId: nil)\n            }\n\n            // 解析 lsof 输出\n            let lines = output.components(separatedBy: .newlines)\n            for line in lines.dropFirst() { // 跳过标题行\n                let parts = line.split(separator: " = "]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = Pipe()\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            guard let output = String(data: data, encoding: .utf8),\n                  !output.isEmpty else {\n                return PortInfo(port: port, isAvailable: true,\n                              processName: nil, processId: nil)\n            }\n\n            // 解析 lsof 输出\n            let lines = output.components(separatedBy: .newlines)\n            for line in lines.dropFirst() { // 跳过标题行\n                let parts = line.split(separator: ";
")\n            .map { $0.trimmingCharacters(in: .whitespaces) }\n            .filter { !$0.isEmpty }\n        return !siteName.isEmpty && !domainList.isEmpty && !siteDirectory.isEmpty\n    }\n\n    // 选择目录\n    private func selectDirectory() {\n        let panel = NSOpenPanel()\n        panel.canChooseFiles = false\n        panel.canChooseDirectories = true\n        panel.allowsMultipleSelection = false\n        panel.directoryURL = URL(fileURLWithPath: siteDirectory.isEmpty ? " = ")\n            .map { $0.trimmingCharacters(in: .whitespaces) }\n            .filter { !$0.isEmpty }\n        return !siteName.isEmpty && !domainList.isEmpty && !siteDirectory.isEmpty\n    }\n\n    // 选择目录\n    private func selectDirectory() {\n        let panel = NSOpenPanel()\n        panel.canChooseFiles = false\n        panel.canChooseDirectories = true\n        panel.allowsMultipleSelection = false\n        panel.directoryURL = URL(fileURLWithPath: siteDirectory.isEmpty ? ";
"\n        )\n        installProgressMap[software.id] = initialProgress\n\n        // 添加到正在安装的版本列表\n        installingVersions[software.id] = InstallingVersion(\n            id: software.id,\n            name: software.name,\n            version: software.version,\n            progress: initialProgress\n        )\n\n        // 检查端口冲突\n        if let port = software.port {\n            let conflict = checkPortConflict(port: port, excluding: software.id, category: category)\n            if let conflictSoftware = conflict {\n                updateInstallFailure(\n                    softwareId: software.id,\n                    error: " = "\n        )\n        installProgressMap[software.id] = initialProgress\n\n        // 添加到正在安装的版本列表\n        installingVersions[software.id] = InstallingVersion(\n            id: software.id,\n            name: software.name,\n            version: software.version,\n            progress: initialProgress\n        )\n\n        // 检查端口冲突\n        if let port = software.port {\n            let conflict = checkPortConflict(port: port, excluding: software.id, category: category)\n            if let conflictSoftware = conflict {\n                updateInstallFailure(\n                    softwareId: software.id,\n                    error: ";
")\n            .map { $0.trimmingCharacters(in: .whitespaces) }\n            .filter { !$0.isEmpty }\n\n        guard let firstDomain = domainList.first else { return }\n\n        // 去掉端口号\n        let cleanDomain = firstDomain.split(separator: " = ")\n            .map { $0.trimmingCharacters(in: .whitespaces) }\n            .filter { !$0.isEmpty }\n\n        guard let firstDomain = domainList.first else { return }\n\n        // 去掉端口号\n        let cleanDomain = firstDomain.split(separator: ";
" }.count\n\n                    // server 块结束\n                    if braceCount == 0 {\n                        // 注释掉整个 server 块\n                        for j in serverBlockStart...i {\n                            if !lines[j].trimmingCharacters(in: .whitespaces).isEmpty {\n                                lines[j] = " = " }.count\n\n                    // server 块结束\n                    if braceCount == 0 {\n                        // 注释掉整个 server 块\n                        for j in serverBlockStart...i {\n                            if !lines[j].trimmingCharacters(in: .whitespaces).isEmpty {\n                                lines[j] = ";
")\n                            .foregroundColor(.blue)\n                            .font(.system(size: 12))\n                        Text(domain)\n                            .font(.system(size: 13, design: .monospaced))\n                    }\n                }\n            }\n        }\n        .padding()\n            .glass(.input)\n    }\n\n    // MARK: - 有效期信息\n    private var validitySection: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            Text(" = ")\n                            .foregroundColor(.blue)\n                            .font(.system(size: 12))\n                        Text(domain)\n                            .font(.system(size: 13, design: .monospaced))\n                    }\n                }\n            }\n        }\n        .padding()\n            .glass(.input)\n    }\n\n    // MARK: - 有效期信息\n    private var validitySection: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            Text(";
"\n        }\n    }\n    \n    // 选择网站目录\n    private func selectSiteDirectory() {\n        let panel = NSOpenPanel()\n        panel.canChooseFiles = false\n        panel.canChooseDirectories = true\n        panel.allowsMultipleSelection = false\n        let defaultPath = siteDirectory.isEmpty ? (appSettings.defaultSiteRoot.isEmpty ? " = "\n        }\n    }\n    \n    // 选择网站目录\n    private func selectSiteDirectory() {\n        let panel = NSOpenPanel()\n        panel.canChooseFiles = false\n        panel.canChooseDirectories = true\n        panel.allowsMultipleSelection = false\n        let defaultPath = siteDirectory.isEmpty ? (appSettings.defaultSiteRoot.isEmpty ? ";
")\n                .foregroundColor(.red)\n                .font(.system(size: 12))\n\n            Text(log.errorMessage)\n                .font(.system(size: 12))\n                .foregroundColor(.primary)\n                .lineLimit(isExpanded ? nil : 2)\n                .fixedSize(horizontal: false, vertical: true)\n\n            Spacer()\n\n            // 展开/收起按钮（始终显示）\n            Image(systemName: isExpanded ? " = ")\n                .foregroundColor(.red)\n                .font(.system(size: 12))\n\n            Text(log.errorMessage)\n                .font(.system(size: 12))\n                .foregroundColor(.primary)\n                .lineLimit(isExpanded ? nil : 2)\n                .fixedSize(horizontal: false, vertical: true)\n\n            Spacer()\n\n            // 展开/收起按钮（始终显示）\n            Image(systemName: isExpanded ? ";
"数据库已导出到: \\(url.path)" = "数据库已导出到: \\(url.path)";
"✅ 已为 Nginx 代理生成 Apache 后端配置: \\(siteName)" = "✅ 已为 Nginx 代理生成 Apache 后端配置: \\(siteName)";
":\n            return getApacheDynamicPath(key: key, config: config, defaultValue: meta.suggestedValue)\n        default:\n            return meta.suggestedValue\n        }\n    }\n\n    /// MySQL 动态路径\n    private static func getMySQLDynamicPath(key: String, config: WorkspaceConfig, version: String?, defaultValue: String) -> String {\n        let ver = version ?? " = ":\n            return getApacheDynamicPath(key: key, config: config, defaultValue: meta.suggestedValue)\n        default:\n            return meta.suggestedValue\n        }\n    }\n\n    /// MySQL 动态路径\n    private static func getMySQLDynamicPath(key: String, config: WorkspaceConfig, version: String?, defaultValue: String) -> String {\n        let ver = version ?? ";
" 后面的非空白内容直到行尾\n        let pattern = " = " 后面的非空白内容直到行尾\n        let pattern = ";
" {\n            // V2 全局使用静态模糊背景，因此移除所有极其昂贵的实时 Material 渲染，彻底解决卡顿问题\n            switch level {\n            case .panel:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.extraLarge)\n                    .fill(isDark ? Color.black : Color.white)\n                    .opacity(isDark ? 0.3 : 0.4)\n            case .section:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                    .fill(isDark ? Color.black : Color.white)\n                    .opacity(isDark ? 0.15 : 0.2)\n            case .card:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.large)\n                    .fill(isDark ? Color.black : Color.white)\n                    .opacity(isDark ? 0.2 : 0.3)\n            case .chip:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                    .fill(isDark ? Color.black : Color.white)\n                    .opacity(isDark ? 0.15 : 0.25)\n            case .input:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                    .fill(isDark ? Color.black : Color.white)\n                    .opacity(isDark ? 0.1 : 0.15)\n            }\n        } else {\n            // V1 旧版逻辑保留\n            switch level {\n            case .panel:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.extraLarge)\n                    .fill(isDark ? .regularMaterial : .thinMaterial)\n                    .opacity(isDark ? 0.8 : 0.75)\n            case .section:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                    .fill(.regularMaterial)\n                    .opacity(isDark ? 0.75 : 0.65)\n            case .card:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.large)\n                    .fill(.thinMaterial)\n                    .opacity(isDark ? 0.7 : 0.6)\n            case .chip:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                    .fill(.ultraThinMaterial)\n                    .opacity(isDark ? 0.65 : 0.55)\n            case .input:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                    .fill(.ultraThinMaterial)\n                    .opacity(isDark ? 0.4 : 0.6)\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var highlightShape: some View {\n        let isDark = colorScheme == .dark\n        let highlightColor = Color.white\n        \n        switch level {\n        case .panel:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.extraLarge)\n                .fill(\n                    LinearGradient(\n                        colors: [highlightColor.opacity(isDark ? AppTokens.Opacity.ultraLight : AppTokens.Opacity.light), Color.clear],\n                        startPoint: .topLeading,\n                        endPoint: .bottomTrailing\n                    )\n                )\n        case .section:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                .fill(\n                    LinearGradient(\n                        colors: [highlightColor.opacity(isDark ? AppTokens.Opacity.ultraLight : AppTokens.Opacity.light), Color.clear],\n                        startPoint: .topLeading,\n                        endPoint: .bottomTrailing\n                    )\n                )\n        case .card:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.large)\n                .fill(\n                    LinearGradient(\n                        colors: [highlightColor.opacity(isDark ? AppTokens.Opacity.light : AppTokens.Opacity.hover), Color.clear],\n                        startPoint: .topLeading,\n                        endPoint: .bottomTrailing\n                    )\n                )\n        case .chip:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                .fill(\n                    LinearGradient(\n                        colors: [highlightColor.opacity(isDark ? AppTokens.Opacity.ultraLight : AppTokens.Opacity.light), Color.clear],\n                        startPoint: .topLeading,\n                        endPoint: .bottomTrailing\n                    )\n                )\n        case .input:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                .fill(\n                    LinearGradient(\n                        colors: [highlightColor.opacity(isDark ? AppTokens.Opacity.ultraLight : 0.05), Color.clear],\n                        startPoint: .topLeading,\n                        endPoint: .bottomTrailing\n                    )\n                )\n        }\n    }\n\n    @ViewBuilder\n    private var borderShape: some View {\n        let isDark = colorScheme == .dark\n        let borderColor = Color.white\n        \n        switch level {\n        case .panel:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.extraLarge).stroke(borderColor.opacity(isDark ? AppTokens.Opacity.light : AppTokens.Opacity.border), lineWidth: 1)\n        case .section:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.medium).stroke(borderColor.opacity(isDark ? AppTokens.Opacity.hover : AppTokens.Opacity.border), lineWidth: 1)\n        case .card:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.large).stroke(borderColor.opacity(isDark ? AppTokens.Opacity.hover : AppTokens.Opacity.border), lineWidth: 1)\n        case .chip:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.small).stroke(borderColor.opacity(isDark ? AppTokens.Opacity.light : AppTokens.Opacity.hover), lineWidth: 1)\n        case .input:\n            if BuildInfo.uiVersion == " = " {\n            // V2 全局使用静态模糊背景，因此移除所有极其昂贵的实时 Material 渲染，彻底解决卡顿问题\n            switch level {\n            case .panel:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.extraLarge)\n                    .fill(isDark ? Color.black : Color.white)\n                    .opacity(isDark ? 0.3 : 0.4)\n            case .section:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                    .fill(isDark ? Color.black : Color.white)\n                    .opacity(isDark ? 0.15 : 0.2)\n            case .card:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.large)\n                    .fill(isDark ? Color.black : Color.white)\n                    .opacity(isDark ? 0.2 : 0.3)\n            case .chip:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                    .fill(isDark ? Color.black : Color.white)\n                    .opacity(isDark ? 0.15 : 0.25)\n            case .input:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                    .fill(isDark ? Color.black : Color.white)\n                    .opacity(isDark ? 0.1 : 0.15)\n            }\n        } else {\n            // V1 旧版逻辑保留\n            switch level {\n            case .panel:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.extraLarge)\n                    .fill(isDark ? .regularMaterial : .thinMaterial)\n                    .opacity(isDark ? 0.8 : 0.75)\n            case .section:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                    .fill(.regularMaterial)\n                    .opacity(isDark ? 0.75 : 0.65)\n            case .card:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.large)\n                    .fill(.thinMaterial)\n                    .opacity(isDark ? 0.7 : 0.6)\n            case .chip:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                    .fill(.ultraThinMaterial)\n                    .opacity(isDark ? 0.65 : 0.55)\n            case .input:\n                RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                    .fill(.ultraThinMaterial)\n                    .opacity(isDark ? 0.4 : 0.6)\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var highlightShape: some View {\n        let isDark = colorScheme == .dark\n        let highlightColor = Color.white\n        \n        switch level {\n        case .panel:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.extraLarge)\n                .fill(\n                    LinearGradient(\n                        colors: [highlightColor.opacity(isDark ? AppTokens.Opacity.ultraLight : AppTokens.Opacity.light), Color.clear],\n                        startPoint: .topLeading,\n                        endPoint: .bottomTrailing\n                    )\n                )\n        case .section:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                .fill(\n                    LinearGradient(\n                        colors: [highlightColor.opacity(isDark ? AppTokens.Opacity.ultraLight : AppTokens.Opacity.light), Color.clear],\n                        startPoint: .topLeading,\n                        endPoint: .bottomTrailing\n                    )\n                )\n        case .card:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.large)\n                .fill(\n                    LinearGradient(\n                        colors: [highlightColor.opacity(isDark ? AppTokens.Opacity.light : AppTokens.Opacity.hover), Color.clear],\n                        startPoint: .topLeading,\n                        endPoint: .bottomTrailing\n                    )\n                )\n        case .chip:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                .fill(\n                    LinearGradient(\n                        colors: [highlightColor.opacity(isDark ? AppTokens.Opacity.ultraLight : AppTokens.Opacity.light), Color.clear],\n                        startPoint: .topLeading,\n                        endPoint: .bottomTrailing\n                    )\n                )\n        case .input:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                .fill(\n                    LinearGradient(\n                        colors: [highlightColor.opacity(isDark ? AppTokens.Opacity.ultraLight : 0.05), Color.clear],\n                        startPoint: .topLeading,\n                        endPoint: .bottomTrailing\n                    )\n                )\n        }\n    }\n\n    @ViewBuilder\n    private var borderShape: some View {\n        let isDark = colorScheme == .dark\n        let borderColor = Color.white\n        \n        switch level {\n        case .panel:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.extraLarge).stroke(borderColor.opacity(isDark ? AppTokens.Opacity.light : AppTokens.Opacity.border), lineWidth: 1)\n        case .section:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.medium).stroke(borderColor.opacity(isDark ? AppTokens.Opacity.hover : AppTokens.Opacity.border), lineWidth: 1)\n        case .card:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.large).stroke(borderColor.opacity(isDark ? AppTokens.Opacity.hover : AppTokens.Opacity.border), lineWidth: 1)\n        case .chip:\n            RoundedRectangle(cornerRadius: AppTokens.Radius.small).stroke(borderColor.opacity(isDark ? AppTokens.Opacity.light : AppTokens.Opacity.hover), lineWidth: 1)\n        case .input:\n            if BuildInfo.uiVersion == ";
"\n        var hasSSL = false\n        var ports: [Int] = []\n\n        // 解析配置文件\n        let lines = content.components(separatedBy: .newlines)\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n\n            // 解析 ServerName\n            if trimmed.hasPrefix(" = "\n        var hasSSL = false\n        var ports: [Int] = []\n\n        // 解析配置文件\n        let lines = content.components(separatedBy: .newlines)\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n\n            // 解析 ServerName\n            if trimmed.hasPrefix(";
"\n        }\n    }\n\n    // 创建默认配置文件\n    private func createDefaultConfigFile() {\n        let defaultPath = getDefaultConfigPath()\n        guard let path = defaultPath else { return }\n\n        // 确保目录存在\n        let directory = (path as NSString).deletingLastPathComponent\n        try? FileManager.default.createDirectory(\n            atPath: directory,\n            withIntermediateDirectories: true,\n            attributes: nil\n        )\n\n        // 创建默认配置内容\n        let defaultContent = getDefaultConfigContent()\n\n        do {\n            try defaultContent.write(toFile: path, atomically: true, encoding: .utf8)\n            // 重新加载\n            errorMessage = nil\n            loadConfigFile()\n        } catch {\n            errorMessage = " = "\n        }\n    }\n\n    // 创建默认配置文件\n    private func createDefaultConfigFile() {\n        let defaultPath = getDefaultConfigPath()\n        guard let path = defaultPath else { return }\n\n        // 确保目录存在\n        let directory = (path as NSString).deletingLastPathComponent\n        try? FileManager.default.createDirectory(\n            atPath: directory,\n            withIntermediateDirectories: true,\n            attributes: nil\n        )\n\n        // 创建默认配置内容\n        let defaultContent = getDefaultConfigContent()\n\n        do {\n            try defaultContent.write(toFile: path, atomically: true, encoding: .utf8)\n            // 重新加载\n            errorMessage = nil\n            loadConfigFile()\n        } catch {\n            errorMessage = ";
"\n    @State private var selectedSites: Set<UUID> = []\n    @State private var showTrashOnly = false\n    \n    // Sheets states\n    @State private var showAddSiteSheet = false\n    @State private var showVhostSyncSheet = false\n    @State private var showBatchImportSheet = false\n    @State private var showRepairSheet = false\n    @State private var showHealthCheckSheet = false\n    @State private var showSettingsSheet = false\n    \n    var body: some View {\n        VStack(spacing: 0) {\n            // 顶部工具栏 (两行布局)\n            VStack(spacing: 12) {\n                // 第一行：主要操作按钮\n                HStack(spacing: 8) {\n                    if showTrashOnly {\n                        MacButton(icon: " = "\n    @State private var selectedSites: Set<UUID> = []\n    @State private var showTrashOnly = false\n    \n    // Sheets states\n    @State private var showAddSiteSheet = false\n    @State private var showVhostSyncSheet = false\n    @State private var showBatchImportSheet = false\n    @State private var showRepairSheet = false\n    @State private var showHealthCheckSheet = false\n    @State private var showSettingsSheet = false\n    \n    var body: some View {\n        VStack(spacing: 0) {\n            // 顶部工具栏 (两行布局)\n            VStack(spacing: 12) {\n                // 第一行：主要操作按钮\n                HStack(spacing: 8) {\n                    if showTrashOnly {\n                        MacButton(icon: ";
")\n                    try data.write(to: tempURL, options: .atomic)\n                    \n                    // 移动到目标位置\n                    if FileManager.default.fileExists(atPath: self.fileURL.path) {\n                        try FileManager.default.removeItem(at: self.fileURL)\n                    }\n                    try FileManager.default.moveItem(at: tempURL, to: self.fileURL)\n                    \n                    continuation.resume()\n                } catch {\n                    continuation.resume(throwing: error)\n                }\n            }\n        }\n    }\n    \n    /// 异步保存数据（带防抖）\n    func save(_ items: [T]) {\n        // 取消之前的保存任务\n        saveTask?.cancel()\n        \n        // 创建新的保存任务\n        saveTask = Task { [weak self] in\n            guard let self = self else { return }\n            \n            // 等待防抖延迟\n            try? await Task.sleep(nanoseconds: UInt64(self.saveDelay * 1_000_000_000))\n            \n            // 检查任务是否被取消\n            guard !Task.isCancelled else { return }\n            \n            // 执行保存\n            do {\n                try await self.saveImmediately(items)\n            } catch {\n                print(" = ")\n                    try data.write(to: tempURL, options: .atomic)\n                    \n                    // 移动到目标位置\n                    if FileManager.default.fileExists(atPath: self.fileURL.path) {\n                        try FileManager.default.removeItem(at: self.fileURL)\n                    }\n                    try FileManager.default.moveItem(at: tempURL, to: self.fileURL)\n                    \n                    continuation.resume()\n                } catch {\n                    continuation.resume(throwing: error)\n                }\n            }\n        }\n    }\n    \n    /// 异步保存数据（带防抖）\n    func save(_ items: [T]) {\n        // 取消之前的保存任务\n        saveTask?.cancel()\n        \n        // 创建新的保存任务\n        saveTask = Task { [weak self] in\n            guard let self = self else { return }\n            \n            // 等待防抖延迟\n            try? await Task.sleep(nanoseconds: UInt64(self.saveDelay * 1_000_000_000))\n            \n            // 检查任务是否被取消\n            guard !Task.isCancelled else { return }\n            \n            // 执行保存\n            do {\n                try await self.saveImmediately(items)\n            } catch {\n                print(";
"  // 用于跟踪之前的域名\n    @State private var showCustomRunningDir = false  // 是否显示自定义运行目录输入框\n    @State private var availableSubdirs: [String] = []  // 可用的子目录列表\n    @State private var directoryPermissionStatus: (exists: Bool, writable: Bool, error: String?) = (false, false, nil)  // 目录权限状态\n    @State private var selectedWebServer: WebServerType = .nginx\n    @State private var selectedPHPVersion = " = "  // 用于跟踪之前的域名\n    @State private var showCustomRunningDir = false  // 是否显示自定义运行目录输入框\n    @State private var availableSubdirs: [String] = []  // 可用的子目录列表\n    @State private var directoryPermissionStatus: (exists: Bool, writable: Bool, error: String?) = (false, false, nil)  // 目录权限状态\n    @State private var selectedWebServer: WebServerType = .nginx\n    @State private var selectedPHPVersion = ";
")\n\n        // 2. 分开执行多条 SQL 语句\n        let sqls = [\n            " = ")\n\n        // 2. 分开执行多条 SQL 语句\n        let sqls = [\n            ";
" : database.note)\n                .font(.system(size: 11))\n                .foregroundColor(.secondary)\n                .lineLimit(1)\n                .frame(width: 100, alignment: .leading)\n\n            // 操作按钮\n            actionButtons\n                .frame(minWidth: 180, alignment: .leading)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 8)\n        .background(isHovered ? Color.gray.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n        .onHover { hovering in\n            isHovered = hovering\n        }\n    }\n\n    // 密码视图\n    private var passwordView: some View {\n        HStack(spacing: 4) {\n            Text(showPassword ? database.password : " = " : database.note)\n                .font(.system(size: 11))\n                .foregroundColor(.secondary)\n                .lineLimit(1)\n                .frame(width: 100, alignment: .leading)\n\n            // 操作按钮\n            actionButtons\n                .frame(minWidth: 180, alignment: .leading)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 8)\n        .background(isHovered ? Color.gray.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n        .onHover { hovering in\n            isHovered = hovering\n        }\n    }\n\n    // 密码视图\n    private var passwordView: some View {\n        HStack(spacing: 4) {\n            Text(showPassword ? database.password : ";
")\n        }\n    }\n\n    /// 只更新服务的运行状态（不重新检测安装）\n    private func updateServicesState(_ cachedServices: [ServiceStatus]) async -> [ServiceStatus] {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                // 预加载 brew services list\n                self.preloadBrewServicesCache()\n\n                var updatedServices = cachedServices\n                for i in 0..<updatedServices.count {\n                    if let brewName = updatedServices[i].brewName {\n                        updatedServices[i].state = self.getServiceState(brewName)\n                    }\n                }\n                continuation.resume(returning: updatedServices)\n            }\n        }\n    }\n\n    /// 从缓存中移除服务\n    func removeServiceFromCache(_ service: ServiceStatus) {\n        services.removeAll { $0.name == service.name }\n        saveServicesToCache()\n    }\n\n    /// 公开方法：手动刷新服务列表（强制重新检测安装）\n    func refreshServices() {\n        isLoadingServices = true\n        // 清除缓存，强制重新检测\n        AppState.cachedBrewServicesOutput = nil\n        AppState.cachedInstalledPackages = nil\n        Task {\n            await detectInstalledServicesAsync()\n        }\n    }\n\n    /// 异步检测已安装的服务\n    private func detectInstalledServicesAsync() async {\n        let detectedServices = await withCheckedContinuation { (continuation: CheckedContinuation<[ServiceStatus], Never>) in\n            DispatchQueue.global(qos: .userInitiated).async {\n                var services: [ServiceStatus] = []\n\n                // 预加载缓存\n                self.preloadInstalledPackagesCache()\n                self.preloadBrewServicesCache()\n\n                // 从 UserDefaults 读取用户配置的端口（线程安全）\n                let savedNginxPort = UserDefaults.standard.object(forKey: " = ")\n        }\n    }\n\n    /// 只更新服务的运行状态（不重新检测安装）\n    private func updateServicesState(_ cachedServices: [ServiceStatus]) async -> [ServiceStatus] {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                // 预加载 brew services list\n                self.preloadBrewServicesCache()\n\n                var updatedServices = cachedServices\n                for i in 0..<updatedServices.count {\n                    if let brewName = updatedServices[i].brewName {\n                        updatedServices[i].state = self.getServiceState(brewName)\n                    }\n                }\n                continuation.resume(returning: updatedServices)\n            }\n        }\n    }\n\n    /// 从缓存中移除服务\n    func removeServiceFromCache(_ service: ServiceStatus) {\n        services.removeAll { $0.name == service.name }\n        saveServicesToCache()\n    }\n\n    /// 公开方法：手动刷新服务列表（强制重新检测安装）\n    func refreshServices() {\n        isLoadingServices = true\n        // 清除缓存，强制重新检测\n        AppState.cachedBrewServicesOutput = nil\n        AppState.cachedInstalledPackages = nil\n        Task {\n            await detectInstalledServicesAsync()\n        }\n    }\n\n    /// 异步检测已安装的服务\n    private func detectInstalledServicesAsync() async {\n        let detectedServices = await withCheckedContinuation { (continuation: CheckedContinuation<[ServiceStatus], Never>) in\n            DispatchQueue.global(qos: .userInitiated).async {\n                var services: [ServiceStatus] = []\n\n                // 预加载缓存\n                self.preloadInstalledPackagesCache()\n                self.preloadBrewServicesCache()\n\n                // 从 UserDefaults 读取用户配置的端口（线程安全）\n                let savedNginxPort = UserDefaults.standard.object(forKey: ";
"  // 提示消息\n\n    enum BatchOperation {\n        case delete\n        case export\n    }\n\n    // 判断当前激活的标签页是否是本地数据库\n    private var isCurrentTabLocal: Bool {\n        if databaseManager.activeTabId == nil {\n            return true  // " = "  // 提示消息\n\n    enum BatchOperation {\n        case delete\n        case export\n    }\n\n    // 判断当前激活的标签页是否是本地数据库\n    private var isCurrentTabLocal: Bool {\n        if databaseManager.activeTabId == nil {\n            return true  // ";
": return apache\n        default: return [:]\n        }\n    }\n\n    /// 从字典生成分组配置 (用于UI显示)\n    static func getConfigs(for serviceName: String, version: String? = nil) -> [ConfigSection] {\n        let dict = getConfigDict(for: serviceName)\n        var sections: [String: [ConfigItem]] = [:]\n\n        // 按 section 分组，过滤版本\n        for (key, meta) in dict {\n            // 版本过滤\n            if let ver = version, !meta.isApplicable(for: ver) {\n                continue\n            }\n\n            let item = ConfigItem(\n                key: key,\n                value: meta.defaultValue,\n                description: meta.description,\n                unit: meta.unit,\n                defaultValue: meta.defaultValue,\n                suggestedValue: meta.suggestedValue,\n                isCommon: meta.isCommon\n            )\n            sections[meta.section, default: []].append(item)\n        }\n\n        // 定义分组顺序\n        let sectionOrder = getSectionOrder(for: serviceName)\n\n        return sectionOrder.compactMap { title in\n            guard let items = sections[title], !items.isEmpty else { return nil }\n            return ConfigSection(title: title, items: items.sorted { $0.key < $1.key })\n        }\n    }\n\n    /// 获取分组顺序\n    private static func getSectionOrder(for serviceName: String) -> [String] {\n        switch serviceName {\n        case " = ": return apache\n        default: return [:]\n        }\n    }\n\n    /// 从字典生成分组配置 (用于UI显示)\n    static func getConfigs(for serviceName: String, version: String? = nil) -> [ConfigSection] {\n        let dict = getConfigDict(for: serviceName)\n        var sections: [String: [ConfigItem]] = [:]\n\n        // 按 section 分组，过滤版本\n        for (key, meta) in dict {\n            // 版本过滤\n            if let ver = version, !meta.isApplicable(for: ver) {\n                continue\n            }\n\n            let item = ConfigItem(\n                key: key,\n                value: meta.defaultValue,\n                description: meta.description,\n                unit: meta.unit,\n                defaultValue: meta.defaultValue,\n                suggestedValue: meta.suggestedValue,\n                isCommon: meta.isCommon\n            )\n            sections[meta.section, default: []].append(item)\n        }\n\n        // 定义分组顺序\n        let sectionOrder = getSectionOrder(for: serviceName)\n\n        return sectionOrder.compactMap { title in\n            guard let items = sections[title], !items.isEmpty else { return nil }\n            return ConfigSection(title: title, items: items.sorted { $0.key < $1.key })\n        }\n    }\n\n    /// 获取分组顺序\n    private static func getSectionOrder(for serviceName: String) -> [String] {\n        switch serviceName {\n        case ";
")\n                        .foregroundColor(canSelect ? (isSelected ? .blue : .secondary) : .gray)\n                        .font(.system(size: 16))\n                }\n                .buttonStyle(.plain)\n                .disabled(!canSelect)\n\n                VStack(alignment: .leading, spacing: 4) {\n                    // 第一行：目录名和状态\n                    HStack(spacing: 8) {\n                        Image(systemName: " = ")\n                        .foregroundColor(canSelect ? (isSelected ? .blue : .secondary) : .gray)\n                        .font(.system(size: 16))\n                }\n                .buttonStyle(.plain)\n                .disabled(!canSelect)\n\n                VStack(alignment: .leading, spacing: 4) {\n                    // 第一行：目录名和状态\n                    HStack(spacing: 8) {\n                        Image(systemName: ";
") {\n                    customLogPath = nil\n                }\n                .buttonStyle(.bordered)\n                .controlSize(.regular)\n                .foregroundColor(.orange)\n            }\n\n            // 打开日志按钮\n            Button(" = ") {\n                    customLogPath = nil\n                }\n                .buttonStyle(.bordered)\n                .controlSize(.regular)\n                .foregroundColor(.orange)\n            }\n\n            // 打开日志按钮\n            Button(";
")\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    private var logContentView: some View {\n        ScrollView {\n            LazyVStack(alignment: .leading, spacing: 0) {\n                // 加载更多按钮\n                if hasMoreLogs && !logLines.isEmpty {\n                    loadMoreButton\n                }\n\n                // 日志行\n                ForEach(Array(logLines.enumerated()), id: \\.offset) { _, line in\n                    logLineView(line)\n                }\n\n                if isLoading {\n                    loadingView\n                }\n\n                if logLines.isEmpty && !isLoading {\n                    emptyView\n                }\n            }\n            .padding()\n        }\n    }\n\n    private var loadMoreButton: some View {\n        Button(action: loadMoreLogs) {\n            HStack {\n                Image(systemName: " = ")\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    private var logContentView: some View {\n        ScrollView {\n            LazyVStack(alignment: .leading, spacing: 0) {\n                // 加载更多按钮\n                if hasMoreLogs && !logLines.isEmpty {\n                    loadMoreButton\n                }\n\n                // 日志行\n                ForEach(Array(logLines.enumerated()), id: \\.offset) { _, line in\n                    logLineView(line)\n                }\n\n                if isLoading {\n                    loadingView\n                }\n\n                if logLines.isEmpty && !isLoading {\n                    emptyView\n                }\n            }\n            .padding()\n        }\n    }\n\n    private var loadMoreButton: some View {\n        Button(action: loadMoreLogs) {\n            HStack {\n                Image(systemName: ";
")\n                }\n            }\n        }\n    }\n\n    // 导出数据库\n    private func exportDatabase() {\n        let savePanel = NSSavePanel()\n        savePanel.allowedContentTypes = [.init(filenameExtension: " = ")\n                }\n            }\n        }\n    }\n\n    // 导出数据库\n    private func exportDatabase() {\n        let savePanel = NSSavePanel()\n        savePanel.allowedContentTypes = [.init(filenameExtension: ";
"\n            ]\n        default:\n            configPaths = []\n        }\n\n        // 查找第一个存在的配置文件\n        for configPath in configPaths {\n            if FileManager.default.fileExists(atPath: configPath) {\n                // 使用 open -t 命令用文本编辑器打开\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: " = "\n            ]\n        default:\n            configPaths = []\n        }\n\n        // 查找第一个存在的配置文件\n        for configPath in configPaths {\n            if FileManager.default.fileExists(atPath: configPath) {\n                // 使用 open -t 命令用文本编辑器打开\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: ";
"强烈建议迁移 \\(migration.serviceName) 配置文件" = "强烈建议迁移 \\(migration.serviceName) 配置文件";
"]\n\n                let pipe = Pipe()\n                process.standardOutput = pipe\n\n                // 添加超时处理\n                var hasResumed = false\n                let timeoutWorkItem = DispatchWorkItem {\n                    if !hasResumed {\n                        hasResumed = true\n                        process.terminate()\n                        continuation.resume(returning: false)\n                    }\n                }\n\n                DispatchQueue.global().asyncAfter(deadline: .now() + 10, execute: timeoutWorkItem)\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    timeoutWorkItem.cancel()\n                    if !hasResumed {\n                        hasResumed = true\n                        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                        if let output = String(data: data, encoding: .utf8) {\n                            let pattern = " = "]\n\n                let pipe = Pipe()\n                process.standardOutput = pipe\n\n                // 添加超时处理\n                var hasResumed = false\n                let timeoutWorkItem = DispatchWorkItem {\n                    if !hasResumed {\n                        hasResumed = true\n                        process.terminate()\n                        continuation.resume(returning: false)\n                    }\n                }\n\n                DispatchQueue.global().asyncAfter(deadline: .now() + 10, execute: timeoutWorkItem)\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    timeoutWorkItem.cancel()\n                    if !hasResumed {\n                        hasResumed = true\n                        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                        if let output = String(data: data, encoding: .utf8) {\n                            let pattern = ";
"\n        主机: \\(host)\n        端口: \\(port)\n        用户名: root\n        密码: \\(password)\n        " = "\n        主机: \\(host)\n        端口: \\(port)\n        用户名: root\n        密码: \\(password)\n        ";
"回滚后二次校验失败：\\(error.localizedDescription)" = "回滚后二次校验失败：\\(error.localizedDescription)";
")\n                            .frame(width: 20)\n                        Text(LocalizedStringKey(item.rawValue))\n                            .font(.system(size: 13, weight: selectedSidebarItem == item ? .medium : .regular))\n                        Spacer()\n                    }\n                    .foregroundColor(selectedSidebarItem == item ? .blue : .primary)\n                    .padding(.horizontal, 12)\n                    .padding(.vertical, 10)\n                    .background(selectedSidebarItem == item ? Color.blue.opacity(0.1) : Color.clear)\n                    .cornerRadius(AppTokens.Radius.medium)\n                }\n                .buttonStyle(.plain)\n                .padding(.horizontal, 8)\n                .padding(.vertical, 2)\n            }\n\n            Spacer()\n        }\n        .frame(width: 140)\n        .padding(.top, 12)\n        .background(Color.white.opacity(0.05))\n    }\n    \n    // MARK: - V2 标题栏\n    private var titleBarV2: some View {\n        HStack {\n            VStack(alignment: .leading, spacing: 4) {\n                Text(LocalizedStringKey(" = ")\n                            .frame(width: 20)\n                        Text(LocalizedStringKey(item.rawValue))\n                            .font(.system(size: 13, weight: selectedSidebarItem == item ? .medium : .regular))\n                        Spacer()\n                    }\n                    .foregroundColor(selectedSidebarItem == item ? .blue : .primary)\n                    .padding(.horizontal, 12)\n                    .padding(.vertical, 10)\n                    .background(selectedSidebarItem == item ? Color.blue.opacity(0.1) : Color.clear)\n                    .cornerRadius(AppTokens.Radius.medium)\n                }\n                .buttonStyle(.plain)\n                .padding(.horizontal, 8)\n                .padding(.vertical, 2)\n            }\n\n            Spacer()\n        }\n        .frame(width: 140)\n        .padding(.top, 12)\n        .background(Color.white.opacity(0.05))\n    }\n    \n    // MARK: - V2 标题栏\n    private var titleBarV2: some View {\n        HStack {\n            VStack(alignment: .leading, spacing: 4) {\n                Text(LocalizedStringKey(";
"]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            if process.terminationStatus == 0 {\n                let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                if let path = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),\n                   !path.isEmpty {\n                    return path\n                }\n            }\n        } catch {\n            return nil\n        }\n\n        return nil\n    }\n\n    // MARK: - 执行 Brew 安装命令\n    nonisolated private func runBrewInstall() throws {\n        guard let brewPath = getBrewPath() else {\n            throw InstallationError.homebrewNotInstalled\n        }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [" = "]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            if process.terminationStatus == 0 {\n                let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                if let path = String(data: data, encoding: .utf8)?.trimmingCharacters(in: .whitespacesAndNewlines),\n                   !path.isEmpty {\n                    return path\n                }\n            }\n        } catch {\n            return nil\n        }\n\n        return nil\n    }\n\n    // MARK: - 执行 Brew 安装命令\n    nonisolated private func runBrewInstall() throws {\n        guard let brewPath = getBrewPath() else {\n            throw InstallationError.homebrewNotInstalled\n        }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [";
") {\n                if trimmed.count < 80 {\n                    progress.currentStep = trimmed\n                }\n                break\n            }\n        }\n\n        installProgressMap[softwareId] = progress\n\n        // 同步更新 installingVersions\n        if var installing = installingVersions[softwareId] {\n            installing.progress = progress\n            installingVersions[softwareId] = installing\n        }\n    }\n\n    // 切换进度详情展开状态\n    func toggleProgressExpanded(for softwareId: String) {\n        if var progress = installProgressMap[softwareId] {\n            progress.isExpanded.toggle()\n            installProgressMap[softwareId] = progress\n        }\n    }\n\n    // 清除进度信息\n    func clearProgress(for softwareId: String) {\n        installProgressMap.removeValue(forKey: softwareId)\n    }\n\n    // 清除失败的安装记录\n    func clearFailedInstall(for softwareId: String) {\n        installingVersions.removeValue(forKey: softwareId)\n        installProgressMap.removeValue(forKey: softwareId)\n    }\n\n    // 更新安装失败状态\n    private func updateInstallFailure(softwareId: String, error: String, detail: String?) {\n        installProgressMap[softwareId]?.phase = .failed\n        installProgressMap[softwareId]?.currentStep = error\n        installProgressMap[softwareId]?.errorMessage = error\n        installProgressMap[softwareId]?.errorDetail = detail\n\n        if var installing = installingVersions[softwareId] {\n            installing.progress.phase = .failed\n            installing.progress.currentStep = error\n            installing.progress.errorMessage = error\n            installing.progress.errorDetail = detail\n            installingVersions[softwareId] = installing\n\n            // 保存到错误日志\n            ErrorLogManager.shared.addLog(\n                softwareId: softwareId,\n                softwareName: installing.name,\n                version: installing.version,\n                operation: " = ") {\n                if trimmed.count < 80 {\n                    progress.currentStep = trimmed\n                }\n                break\n            }\n        }\n\n        installProgressMap[softwareId] = progress\n\n        // 同步更新 installingVersions\n        if var installing = installingVersions[softwareId] {\n            installing.progress = progress\n            installingVersions[softwareId] = installing\n        }\n    }\n\n    // 切换进度详情展开状态\n    func toggleProgressExpanded(for softwareId: String) {\n        if var progress = installProgressMap[softwareId] {\n            progress.isExpanded.toggle()\n            installProgressMap[softwareId] = progress\n        }\n    }\n\n    // 清除进度信息\n    func clearProgress(for softwareId: String) {\n        installProgressMap.removeValue(forKey: softwareId)\n    }\n\n    // 清除失败的安装记录\n    func clearFailedInstall(for softwareId: String) {\n        installingVersions.removeValue(forKey: softwareId)\n        installProgressMap.removeValue(forKey: softwareId)\n    }\n\n    // 更新安装失败状态\n    private func updateInstallFailure(softwareId: String, error: String, detail: String?) {\n        installProgressMap[softwareId]?.phase = .failed\n        installProgressMap[softwareId]?.currentStep = error\n        installProgressMap[softwareId]?.errorMessage = error\n        installProgressMap[softwareId]?.errorDetail = detail\n\n        if var installing = installingVersions[softwareId] {\n            installing.progress.phase = .failed\n            installing.progress.currentStep = error\n            installing.progress.errorMessage = error\n            installing.progress.errorDetail = detail\n            installingVersions[softwareId] = installing\n\n            // 保存到错误日志\n            ErrorLogManager.shared.addLog(\n                softwareId: softwareId,\n                softwareName: installing.name,\n                version: installing.version,\n                operation: ";
"  // 支持 MySQL 8.0+ 认证\n        ]\n        if !password.isEmpty {\n            arguments.append(" = "  // 支持 MySQL 8.0+ 认证\n        ]\n        if !password.isEmpty {\n            arguments.append(";
")\n                .font(.system(size: 11))\n                .foregroundColor(.secondary)\n                .frame(minWidth: 150, alignment: .leading)\n\n            // 状态\n            HStack(spacing: 4) {\n                Circle()\n                    .fill(entry.isEnabled ? Color.green : Color.gray)\n                    .frame(width: 8, height: 8)\n                Text(entry.isEnabled ? LocalizedStringKey(" = ")\n                .font(.system(size: 11))\n                .foregroundColor(.secondary)\n                .frame(minWidth: 150, alignment: .leading)\n\n            // 状态\n            HStack(spacing: 4) {\n                Circle()\n                    .fill(entry.isEnabled ? Color.green : Color.gray)\n                    .frame(width: 8, height: 8)\n                Text(entry.isEnabled ? LocalizedStringKey(";
", isOn: Binding(\n                get: { isSelected },\n                set: { _ in onToggleSelection() }\n            ))\n            .labelsHidden()\n            .frame(width: 30)\n\n            // 数据库名\n            Text(database.name)\n                .font(.system(size: 12))\n                .frame(width: 120, alignment: .leading)\n\n            // 用户名\n            Text(database.username)\n                .font(.system(size: 12))\n                .frame(width: 100, alignment: .leading)\n\n            // 密码\n            passwordView\n                .frame(width: 100, alignment: .leading)\n\n            // 备份\n            backupView\n                .frame(width: 120, alignment: .leading)\n\n            // 数据库位置\n            Text(database.type == .local ? " = ", isOn: Binding(\n                get: { isSelected },\n                set: { _ in onToggleSelection() }\n            ))\n            .labelsHidden()\n            .frame(width: 30)\n\n            // 数据库名\n            Text(database.name)\n                .font(.system(size: 12))\n                .frame(width: 120, alignment: .leading)\n\n            // 用户名\n            Text(database.username)\n                .font(.system(size: 12))\n                .frame(width: 100, alignment: .leading)\n\n            // 密码\n            passwordView\n                .frame(width: 100, alignment: .leading)\n\n            // 备份\n            backupView\n                .frame(width: 120, alignment: .leading)\n\n            // 数据库位置\n            Text(database.type == .local ? ";
"))\n                .foregroundColor(.secondary)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n    }\n\n    // 错误视图\n    private func errorView(_ message: String) -> some View {\n        VStack(spacing: 16) {\n            Image(systemName: " = "))\n                .foregroundColor(.secondary)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n    }\n\n    // 错误视图\n    private func errorView(_ message: String) -> some View {\n        VStack(spacing: 16) {\n            Image(systemName: ";
",\n                        isDirectory: true\n                    ),\n                    // 工作空间的 vhosts 目录\n                    ConfigFileLocation(\n                        workspacePath: " = ",\n                        isDirectory: true\n                    ),\n                    // 工作空间的 vhosts 目录\n                    ConfigFileLocation(\n                        workspacePath: ";
"\n\n        // 检查 mkcert 是否可用\n        if await isMkcertAvailable() {\n            print(" = "\n\n        // 检查 mkcert 是否可用\n        if await isMkcertAvailable() {\n            print(";
"✅ openssl 生成自签名证书成功: \\(certPath)" = "✅ openssl 生成自签名证书成功: \\(certPath)";
"\n                # ============================================\n                # 🔄 Nginx 反向代理配置 (Apache 后端)\n                # 此配置将请求转发到 Apache 后端服务器\n                # 前端端口: 80/443 (Nginx)\n                # 后端端口: \\(backendHttpPort)/\\(backendHttpsPort) (Apache)\n                # ============================================\n\n                # HTTP 服务器块 - 反向代理到 Apache\n                server {\n                    listen 80;\n                    server_name \\(serverNames);\n                \\(authBasicBlocks)\n                    location / {\n                        proxy_pass http://127.0.0.1:\\(backendHttpPort);\n                        proxy_set_header Host $host;\n                        proxy_set_header X-Real-IP $remote_addr;\n                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                        proxy_set_header X-Forwarded-Proto $scheme;\n                        proxy_set_header X-Forwarded-Host $host;\n                        proxy_set_header X-Forwarded-Port $server_port;\n                    }\n                }\n\n                # HTTPS 服务器块 - 反向代理到 Apache\n                server {\n                    listen 443 ssl;\n                    server_name \\(serverNames);\n\n                    # SSL 证书配置\n                    ssl_certificate \\(certPath);\n                    ssl_certificate_key \\(keyPath);\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                \\(authBasicBlocks)\n                    location / {\n                        proxy_pass \\(site.sslMode == .nginxTermination ? " = "\n                # ============================================\n                # 🔄 Nginx 反向代理配置 (Apache 后端)\n                # 此配置将请求转发到 Apache 后端服务器\n                # 前端端口: 80/443 (Nginx)\n                # 后端端口: \\(backendHttpPort)/\\(backendHttpsPort) (Apache)\n                # ============================================\n\n                # HTTP 服务器块 - 反向代理到 Apache\n                server {\n                    listen 80;\n                    server_name \\(serverNames);\n                \\(authBasicBlocks)\n                    location / {\n                        proxy_pass http://127.0.0.1:\\(backendHttpPort);\n                        proxy_set_header Host $host;\n                        proxy_set_header X-Real-IP $remote_addr;\n                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n                        proxy_set_header X-Forwarded-Proto $scheme;\n                        proxy_set_header X-Forwarded-Host $host;\n                        proxy_set_header X-Forwarded-Port $server_port;\n                    }\n                }\n\n                # HTTPS 服务器块 - 反向代理到 Apache\n                server {\n                    listen 443 ssl;\n                    server_name \\(serverNames);\n\n                    # SSL 证书配置\n                    ssl_certificate \\(certPath);\n                    ssl_certificate_key \\(keyPath);\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                \\(authBasicBlocks)\n                    location / {\n                        proxy_pass \\(site.sslMode == .nginxTermination ? ";
"]\n        }\n    }\n\n    private struct SiteConfigArtifact: Hashable {\n        let service: ManagedWebService\n        let path: String\n    }\n\n    private struct SiteConfigSnapshot {\n        let artifact: SiteConfigArtifact\n        let backup: ConfigBackup?\n    }\n\n    init() {\n        // 初始化存储管理器\n        storage = JSONStorage<Site>(fileURL: sitesFileURL, backupURL: sitesBackupURL, saveDelay: 0.5)\n        loadSites()\n    }\n\n    private func managedService(for webServer: WebServerType) -> ManagedWebService {\n        switch webServer {\n        case .nginx:\n            return .nginx\n        case .apache:\n            return .apache\n        }\n    }\n\n    private func webServerDisplayName(_ webServer: WebServerType) -> String {\n        managedService(for: webServer).displayName\n    }\n\n    private func requiredWebServers(for webServer: WebServerType) -> [WebServerType] {\n        switch webServer {\n        case .nginx:\n            return [.nginx]\n        case .apache:\n            return [.nginx, .apache]\n        }\n    }\n\n    private func uniqueWebServers(_ webServers: [WebServerType]) -> [WebServerType] {\n        var seen = Set<WebServerType>()\n        return webServers.filter { seen.insert($0).inserted }\n    }\n\n    private func runtimeStatus(for webServer: WebServerType) -> (installed: Bool, running: Bool) {\n        if let checkFunction = checkWebServerRunning {\n            return checkFunction(webServer)\n        }\n\n        let installed = installedWebServers.contains(webServer)\n        return (installed: installed, running: installed)\n    }\n\n    private func allRequiredServicesRunning(for webServer: WebServerType) -> Bool {\n        requiredWebServers(for: webServer).allSatisfy { service in\n            let status = runtimeStatus(for: service)\n            return status.installed && status.running\n        }\n    }\n\n    private func ensureRequiredWebServers(\n        for webServer: WebServerType,\n        operation: String,\n        requireRunning: Bool\n    ) throws {\n        let requiredServices = requiredWebServers(for: webServer)\n        var missingServices: [String] = []\n        var stoppedServices: [String] = []\n\n        for service in requiredServices {\n            let status = runtimeStatus(for: service)\n            let displayName = webServerDisplayName(service)\n\n            if !status.installed {\n                missingServices.append(displayName)\n            } else if requireRunning && !status.running {\n                stoppedServices.append(displayName)\n            }\n        }\n\n        if !missingServices.isEmpty {\n            let prefix = webServer == .apache ? " = "]\n        }\n    }\n\n    private struct SiteConfigArtifact: Hashable {\n        let service: ManagedWebService\n        let path: String\n    }\n\n    private struct SiteConfigSnapshot {\n        let artifact: SiteConfigArtifact\n        let backup: ConfigBackup?\n    }\n\n    init() {\n        // 初始化存储管理器\n        storage = JSONStorage<Site>(fileURL: sitesFileURL, backupURL: sitesBackupURL, saveDelay: 0.5)\n        loadSites()\n    }\n\n    private func managedService(for webServer: WebServerType) -> ManagedWebService {\n        switch webServer {\n        case .nginx:\n            return .nginx\n        case .apache:\n            return .apache\n        }\n    }\n\n    private func webServerDisplayName(_ webServer: WebServerType) -> String {\n        managedService(for: webServer).displayName\n    }\n\n    private func requiredWebServers(for webServer: WebServerType) -> [WebServerType] {\n        switch webServer {\n        case .nginx:\n            return [.nginx]\n        case .apache:\n            return [.nginx, .apache]\n        }\n    }\n\n    private func uniqueWebServers(_ webServers: [WebServerType]) -> [WebServerType] {\n        var seen = Set<WebServerType>()\n        return webServers.filter { seen.insert($0).inserted }\n    }\n\n    private func runtimeStatus(for webServer: WebServerType) -> (installed: Bool, running: Bool) {\n        if let checkFunction = checkWebServerRunning {\n            return checkFunction(webServer)\n        }\n\n        let installed = installedWebServers.contains(webServer)\n        return (installed: installed, running: installed)\n    }\n\n    private func allRequiredServicesRunning(for webServer: WebServerType) -> Bool {\n        requiredWebServers(for: webServer).allSatisfy { service in\n            let status = runtimeStatus(for: service)\n            return status.installed && status.running\n        }\n    }\n\n    private func ensureRequiredWebServers(\n        for webServer: WebServerType,\n        operation: String,\n        requireRunning: Bool\n    ) throws {\n        let requiredServices = requiredWebServers(for: webServer)\n        var missingServices: [String] = []\n        var stoppedServices: [String] = []\n\n        for service in requiredServices {\n            let status = runtimeStatus(for: service)\n            let displayName = webServerDisplayName(service)\n\n            if !status.installed {\n                missingServices.append(displayName)\n            } else if requireRunning && !status.running {\n                stoppedServices.append(displayName)\n            }\n        }\n\n        if !missingServices.isEmpty {\n            let prefix = webServer == .apache ? ";
"\\(outputCharCount) 字符" = "\\(outputCharCount) 字符";
"\n                }\n\n                // 在 appState.services 中查找服务\n                if let service = appState.services.first(where: {\n                    let name = $0.name.lowercased()\n                    return name == serviceName.lowercased() ||\n                           (webServer == .apache && (name == " = "\n                }\n\n                // 在 appState.services 中查找服务\n                if let service = appState.services.first(where: {\n                    let name = $0.name.lowercased()\n                    return name == serviceName.lowercased() ||\n                           (webServer == .apache && (name == ";
", text: $host)\n                        .macInput()\n                }\n\n                // 新密码\n                HStack {\n                    Text(" = ", text: $host)\n                        .macInput()\n                }\n\n                // 新密码\n                HStack {\n                    Text(";
"点击\\" = "点击\\";
"\n        }\n        return nil\n    }\n\n    // 获取服务图标字母和背景颜色（备用方案）\n    private var serviceIconInfo: (letter: String, color: Color) {\n        let name = service.name.lowercased()\n        if name.contains(" = "\n        }\n        return nil\n    }\n\n    // 获取服务图标字母和背景颜色（备用方案）\n    private var serviceIconInfo: (letter: String, color: Color) {\n        let name = service.name.lowercased()\n        if name.contains(";
"[VhostScan] 日志文件已创建: \\(logPath)" = "[VhostScan] 日志文件已创建: \\(logPath)";
")\n        let level = levelComponents.last ?? levelStr\n\n        // 解析Apache时间戳\n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = " = ")\n        let level = levelComponents.last ?? levelStr\n\n        // 解析Apache时间戳\n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = ";
"\n    ]\n\n    /// 查找第一个存在的 mysql 客户端路径\n    static func findMysql() -> String? {\n        return mysql.first { FileManager.default.fileExists(atPath: $0) }\n    }\n\n    /// 查找第一个存在的 mysqldump 客户端路径\n    static func findMysqldump() -> String? {\n        return mysqldump.first { FileManager.default.fileExists(atPath: $0) }\n    }\n}\n\n// MARK: - 客户端兼容性建议（简化版）\nstruct ClientCompatibilityAdvice {\n    let isCompatible: Bool\n    let message: String\n}\n\n@MainActor\nclass DatabaseManager: ObservableObject {\n    @Published var databases: [Database] = []\n    @Published var localMySQLDatabases: [String] = []  // 本地 MySQL 中的真实数据库列表\n    @Published var isLoading = false\n    @Published var errorMessage: String?\n\n    // MARK: - 标签页状态管理（切换界面时保持状态）\n    @Published var openedDatabaseTabs: [OpenedDatabaseTab] = []\n    @Published var activeTabId: String? = nil  // 当前激活的标签页ID，nil表示显示全部列表\n\n    private let databasesFilePath: URL = {\n        let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!\n        let folderURL = appSupport.appendingPathComponent(" = "\n    ]\n\n    /// 查找第一个存在的 mysql 客户端路径\n    static func findMysql() -> String? {\n        return mysql.first { FileManager.default.fileExists(atPath: $0) }\n    }\n\n    /// 查找第一个存在的 mysqldump 客户端路径\n    static func findMysqldump() -> String? {\n        return mysqldump.first { FileManager.default.fileExists(atPath: $0) }\n    }\n}\n\n// MARK: - 客户端兼容性建议（简化版）\nstruct ClientCompatibilityAdvice {\n    let isCompatible: Bool\n    let message: String\n}\n\n@MainActor\nclass DatabaseManager: ObservableObject {\n    @Published var databases: [Database] = []\n    @Published var localMySQLDatabases: [String] = []  // 本地 MySQL 中的真实数据库列表\n    @Published var isLoading = false\n    @Published var errorMessage: String?\n\n    // MARK: - 标签页状态管理（切换界面时保持状态）\n    @Published var openedDatabaseTabs: [OpenedDatabaseTab] = []\n    @Published var activeTabId: String? = nil  // 当前激活的标签页ID，nil表示显示全部列表\n\n    private let databasesFilePath: URL = {\n        let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!\n        let folderURL = appSupport.appendingPathComponent(";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 侧边栏菜单\n    private var sidebarMenu: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            ForEach(SettingsTab.allCases, id: \\.self) { tab in\n                Button(action: { selectedTab = tab }) {\n                    HStack {\n                        Text(LocalizedStringKey(tab.rawValue))\n                            .font(.system(size: 13))\n                        Spacer()\n                    }\n                    .padding(.horizontal, 16)\n                    .padding(.vertical, 12)\n                    .background(selectedTab == tab ? Color.accentColor.opacity(AppTokens.Opacity.hover) : Color.clear)\n                    .foregroundColor(selectedTab == tab ? .accentColor : .primary)\n                }\n                .buttonStyle(.plain)\n            }\n            Spacer()\n        }\n        .padding(.top, 12)\n        .background(Color(NSColor.controlBackgroundColor).opacity(AppTokens.Opacity.disabled))\n    }\n\n    // 设置内容\n    @ViewBuilder\n    private var settingsContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                switch selectedTab {\n                case .general:\n                    generalSettings\n                case .rewrite:\n                    rewriteSettings\n                case .ssl:\n                    sslSettings\n                case .php:\n                    phpSettings\n                case .database:\n                    databaseSettings\n                case .cache:\n                    cacheSettings\n                }\n            }\n            .padding()\n        }\n    }\n\n    // 常规设置\n    private var generalSettings: some View {\n        VStack(alignment: .leading, spacing: 16) {\n            Text(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 侧边栏菜单\n    private var sidebarMenu: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            ForEach(SettingsTab.allCases, id: \\.self) { tab in\n                Button(action: { selectedTab = tab }) {\n                    HStack {\n                        Text(LocalizedStringKey(tab.rawValue))\n                            .font(.system(size: 13))\n                        Spacer()\n                    }\n                    .padding(.horizontal, 16)\n                    .padding(.vertical, 12)\n                    .background(selectedTab == tab ? Color.accentColor.opacity(AppTokens.Opacity.hover) : Color.clear)\n                    .foregroundColor(selectedTab == tab ? .accentColor : .primary)\n                }\n                .buttonStyle(.plain)\n            }\n            Spacer()\n        }\n        .padding(.top, 12)\n        .background(Color(NSColor.controlBackgroundColor).opacity(AppTokens.Opacity.disabled))\n    }\n\n    // 设置内容\n    @ViewBuilder\n    private var settingsContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                switch selectedTab {\n                case .general:\n                    generalSettings\n                case .rewrite:\n                    rewriteSettings\n                case .ssl:\n                    sslSettings\n                case .php:\n                    phpSettings\n                case .database:\n                    databaseSettings\n                case .cache:\n                    cacheSettings\n                }\n            }\n            .padding()\n        }\n    }\n\n    // 常规设置\n    private var generalSettings: some View {\n        VStack(alignment: .leading, spacing: 16) {\n            Text(";
")\n                    .font(.system(size: 13))\n                    .foregroundColor(.secondary)\n            }\n            .padding(24)\n        }\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // MARK: - 辅助方法\n    private func startScan() {\n        isScanning = true\n\n        Task {\n            do {\n                // 使用SecurityAnalysisService进行实际扫描\n                let results = try await SecurityAnalysisService.shared.scanLogs(for: site)\n                scanResults = results\n            } catch {\n                print(" = ")\n                    .font(.system(size: 13))\n                    .foregroundColor(.secondary)\n            }\n            .padding(24)\n        }\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // MARK: - 辅助方法\n    private func startScan() {\n        isScanning = true\n\n        Task {\n            do {\n                // 使用SecurityAnalysisService进行实际扫描\n                let results = try await SecurityAnalysisService.shared.scanLogs(for: site)\n                scanResults = results\n            } catch {\n                print(";
"\n                }\n\n                // 使用较低的优先级（100+）确保排在预定义版本之后\n                additionalVersions.append((displayName, packageName, 9000, 100 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// 动态检测所有已安装的 MySQL 版本（不在预定义列表中的）\n    private nonisolated func detectAdditionalMySQLVersions(\n        predefinedBrewNames: Set<String>\n    ) -> [(name: String, brewName: String, port: Int, priority: Int)] {\n        guard let packages = AppState.cachedInstalledPackages else { return [] }\n\n        var additionalVersions: [(name: String, brewName: String, port: Int, priority: Int)] = []\n\n        for (packageName, _) in packages {\n            // 匹配 mysql 或 mysql@x.x 格式\n            if packageName == " = "\n                }\n\n                // 使用较低的优先级（100+）确保排在预定义版本之后\n                additionalVersions.append((displayName, packageName, 9000, 100 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// 动态检测所有已安装的 MySQL 版本（不在预定义列表中的）\n    private nonisolated func detectAdditionalMySQLVersions(\n        predefinedBrewNames: Set<String>\n    ) -> [(name: String, brewName: String, port: Int, priority: Int)] {\n        guard let packages = AppState.cachedInstalledPackages else { return [] }\n\n        var additionalVersions: [(name: String, brewName: String, port: Int, priority: Int)] = []\n\n        for (packageName, _) in packages {\n            // 匹配 mysql 或 mysql@x.x 格式\n            if packageName == ";
")\n                            .foregroundColor(.secondary)\n                            .font(.system(size: 14))\n                    }\n                    .buttonStyle(.plain)\n                }\n            }\n            .padding(.horizontal, 12)\n            .padding(.vertical, 8)\n            .glass(.input)\n            .frame(width: 300)\n\n            Spacer()\n\n            // 显示IP归属地信息开关\n            Toggle(isOn: $showIPInfo) {\n                Text(LocalizedStringKey(" = ")\n                            .foregroundColor(.secondary)\n                            .font(.system(size: 14))\n                    }\n                    .buttonStyle(.plain)\n                }\n            }\n            .padding(.horizontal, 12)\n            .padding(.vertical, 8)\n            .glass(.input)\n            .frame(width: 300)\n\n            Spacer()\n\n            // 显示IP归属地信息开关\n            Toggle(isOn: $showIPInfo) {\n                Text(LocalizedStringKey(";
"\n        }\n\n        // 安装 Apache\n        let success = await runBrewCommandWithProgress(\n            brewPath, arguments: [" = "\n        }\n\n        // 安装 Apache\n        let success = await runBrewCommandWithProgress(\n            brewPath, arguments: [";
"无法执行配置检测: \\(error.localizedDescription)" = "无法执行配置检测: \\(error.localizedDescription)";
"\n    }\n\n    // MARK: - 配置迁移\n\n    /// 配置迁移信息\n    struct ConfigMigrationInfo {\n        let serviceName: String\n        let sourceFile: String\n        let targetFile: String\n        let targetDirectory: String\n    }\n\n    /// 检查是否需要迁移配置文件\n    func checkConfigMigration(serviceName: String, version: String? = nil) -> ConfigMigrationInfo? {\n        guard let config = workspaceManager.config, config.isInitialized else {\n            return nil\n        }\n\n        let (workspacePath, homebrewPath) = getConfigPaths(serviceName: serviceName, version: version, config: config)\n\n        // 工作空间路径不存在，但 Homebrew 路径存在 -> 需要迁移\n        if !FileManager.default.fileExists(atPath: workspacePath) &&\n           FileManager.default.fileExists(atPath: homebrewPath) {\n            let targetDir = (workspacePath as NSString).deletingLastPathComponent\n            return ConfigMigrationInfo(\n                serviceName: serviceName,\n                sourceFile: homebrewPath,\n                targetFile: workspacePath,\n                targetDirectory: targetDir\n            )\n        }\n        return nil\n    }\n\n    /// 获取配置文件路径对（工作空间路径，Homebrew路径）\n    private func getConfigPaths(serviceName: String, version: String?, config: WorkspaceConfig) -> (String, String) {\n        switch serviceName.lowercased() {\n        case " = "\n    }\n\n    // MARK: - 配置迁移\n\n    /// 配置迁移信息\n    struct ConfigMigrationInfo {\n        let serviceName: String\n        let sourceFile: String\n        let targetFile: String\n        let targetDirectory: String\n    }\n\n    /// 检查是否需要迁移配置文件\n    func checkConfigMigration(serviceName: String, version: String? = nil) -> ConfigMigrationInfo? {\n        guard let config = workspaceManager.config, config.isInitialized else {\n            return nil\n        }\n\n        let (workspacePath, homebrewPath) = getConfigPaths(serviceName: serviceName, version: version, config: config)\n\n        // 工作空间路径不存在，但 Homebrew 路径存在 -> 需要迁移\n        if !FileManager.default.fileExists(atPath: workspacePath) &&\n           FileManager.default.fileExists(atPath: homebrewPath) {\n            let targetDir = (workspacePath as NSString).deletingLastPathComponent\n            return ConfigMigrationInfo(\n                serviceName: serviceName,\n                sourceFile: homebrewPath,\n                targetFile: workspacePath,\n                targetDirectory: targetDir\n            )\n        }\n        return nil\n    }\n\n    /// 获取配置文件路径对（工作空间路径，Homebrew路径）\n    private func getConfigPaths(serviceName: String, version: String?, config: WorkspaceConfig) -> (String, String) {\n        switch serviceName.lowercased() {\n        case ";
"✅ 已删除配置文件: \\(artifact.path)" = "✅ 已删除配置文件: \\(artifact.path)";
")\n        homebrewStatus = isInstalled ? .installed : .notInstalled\n    }\n\n    // MARK: - 内置工具路径\n\n    /// 获取内置的 mkcert 路径\n    func getBundledMkcertPath() -> String? {\n        guard let bundlePath = Bundle.main.resourcePath else {\n            return nil\n        }\n\n        // 根据架构选择对应的二进制文件\n        #if arch(arm64)\n        let mkcertPath = " = ")\n        homebrewStatus = isInstalled ? .installed : .notInstalled\n    }\n\n    // MARK: - 内置工具路径\n\n    /// 获取内置的 mkcert 路径\n    func getBundledMkcertPath() -> String? {\n        guard let bundlePath = Bundle.main.resourcePath else {\n            return nil\n        }\n\n        // 根据架构选择对应的二进制文件\n        #if arch(arm64)\n        let mkcertPath = ";
")\n        }\n\n        return PortSyncResult(\n            success: failedFiles.isEmpty && errors.isEmpty,\n            updatedFiles: updatedFiles,\n            failedFiles: failedFiles,\n            errors: errors\n        )\n    }\n\n    /// 同步单个配置文件\n    private func syncFile(\n        path: String,\n        patterns: [PortReferencePattern],\n        newPort: Int\n    ) async -> (success: Bool, error: String?) {\n        do {\n            // 读取文件内容\n            guard let content = try? String(contentsOfFile: path, encoding: .utf8) else {\n                return (false, " = ")\n        }\n\n        return PortSyncResult(\n            success: failedFiles.isEmpty && errors.isEmpty,\n            updatedFiles: updatedFiles,\n            failedFiles: failedFiles,\n            errors: errors\n        )\n    }\n\n    /// 同步单个配置文件\n    private func syncFile(\n        path: String,\n        patterns: [PortReferencePattern],\n        newPort: Int\n    ) async -> (success: Bool, error: String?) {\n        do {\n            // 读取文件内容\n            guard let content = try? String(contentsOfFile: path, encoding: .utf8) else {\n                return (false, ";
"配置解析错误: \\(message)" = "配置解析错误: \\(message)";
")\n        }\n    }\n\n    // PHP 端口配置 (key: PHP版本如 " = ")\n        }\n    }\n\n    // PHP 端口配置 (key: PHP版本如 ";
"\n    }\n\n    /// 获取 Nginx 站点配置目录\n    func getNginxVhostsPath() -> String {\n        if let config = config, config.isInitialized {\n            return config.nginxVhostsPath\n        }\n        return " = "\n    }\n\n    /// 获取 Nginx 站点配置目录\n    func getNginxVhostsPath() -> String {\n        if let config = config, config.isInitialized {\n            return config.nginxVhostsPath\n        }\n        return ";
") {\n                continue\n            }\n\n            // 检测 RewriteEngine\n            if trimmed.contains(" = ") {\n                continue\n            }\n\n            // 检测 RewriteEngine\n            if trimmed.contains(";
")\n            .font(.system(size: 11))\n            .foregroundColor(.secondary)\n    }\n\n    // 备份列\n    private var backupColumn: some View {\n        HStack(spacing: 4) {\n            Button(" = ")\n            .font(.system(size: 11))\n            .foregroundColor(.secondary)\n    }\n\n    // 备份列\n    private var backupColumn: some View {\n        HStack(spacing: 4) {\n            Button(";
"\n        currentCheckingType = nil\n    }\n\n    // MARK: - 分阶段并行检查\n\n    /// 所有站点并行检查指定类型\n    private func checkAllSitesForType(_ type: HealthCheckType, sites: [Site]) async {\n        await withTaskGroup(of: (Int, HealthCheckResult?).self) { group in\n            // 为每个站点添加检查任务\n            for (index, site) in sites.enumerated() {\n                // 检查该站点是否需要此类型的检查\n                let needsCheck = await shouldPerformCheck(type: type, for: site, at: index)\n\n                if needsCheck {\n                    group.addTask {\n                        let result = await self.performCheck(type: type, for: site)\n                        return (index, result)\n                    }\n                }\n            }\n\n            // 收集所有结果并更新报告\n            for await (siteIndex, result) in group {\n                if let result = result, siteIndex < reports.count {\n                    // 找到对应的结果索引并更新\n                    if let resultIndex = reports[siteIndex].results.firstIndex(where: { $0.type == type }) {\n                        reports[siteIndex].results[resultIndex] = result\n                    }\n                }\n            }\n        }\n    }\n\n    /// 判断是否需要执行指定类型的检查\n    private func shouldPerformCheck(type: HealthCheckType, for site: Site, at index: Int) async -> Bool {\n        guard index < reports.count else { return false }\n\n        // 检查报告中是否包含该类型的检查项\n        return reports[index].results.contains(where: { $0.type == type })\n    }\n\n    /// 执行指定类型的检查\n    private func performCheck(type: HealthCheckType, for site: Site) async -> HealthCheckResult? {\n        switch type {\n        case .hosts:\n            return await checkHosts(for: site)\n        case .webServer:\n            return await checkWebServerConfig(for: site)\n        case .ports:\n            return await checkPorts(for: site)\n        case .php:\n            let needsCheck = await shouldCheckPHP(for: site)\n            return needsCheck ? await checkPHP(for: site) : nil\n        case .ssl:\n            return site.sslStatus == .enabled ? await checkSSL(for: site) : nil\n        }\n    }\n\n    // 创建初始的待检测结果\n    private func createInitialResults(for site: Site) -> [HealthCheckResult] {\n        var results: [HealthCheckResult] = [\n            HealthCheckResult(type: .hosts, status: .checking, message: " = "\n        currentCheckingType = nil\n    }\n\n    // MARK: - 分阶段并行检查\n\n    /// 所有站点并行检查指定类型\n    private func checkAllSitesForType(_ type: HealthCheckType, sites: [Site]) async {\n        await withTaskGroup(of: (Int, HealthCheckResult?).self) { group in\n            // 为每个站点添加检查任务\n            for (index, site) in sites.enumerated() {\n                // 检查该站点是否需要此类型的检查\n                let needsCheck = await shouldPerformCheck(type: type, for: site, at: index)\n\n                if needsCheck {\n                    group.addTask {\n                        let result = await self.performCheck(type: type, for: site)\n                        return (index, result)\n                    }\n                }\n            }\n\n            // 收集所有结果并更新报告\n            for await (siteIndex, result) in group {\n                if let result = result, siteIndex < reports.count {\n                    // 找到对应的结果索引并更新\n                    if let resultIndex = reports[siteIndex].results.firstIndex(where: { $0.type == type }) {\n                        reports[siteIndex].results[resultIndex] = result\n                    }\n                }\n            }\n        }\n    }\n\n    /// 判断是否需要执行指定类型的检查\n    private func shouldPerformCheck(type: HealthCheckType, for site: Site, at index: Int) async -> Bool {\n        guard index < reports.count else { return false }\n\n        // 检查报告中是否包含该类型的检查项\n        return reports[index].results.contains(where: { $0.type == type })\n    }\n\n    /// 执行指定类型的检查\n    private func performCheck(type: HealthCheckType, for site: Site) async -> HealthCheckResult? {\n        switch type {\n        case .hosts:\n            return await checkHosts(for: site)\n        case .webServer:\n            return await checkWebServerConfig(for: site)\n        case .ports:\n            return await checkPorts(for: site)\n        case .php:\n            let needsCheck = await shouldCheckPHP(for: site)\n            return needsCheck ? await checkPHP(for: site) : nil\n        case .ssl:\n            return site.sslStatus == .enabled ? await checkSSL(for: site) : nil\n        }\n    }\n\n    // 创建初始的待检测结果\n    private func createInitialResults(for site: Site) -> [HealthCheckResult] {\n        var results: [HealthCheckResult] = [\n            HealthCheckResult(type: .hosts, status: .checking, message: ";
"\n        }\n    }\n}\n\n// 保留旧的类型别名以兼容现有代码\ntypealias NginxInstallMode = WebServerInstallMode\n\nstruct WebServerInstallSheet: View {\n    @Environment(\\.dismiss) private var dismiss\n\n    let serverType: WebServerInstallType\n    let onInstall: (WebServerInstallMode, Int) -> Void\n\n    @State private var selectedMode: WebServerInstallMode = .takeOver\n    @State private var isDetecting = true\n    @State private var availablePort: Int = 8080\n    @State private var portInfos: [PortInfo] = []\n    @State private var customPort: String = " = "\n        }\n    }\n}\n\n// 保留旧的类型别名以兼容现有代码\ntypealias NginxInstallMode = WebServerInstallMode\n\nstruct WebServerInstallSheet: View {\n    @Environment(\\.dismiss) private var dismiss\n\n    let serverType: WebServerInstallType\n    let onInstall: (WebServerInstallMode, Int) -> Void\n\n    @State private var selectedMode: WebServerInstallMode = .takeOver\n    @State private var isDetecting = true\n    @State private var availablePort: Int = 8080\n    @State private var portInfos: [PortInfo] = []\n    @State private var customPort: String = ";
"共 \\(filteredSites.count) 条  前往" = "共 \\(filteredSites.count) 条  前往";
"\n    @State private var isPortSaving: Bool = false\n    @State private var isPortLoading: Bool = false\n    @State private var portSyncResult: PortSyncResult?\n    @State private var showPortSyncResult: Bool = false\n\n    // 判断是否为 PHP 服务\n    private var isPHPService: Bool {\n        service.name == " = "\n    @State private var isPortSaving: Bool = false\n    @State private var isPortLoading: Bool = false\n    @State private var portSyncResult: PortSyncResult?\n    @State private var showPortSyncResult: Bool = false\n\n    // 判断是否为 PHP 服务\n    private var isPHPService: Bool {\n        service.name == ";
") {\n            return NSImage(contentsOf: url)\n        }\n        return nil\n    }\n\n    // 状态徽章\n    @ViewBuilder\n    private var statusBadge: some View {\n        if !service.installed {\n            Text(" = ") {\n            return NSImage(contentsOf: url)\n        }\n        return nil\n    }\n\n    // 状态徽章\n    @ViewBuilder\n    private var statusBadge: some View {\n        if !service.installed {\n            Text(";
") {\n            return true\n        }\n\n        return false\n    }\n\n    /// 判断两个配置是否匹配（考虑默认配置的特殊情况）\n    private func configsMatch(_ config1: FoundVhostConfig, _ config2: FoundVhostConfig) -> Bool {\n        // 如果两个都是默认配置，则认为匹配\n        if isDefaultConfig(config1) && isDefaultConfig(config2) {\n            return true\n        }\n\n        // 否则检查域名是否有交集\n        return Set(config1.parsed.domains).intersection(Set(config2.parsed.domains)).count > 0\n    }\n\n    private func checkConfigCompleteness(_ configs: [FoundVhostConfig]) -> [FoundVhostConfig] {\n        var result: [FoundVhostConfig] = []\n\n        // 分离 Apache 配置和 Nginx 代理配置\n        let apacheConfigs = configs.filter { $0.parsed.webServer == .apache }\n        let nginxProxyConfigs = configs.filter { $0.parsed.webServer == .nginx && $0.parsed.isProxy }\n        let nginxDirectConfigs = configs.filter { $0.parsed.webServer == .nginx && !$0.parsed.isProxy }\n\n        // 获取工作空间路径\n        let workspaceManager = WorkspaceManager.shared\n        let nginxVhostsDir: String\n        let apacheVhostsDir: String\n\n        if let config = workspaceManager.config, config.isInitialized {\n            nginxVhostsDir = config.nginxVhostsPath\n            apacheVhostsDir = config.apacheVhostsPath\n        } else {\n            let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n            nginxVhostsDir = " = ") {\n            return true\n        }\n\n        return false\n    }\n\n    /// 判断两个配置是否匹配（考虑默认配置的特殊情况）\n    private func configsMatch(_ config1: FoundVhostConfig, _ config2: FoundVhostConfig) -> Bool {\n        // 如果两个都是默认配置，则认为匹配\n        if isDefaultConfig(config1) && isDefaultConfig(config2) {\n            return true\n        }\n\n        // 否则检查域名是否有交集\n        return Set(config1.parsed.domains).intersection(Set(config2.parsed.domains)).count > 0\n    }\n\n    private func checkConfigCompleteness(_ configs: [FoundVhostConfig]) -> [FoundVhostConfig] {\n        var result: [FoundVhostConfig] = []\n\n        // 分离 Apache 配置和 Nginx 代理配置\n        let apacheConfigs = configs.filter { $0.parsed.webServer == .apache }\n        let nginxProxyConfigs = configs.filter { $0.parsed.webServer == .nginx && $0.parsed.isProxy }\n        let nginxDirectConfigs = configs.filter { $0.parsed.webServer == .nginx && !$0.parsed.isProxy }\n\n        // 获取工作空间路径\n        let workspaceManager = WorkspaceManager.shared\n        let nginxVhostsDir: String\n        let apacheVhostsDir: String\n\n        if let config = workspaceManager.config, config.isInitialized {\n            nginxVhostsDir = config.nginxVhostsPath\n            apacheVhostsDir = config.apacheVhostsPath\n        } else {\n            let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n            nginxVhostsDir = ";
"  // 扫描的路径\n\n    private let certificatesFileURL: URL\n\n    private init() {\n        // 设置证书数据文件路径\n        let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!\n        let folderURL = appSupport.appendingPathComponent(" = "  // 扫描的路径\n\n    private let certificatesFileURL: URL\n\n    private init() {\n        // 设置证书数据文件路径\n        let appSupport = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first!\n        let folderURL = appSupport.appendingPathComponent(";
")\n\n                // 私钥路径（与证书在同一目录）\n                let keyPath = " = ")\n\n                // 私钥路径（与证书在同一目录）\n                let keyPath = ";
"\n    }\n\n    private init() {}\n\n    // MARK: - MySQL 路径\n\n    /// 获取 MySQL 配置文件路径（优先返回存在的文件）\n    func getMySQLConfigPath(version: String? = nil) -> String {\n        let ver = version ?? " = "\n    }\n\n    private init() {}\n\n    // MARK: - MySQL 路径\n\n    /// 获取 MySQL 配置文件路径（优先返回存在的文件）\n    func getMySQLConfigPath(version: String? = nil) -> String {\n        let ver = version ?? ";
"\n        if FileManager.default.fileExists(atPath: genericPath) {\n            return genericPath\n        }\n\n        return nil\n    }\n\n    // MARK: - 命令检测\n\n    /// 检查命令是否可用\n    private func isCommandAvailable(_ command: String) async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: " = "\n        if FileManager.default.fileExists(atPath: genericPath) {\n            return genericPath\n        }\n\n        return nil\n    }\n\n    // MARK: - 命令检测\n\n    /// 检查命令是否可用\n    private func isCommandAvailable(_ command: String) async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: ";
"))\n                .font(.system(size: 11))\n                .foregroundColor(.secondary)\n                .lineLimit(1)\n                .frame(width: 100, alignment: .leading)\n\n            Spacer()\n\n            // 操作按钮\n            actionButtons\n                .frame(width: 180, alignment: .trailing)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 8)\n        .background(isHovered ? Color.gray.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n        .onHover { hovering in isHovered = hovering }\n        .onAppear { fetchDatabaseUser() }\n        .sheetWithLocale(isPresented: $showPermissionsSheet) {\n            LocalDatabasePermissionsSheet(\n                databaseName: name,\n                username: dbUsername,\n                host: host,\n                port: port,\n                password: password\n            )\n        }\n        .sheetWithLocale(isPresented: $showChangePasswordSheet) {\n            LocalChangePasswordSheet(\n                databaseName: name,\n                username: dbUsername,\n                host: host,\n                port: port,\n                password: password\n            )\n        }\n        .sheetWithLocale(isPresented: $showImportSheet) {\n            LocalImportDatabaseSheet(\n                databaseName: name,\n                host: host,\n                port: port,\n                password: password\n            )\n        }\n    }\n\n    // 密码列\n    private var passwordColumn: some View {\n        Text(" = "))\n                .font(.system(size: 11))\n                .foregroundColor(.secondary)\n                .lineLimit(1)\n                .frame(width: 100, alignment: .leading)\n\n            Spacer()\n\n            // 操作按钮\n            actionButtons\n                .frame(width: 180, alignment: .trailing)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 8)\n        .background(isHovered ? Color.gray.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n        .onHover { hovering in isHovered = hovering }\n        .onAppear { fetchDatabaseUser() }\n        .sheetWithLocale(isPresented: $showPermissionsSheet) {\n            LocalDatabasePermissionsSheet(\n                databaseName: name,\n                username: dbUsername,\n                host: host,\n                port: port,\n                password: password\n            )\n        }\n        .sheetWithLocale(isPresented: $showChangePasswordSheet) {\n            LocalChangePasswordSheet(\n                databaseName: name,\n                username: dbUsername,\n                host: host,\n                port: port,\n                password: password\n            )\n        }\n        .sheetWithLocale(isPresented: $showImportSheet) {\n            LocalImportDatabaseSheet(\n                databaseName: name,\n                host: host,\n                port: port,\n                password: password\n            )\n        }\n    }\n\n    // 密码列\n    private var passwordColumn: some View {\n        Text(";
"\n    @State private var enableSSL = false  // 默认不启用SSL\n    @State private var forceHttps = false  // 强制HTTPS（HTTP自动跳转到HTTPS）\n    @State private var sslMode: SSLMode = .nginxTermination  // SSL架构模式（默认Nginx SSL终止）\n    @State private var updateHosts = false  // 默认不启用本地解析\n    @State private var createFTP = false  // FTP创建选项\n    @State private var createDatabase = false  // 数据库创建选项\n    @State private var expiryDate = " = "\n    @State private var enableSSL = false  // 默认不启用SSL\n    @State private var forceHttps = false  // 强制HTTPS（HTTP自动跳转到HTTPS）\n    @State private var sslMode: SSLMode = .nginxTermination  // SSL架构模式（默认Nginx SSL终止）\n    @State private var updateHosts = false  // 默认不启用本地解析\n    @State private var createFTP = false  // FTP创建选项\n    @State private var createDatabase = false  // 数据库创建选项\n    @State private var expiryDate = ";
")\n                .font(.system(size: 8))\n                .foregroundColor(.green)\n            Text(site.status.rawValue)\n                .font(.system(size: 12))\n                .foregroundColor(.green)\n        }\n    }\n\n    // 备份视图\n    private var backupView: some View {\n        Group {\n            if site.hasBackup {\n                Text(" = ")\n                .font(.system(size: 8))\n                .foregroundColor(.green)\n            Text(site.status.rawValue)\n                .font(.system(size: 12))\n                .foregroundColor(.green)\n        }\n    }\n\n    // 备份视图\n    private var backupView: some View {\n        Group {\n            if site.hasBackup {\n                Text(";
"nginx -t 输出:\\n\\(output)" = "nginx -t 输出:\\n\\(output)";
"扫描子目录失败: \\(error)" = "扫描子目录失败: \\(error)";
"]\n\n            let lsofPipe = Pipe()\n            lsofProcess.standardOutput = lsofPipe\n            lsofProcess.standardError = lsofPipe\n\n            do {\n                try lsofProcess.run()\n                lsofProcess.waitUntilExit()\n\n                let data = lsofPipe.fileHandleForReading.readDataToEndOfFile()\n                if let output = String(data: data, encoding: .utf8) {\n                    let lines = output.components(separatedBy: .newlines)\n                    for line in lines {\n                        // txt 类型的文件通常是可执行文件\n                        if line.hasPrefix(" = "]\n\n            let lsofPipe = Pipe()\n            lsofProcess.standardOutput = lsofPipe\n            lsofProcess.standardError = lsofPipe\n\n            do {\n                try lsofProcess.run()\n                lsofProcess.waitUntilExit()\n\n                let data = lsofPipe.fileHandleForReading.readDataToEndOfFile()\n                if let output = String(data: data, encoding: .utf8) {\n                    let lines = output.components(separatedBy: .newlines)\n                    for line in lines {\n                        // txt 类型的文件通常是可执行文件\n                        if line.hasPrefix(";
"]\n\n                let pipe = Pipe()\n                process.standardOutput = pipe\n                process.standardError = pipe\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                    guard let output = String(data: data, encoding: .utf8) else {\n                        continuation.resume(returning: [])\n                        return\n                    }\n\n                    // 更新 AppState 缓存\n                    AppState.cachedBrewServicesOutput = output\n\n                    var running: Set<String> = []\n                    let lines = output.components(separatedBy: .newlines)\n\n                    for line in lines {\n                        // 跳过空行和表头行\n                        let trimmed = line.trimmingCharacters(in: .whitespaces)\n                        if trimmed.isEmpty || trimmed.hasPrefix(" = "]\n\n                let pipe = Pipe()\n                process.standardOutput = pipe\n                process.standardError = pipe\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    let data = pipe.fileHandleForReading.readDataToEndOfFile()\n                    guard let output = String(data: data, encoding: .utf8) else {\n                        continuation.resume(returning: [])\n                        return\n                    }\n\n                    // 更新 AppState 缓存\n                    AppState.cachedBrewServicesOutput = output\n\n                    var running: Set<String> = []\n                    let lines = output.components(separatedBy: .newlines)\n\n                    for line in lines {\n                        // 跳过空行和表头行\n                        let trimmed = line.trimmingCharacters(in: .whitespaces)\n                        if trimmed.isEmpty || trimmed.hasPrefix(";
"]\n        var result = databases\n\n        // 过滤系统数据库\n        if hideSystemDatabases {\n            result = result.filter { !systemDatabases.contains($0.lowercased()) }\n        }\n\n        // 搜索过滤\n        if !searchText.isEmpty {\n            result = result.filter { $0.localizedCaseInsensitiveContains(searchText) }\n        }\n\n        return result\n    }\n\n    // 筛选后的远程数据库\n    private var filteredRemoteDatabases: [MySQLDatabaseInfo] {\n        var result = remoteDatabases\n\n        // 过滤系统数据库\n        if hideSystemDatabases {\n            result = result.filter { !$0.isSystem }\n        }\n\n        // 搜索过滤\n        if !searchText.isEmpty {\n            result = result.filter { $0.name.localizedCaseInsensitiveContains(searchText) }\n        }\n\n        return result\n    }\n\n    // 导出数据库\n    private func exportDatabase(_ name: String) {\n        let panel = NSSavePanel()\n        panel.nameFieldStringValue = " = "]\n        var result = databases\n\n        // 过滤系统数据库\n        if hideSystemDatabases {\n            result = result.filter { !systemDatabases.contains($0.lowercased()) }\n        }\n\n        // 搜索过滤\n        if !searchText.isEmpty {\n            result = result.filter { $0.localizedCaseInsensitiveContains(searchText) }\n        }\n\n        return result\n    }\n\n    // 筛选后的远程数据库\n    private var filteredRemoteDatabases: [MySQLDatabaseInfo] {\n        var result = remoteDatabases\n\n        // 过滤系统数据库\n        if hideSystemDatabases {\n            result = result.filter { !$0.isSystem }\n        }\n\n        // 搜索过滤\n        if !searchText.isEmpty {\n            result = result.filter { $0.name.localizedCaseInsensitiveContains(searchText) }\n        }\n\n        return result\n    }\n\n    // 导出数据库\n    private func exportDatabase(_ name: String) {\n        let panel = NSSavePanel()\n        panel.nameFieldStringValue = ";
"写入失败: \\(errorMessage)" = "写入失败: \\(errorMessage)";
") && !software.installed {\n            if let conflictInfo = softwareManager.checkPHPPortConflict(software) {\n                await MainActor.run {\n                    phpPortConflictInfo = conflictInfo\n                    pendingPHPInstall = software\n                    showPHPPortConflictSheet = true\n                }\n                return\n            }\n        }\n\n        do {\n            try await softwareManager.installSoftware(software, category: selectedCategory)\n        } catch {\n            errorMessage = error.localizedDescription\n            showError = true\n        }\n    }\n\n    private func handleNginxInstall(mode: NginxInstallMode, port: Int) {\n        Task {\n            do {\n                try await softwareManager.installNginx(mode: mode, port: port)\n            } catch {\n                errorMessage = error.localizedDescription\n                showError = true\n            }\n        }\n    }\n\n    private func handleApacheInstall(mode: WebServerInstallMode, port: Int) {\n        Task {\n            do {\n                try await softwareManager.installApache(mode: mode, port: port)\n            } catch {\n                errorMessage = error.localizedDescription\n                showError = true\n            }\n        }\n    }\n\n    private func handlePureFTPInstall(mode: PureFTPDatabaseMode) {\n        Task {\n            do {\n                try await softwareManager.installPureFTP(databaseMode: mode)\n            } catch {\n                errorMessage = error.localizedDescription\n                showError = true\n            }\n        }\n    }\n\n    private func handlePHPPortConflictResolution(_ resolution: PHPPortConflictResolution) {\n        guard let software = pendingPHPInstall else { return }\n\n        Task {\n            switch resolution {\n            case .stopConflictingService:\n                // 停止冲突的服务，然后使用默认端口安装\n                if let conflictInfo = phpPortConflictInfo {\n                    let stopped = await softwareManager.stopPHPService(named: conflictInfo.conflictingService)\n                    if stopped {\n                        do {\n                            try await softwareManager.installSoftware(software, category: selectedCategory)\n                        } catch {\n                            errorMessage = error.localizedDescription\n                            showError = true\n                        }\n                    } else {\n                        errorMessage = " = ") && !software.installed {\n            if let conflictInfo = softwareManager.checkPHPPortConflict(software) {\n                await MainActor.run {\n                    phpPortConflictInfo = conflictInfo\n                    pendingPHPInstall = software\n                    showPHPPortConflictSheet = true\n                }\n                return\n            }\n        }\n\n        do {\n            try await softwareManager.installSoftware(software, category: selectedCategory)\n        } catch {\n            errorMessage = error.localizedDescription\n            showError = true\n        }\n    }\n\n    private func handleNginxInstall(mode: NginxInstallMode, port: Int) {\n        Task {\n            do {\n                try await softwareManager.installNginx(mode: mode, port: port)\n            } catch {\n                errorMessage = error.localizedDescription\n                showError = true\n            }\n        }\n    }\n\n    private func handleApacheInstall(mode: WebServerInstallMode, port: Int) {\n        Task {\n            do {\n                try await softwareManager.installApache(mode: mode, port: port)\n            } catch {\n                errorMessage = error.localizedDescription\n                showError = true\n            }\n        }\n    }\n\n    private func handlePureFTPInstall(mode: PureFTPDatabaseMode) {\n        Task {\n            do {\n                try await softwareManager.installPureFTP(databaseMode: mode)\n            } catch {\n                errorMessage = error.localizedDescription\n                showError = true\n            }\n        }\n    }\n\n    private func handlePHPPortConflictResolution(_ resolution: PHPPortConflictResolution) {\n        guard let software = pendingPHPInstall else { return }\n\n        Task {\n            switch resolution {\n            case .stopConflictingService:\n                // 停止冲突的服务，然后使用默认端口安装\n                if let conflictInfo = phpPortConflictInfo {\n                    let stopped = await softwareManager.stopPHPService(named: conflictInfo.conflictingService)\n                    if stopped {\n                        do {\n                            try await softwareManager.installSoftware(software, category: selectedCategory)\n                        } catch {\n                            errorMessage = error.localizedDescription\n                            showError = true\n                        }\n                    } else {\n                        errorMessage = ";
"停止 \\(software.name) 失败" = "Failed to stop \\(software.name)";
"]\n        brewPath = paths.first { FileManager.default.fileExists(atPath: $0) }\n\n        // 订阅 ServiceStatusMonitor 的状态变化\n        subscribeToStatusMonitor()\n\n        loadSoftwareVersions()\n    }\n\n    /// 订阅全局状态监控器\n    private func subscribeToStatusMonitor() {\n        let monitor = ServiceStatusMonitor.shared\n\n        // 当运行状态变化时，更新软件列表的运行状态\n        monitor.$runningServices\n            .dropFirst() // 跳过初始值\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] running in\n                self?.updateRunningStates(running)\n            }\n            .store(in: &cancellables)\n\n        // 当安装包列表变化时，更新软件列表\n        monitor.$installedPackages\n            .dropFirst()\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] installed in\n                guard let self = self else { return }\n                let running = monitor.runningServices\n                self.rebuildLists(installed: installed, running: running)\n            }\n            .store(in: &cancellables)\n    }\n\n    /// 只更新运行状态（不重建整个列表）\n    private func updateRunningStates(_ running: Set<String>) {\n        // 先立即更新运行状态（不阻塞主线程）\n        for i in phpVersions.indices {\n            let brewName = phpVersions[i].brewName ?? phpVersions[i].id\n            phpVersions[i].isRunning = running.contains(brewName)\n            if phpVersions[i].isRunning { phpVersions[i].stopReason = nil }\n        }\n        for i in webServers.indices {\n            let brewName = webServers[i].brewName ?? webServers[i].id\n            webServers[i].isRunning = running.contains(brewName)\n            if webServers[i].isRunning { webServers[i].stopReason = nil }\n        }\n        for i in databases.indices {\n            let brewName = databases[i].brewName ?? databases[i].id\n            databases[i].isRunning = running.contains(brewName)\n            if databases[i].isRunning { databases[i].stopReason = nil }\n        }\n        for i in caches.indices {\n            let brewName = caches[i].brewName ?? caches[i].id\n            caches[i].isRunning = running.contains(brewName)\n            if caches[i].isRunning { caches[i].stopReason = nil }\n        }\n        for i in tools.indices {\n            let brewName = tools[i].brewName ?? tools[i].id\n            if tools[i].id == " = "]\n        brewPath = paths.first { FileManager.default.fileExists(atPath: $0) }\n\n        // Subscribe to ServiceStatusMonitor status changes\n        subscribeToStatusMonitor()\n\n        loadSoftwareVersions()\n    }\n\n    /// Subscribe to global status monitor\n    private func subscribeToStatusMonitor() {\n        let monitor = ServiceStatusMonitor.shared\n\n        // Update software list running status when runtime status changes\n        monitor.$runningServices\n            .dropFirst() // Skip initial value\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] running in\n                self?.updateRunningStates(running)\n            }\n            .store(in: &cancellables)\n\n        // Update software list when installed packages change\n        monitor.$installedPackages\n            .dropFirst()\n            .receive(on: DispatchQueue.main)\n            .sink { [weak self] installed in\n                guard let self = self else { return }\n                let running = monitor.runningServices\n                self.rebuildLists(installed: installed, running: running)\n            }\n            .store(in: &cancellables)\n    }\n\n    /// Update only running states (without rebuilding entire list)\n    private func updateRunningStates(_ running: Set<String>) {\n        // Immediately update running states (non-blocking main thread)\n        for i in phpVersions.indices {\n            let brewName = phpVersions[i].brewName ?? phpVersions[i].id\n            phpVersions[i].isRunning = running.contains(brewName)\n            if phpVersions[i].isRunning { phpVersions[i].stopReason = nil }\n        }\n        for i in webServers.indices {\n            let brewName = webServers[i].brewName ?? webServers[i].id\n            webServers[i].isRunning = running.contains(brewName)\n            if webServers[i].isRunning { webServers[i].stopReason = nil }\n        }\n        for i in databases.indices {\n            let brewName = databases[i].brewName ?? databases[i].id\n            databases[i].isRunning = running.contains(brewName)\n            if databases[i].isRunning { databases[i].stopReason = nil }\n        }\n        for i in caches.indices {\n            let brewName = caches[i].brewName ?? caches[i].id\n            caches[i].isRunning = running.contains(brewName)\n            if caches[i].isRunning { caches[i].stopReason = nil }\n        }\n        for i in tools.indices {\n            let brewName = tools[i].brewName ?? tools[i].id\n            if tools[i].id == ";
" {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .sheetWithLocale(item: $selectedSite) { site in\n            SiteLogsView(site: site)\n        }\n        .sheetWithLocale(isPresented: $showCustomLogAnalysis) {\n            CustomLogAnalysisSheet()\n        }\n    }\n    \n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // 顶部工具栏\n            toolBar\n\n            Divider()\n\n            if siteManager.sites.isEmpty {\n                emptyStateView\n            } else {\n                siteListView\n            }\n        }\n    }\n    \n    private var v2Body: some View {\n        VStack(spacing: 0) {\n            // 顶部工具栏\n            toolBarV2\n\n            if siteManager.sites.isEmpty {\n                emptyStateViewV2\n            } else {\n                siteListViewV2\n            }\n        }\n        .background(Color.white.opacity(0.1))\n        .cornerRadius(AppTokens.Radius.medium)\n        .overlay(\n            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                .stroke(Color.white.opacity(0.2), lineWidth: 1)\n        )\n        .padding(.horizontal, 20)\n        .padding(.vertical, 20)\n    }\n\n    // MARK: - V2 顶部工具栏\n    private var toolBarV2: some View {\n        HStack {\n            Text(" = " {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .sheetWithLocale(item: $selectedSite) { site in\n            SiteLogsView(site: site)\n        }\n        .sheetWithLocale(isPresented: $showCustomLogAnalysis) {\n            CustomLogAnalysisSheet()\n        }\n    }\n    \n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // Top toolbar\n            toolBar\n\n            Divider()\n\n            if siteManager.sites.isEmpty {\n                emptyStateView\n            } else {\n                siteListView\n            }\n        }\n    }\n    \n    private var v2Body: some View {\n        VStack(spacing: 0) {\n            // Top toolbar\n            toolBarV2\n\n            if siteManager.sites.isEmpty {\n                emptyStateViewV2\n            } else {\n                siteListViewV2\n            }\n        }\n        .background(Color.white.opacity(0.1))\n        .cornerRadius(AppTokens.Radius.medium)\n        .overlay(\n            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                .stroke(Color.white.opacity(0.2), lineWidth: 1)\n        )\n        .padding(.horizontal, 20)\n        .padding(.vertical, 20)\n    }\n\n    // MARK: - V2 Top Toolbar\n    private var toolBarV2: some View {\n        HStack {\n            Text(";
"\n            let workspaceFpmPath = config.phpFpmConfigFile(version: version)\n\n            var configModified = false\n\n            // 优先修改 www.conf（PHP 7.x/8.x）\n            if fm.fileExists(atPath: workspaceWwwPath) {\n                try updatePHPFPMConfig(at: workspaceWwwPath, port: port)\n                configModified = true\n            }\n\n            // 也检查 php-fpm.conf（PHP 5.x 或某些配置）\n            if fm.fileExists(atPath: workspaceFpmPath) {\n                try updatePHPFPMConfig(at: workspaceFpmPath, port: port)\n                configModified = true\n            }\n\n            if configModified {\n                return\n            }\n        }\n\n        // 回退到直接修改 Homebrew 配置（工作空间未初始化时）\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        let possiblePaths = [\n            " = "\n            let workspaceFpmPath = config.phpFpmConfigFile(version: version)\n\n            var configModified = false\n\n            // Prefer modifying www.conf (PHP 7.x/8.x)\n            if fm.fileExists(atPath: workspaceWwwPath) {\n                try updatePHPFPMConfig(at: workspaceWwwPath, port: port)\n                configModified = true\n            }\n\n            // Also check php-fpm.conf (PHP 5.x or certain configurations)\n            if fm.fileExists(atPath: workspaceFpmPath) {\n                try updatePHPFPMConfig(at: workspaceFpmPath, port: port)\n                configModified = true\n            }\n\n            if configModified {\n                return\n            }\n        }\n\n        // Fallback to directly modifying Homebrew configuration (when workspace is uninitialized)\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        let possiblePaths = [\n            ";
")\n                    formatted.append(String(repeating: indentString, count: indentLevel))\n                    continue\n                }\n            }\n            formatted.append(char)\n        }\n        \n        // 检查栈中剩余的未闭合的左括号\n        for remaining in stack {\n            errors.append(NSRange(location: remaining.1, length: 1))\n        }\n        \n        if !errors.isEmpty {\n            inputSyntaxErrors = errors\n            errorMessage = " = ")\n                    formatted.append(String(repeating: indentString, count: indentLevel))\n                    continue\n                }\n            }\n            formatted.append(char)\n        }\n        \n        // Check for remaining unclosed left brackets in stack\n        for remaining in stack {\n            errors.append(NSRange(location: remaining.1, length: 1))\n        }\n        \n        if !errors.isEmpty {\n            inputSyntaxErrors = errors\n            errorMessage = ";
" {\n                    Button(action: {\n                        withAnimation {\n                            if !showAddRuleForm {\n                                // 打开表单时生成随机用户名和密码\n                                newRuleUsername = generateRandomUsername()\n                                newRulePassword = generateRandomPassword()\n                            }\n                            showAddRuleForm.toggle()\n                        }\n                    }) {\n                        HStack(spacing: 4) {\n                            Image(systemName: " = " {\n                    Button(action: {\n                        withAnimation {\n                            if !showAddRuleForm {\n                                // Generate random username and password when opening form\n                                newRuleUsername = generateRandomUsername()\n                                newRulePassword = generateRandomPassword()\n                            }\n                            showAddRuleForm.toggle()\n                        }\n                    }) {\n                        HStack(spacing: 4) {\n                            Image(systemName: ";
"选择常用模型" = "Select Common Model";
")\n        }\n\n        // 3. 授权\n        let grantSQL = " = ")\n        }\n\n        // 3. Authorization\n        let grantSQL = ";
")\n\n        return paths\n    }\n\n    // MARK: - 日志路径\n\n    /// 获取 MySQL 日志目录\n    func getMySQLLogsPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.mysqlLogsPath\n        }\n        return " = ")\n\n        return paths\n    }\n\n    // MARK: - Log Paths\n\n    /// Get MySQL log directory\n    func getMySQLLogsPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.mysqlLogsPath\n        }\n        return ";
"\n        }\n\n        if let url = URL(string: urlString) {\n            NSWorkspace.shared.open(url)\n        }\n    }\n\n    // 获取已安装的PHP版本列表\n    private var installedPHPVersions: [String] {\n        var versions: [String] = []\n        for service in appState.services {\n            // 通过名称判断是否是PHP服务，如 " = "\n        }\n\n        if let url = URL(string: urlString) {\n            NSWorkspace.shared.open(url)\n        }\n    }\n\n    // Get list of installed PHP versions\n    private var installedPHPVersions: [String] {\n        var versions: [String] = []\n        for service in appState.services {\n            // Determine if service is a PHP service by name, e.g., ";
")\n                    .foregroundColor(.blue)\n            }\n            .buttonStyle(.plain)\n            .frame(width: 45, alignment: .center)\n            \n            // 容量\n            Text(formatSize(site.directorySize))\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 60, alignment: .leading)\n            \n            // 到期时间\n            Text(site.expiryDate ?? " = ")\n                    .foregroundColor(.blue)\n            }\n            .buttonStyle(.plain)\n            .frame(width: 45, alignment: .center)\n            \n            // Capacity\n            Text(formatSize(site.directorySize))\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 60, alignment: .leading)\n            \n            // Expiration date\n            Text(site.expiryDate ?? ";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // MARK: - V2 标签页栏\n    private var tabBarV2: some View {\n        HStack(spacing: 0) {\n            ForEach(LogTab.allCases, id: \\.self) { tab in\n                Button(action: {\n                    withAnimation(.easeInOut(duration: 0.2)) {\n                        selectedTab = tab\n                    }\n                }) {\n                    VStack(spacing: 8) {\n                        Text(LocalizedStringKey(tab.rawValue))\n                            .font(.system(size: 14, weight: selectedTab == tab ? .semibold : .regular))\n                            .foregroundColor(selectedTab == tab ? .blue : .secondary)\n\n                        Rectangle()\n                            .fill(selectedTab == tab ? Color.blue : Color.clear)\n                            .frame(height: 3)\n                    }\n                    .frame(maxWidth: .infinity)\n                    .padding(.vertical, 12)\n                    .background(selectedTab == tab ? Color.blue.opacity(0.1) : Color.clear)\n                }\n                .buttonStyle(.plain)\n            }\n        }\n        .background(Color.white.opacity(0.05))\n    }\n\n    // MARK: - 标签页栏\n    private var tabBar: some View {\n        HStack(spacing: 0) {\n            ForEach(LogTab.allCases, id: \\.self) { tab in\n                Button(action: {\n                    withAnimation(.easeInOut(duration: 0.2)) {\n                        selectedTab = tab\n                    }\n                }) {\n                    VStack(spacing: 8) {\n                        Text(LocalizedStringKey(tab.rawValue))\n                            .font(.system(size: 14, weight: selectedTab == tab ? .semibold : .regular))\n                            .foregroundColor(selectedTab == tab ? .green : .secondary)\n\n                        Rectangle()\n                            .fill(selectedTab == tab ? Color.green : Color.clear)\n                            .frame(height: 3)\n                    }\n                    .frame(maxWidth: .infinity)\n                    .padding(.vertical, 12)\n                    .background(selectedTab == tab ? Color.green.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n                }\n                .buttonStyle(.plain)\n            }\n        }\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // MARK: - V2 工具栏\n    private var toolbarV2: some View {\n        HStack(spacing: 12) {\n            // 搜索框\n            HStack(spacing: 8) {\n                Image(systemName: " = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // MARK: - V2 Tab Bar\n    private var tabBarV2: some View {\n        HStack(spacing: 0) {\n            ForEach(LogTab.allCases, id: \\.self) { tab in\n                Button(action: {\n                    withAnimation(.easeInOut(duration: 0.2)) {\n                        selectedTab = tab\n                    }\n                }) {\n                    VStack(spacing: 8) {\n                        Text(LocalizedStringKey(tab.rawValue))\n                            .font(.system(size: 14, weight: selectedTab == tab ? .semibold : .regular))\n                            .foregroundColor(selectedTab == tab ? .blue : .secondary)\n\n                        Rectangle()\n                            .fill(selectedTab == tab ? Color.blue : Color.clear)\n                            .frame(height: 3)\n                    }\n                    .frame(maxWidth: .infinity)\n                    .padding(.vertical, 12)\n                    .background(selectedTab == tab ? Color.blue.opacity(0.1) : Color.clear)\n                }\n                .buttonStyle(.plain)\n            }\n        }\n        .background(Color.white.opacity(0.05))\n    }\n\n    // MARK: - Tab Bar\n    private var tabBar: some View {\n        HStack(spacing: 0) {\n            ForEach(LogTab.allCases, id: \\.self) { tab in\n                Button(action: {\n                    withAnimation(.easeInOut(duration: 0.2)) {\n                        selectedTab = tab\n                    }\n                }) {\n                    VStack(spacing: 8) {\n                        Text(LocalizedStringKey(tab.rawValue))\n                            .font(.system(size: 14, weight: selectedTab == tab ? .semibold : .regular))\n                            .foregroundColor(selectedTab == tab ? .green : .secondary)\n\n                        Rectangle()\n                            .fill(selectedTab == tab ? Color.green : Color.clear)\n                            .frame(height: 3)\n                    }\n                    .frame(maxWidth: .infinity)\n                    .padding(.vertical, 12)\n                    .background(selectedTab == tab ? Color.green.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n                }\n                .buttonStyle(.plain)\n            }\n        }\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // MARK: - V2 Toolbar\n    private var toolbarV2: some View {\n        HStack(spacing: 12) {\n            // Search field\n            HStack(spacing: 8) {\n                Image(systemName: ";
")\n    }\n\n    /// 运行配置语法检测命令，返回错误输出（如果有）\n    private func getConfigTestError(software: SoftwareVersion) async -> String? {\n        let name = software.name.lowercased()\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n\n        let command: String\n        let args: [String]\n\n        if name.contains(" = ")\n    }\n\n    /// Run configuration syntax validation command and return error output (if any)\n    private func getConfigTestError(software: SoftwareVersion) async -> String? {\n        let name = software.name.lowercased()\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n\n        let command: String\n        let args: [String]\n\n        if name.contains(";
"\n        } else {\n            // 从保存的远程数据库中获取密码\n            if let remoteDb = databaseManager.databases.first(where: {\n                $0.type == .remote &&\n                $0.serverAddress == tab.host &&\n                $0.serverPort == tab.port\n            }) {\n                return remoteDb.password\n            }\n            return " = "\n        } else {\n            // Retrieve password from saved remote database\n            if let remoteDb = databaseManager.databases.first(where: {\n                $0.type == .remote &&\n                $0.serverAddress == tab.host &&\n                $0.serverPort == tab.port\n            }) {\n                return remoteDb.password\n            }\n            return ";
"✗ 复制错误页面目录失败: \\(error.localizedDescription)" = "✗ Failed to copy error page directory: \\(error.localizedDescription)";
")\n                                if normalized != newValue {\n                                    domains = normalized\n                                    return\n                                }\n\n                                // 当域名输入时，自动填充站点名称和目录\n                                let domainList = normalized.split(separator: " = ")\n                                if normalized != newValue {\n                                    domains = normalized\n                                    return\n                                }\n\n                                // Auto-fill site name and directory when domain is entered\n                                let domainList = normalized.split(separator: ";
"\n    }\n\n    /// MySQL 日志目录\n    var mysqlLogsPath: String { " = "\n    }\n\n    /// MySQL log directory\n    var mysqlLogsPath: String { ";
"按钮创建新的站点证书" = "button to create a new site certificate";
") || trimmed.isEmpty { continue }\n\n            // 匹配 " = ") || trimmed.isEmpty { continue }\n\n            // Match ";
"\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 表单内容\n            formContent\n\n            Divider()\n\n            // 底部按钮\n            bottomButtons\n        }\n        .frame(width: 500, height: 350)\n        .alert(" = "\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Title bar\n            headerBar\n\n            Divider()\n\n            // Form content\n            formContent\n\n            Divider()\n\n            // Bottom buttons\n            bottomButtons\n        }\n        .frame(width: 500, height: 350)\n        .alert(";
")\n                    .font(.system(size: 13))\n                    .foregroundColor(.secondary)\n\n                // V2 下一页按钮\n                MacButton(icon: " = ")\n                    .font(.system(size: 13))\n                    .foregroundColor(.secondary)\n\n                // Botón «Página siguiente» de V2\n                MacButton(icon: ";
"✗ 私钥文件不存在: \\(keyPath)" = "✗ El archivo de clave privada no existe: \\(keyPath)";
"保存失败: \\(error.localizedDescription)" = "Error al guardar: \\(error.localizedDescription)";
"\n\n        // 根据数据库模式添加配置\n        if databaseMode == .mysql {\n            config += " = "\n\n        // Agregar configuración según el modo de base de datos\n        if databaseMode == .mysql {\n            config += ";
", nil)\n    }\n\n    /// 从 PHP-FPM www.conf 文件中读取端口\n    private func readPHPFPMPortFromFile(_ filePath: String) -> String? {\n        guard FileManager.default.fileExists(atPath: filePath),\n              let content = try? String(contentsOfFile: filePath, encoding: .utf8) else {\n            return nil\n        }\n\n        let lines = content.components(separatedBy: .newlines)\n\n        // 查找 listen 指令\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed.hasPrefix(" = ", nil)\n    }\n\n    /// Leer el puerto desde el archivo www.conf de PHP-FPM\n    private func readPHPFPMPortFromFile(_ filePath: String) -> String? {\n        guard FileManager.default.fileExists(atPath: filePath),\n              let content = try? String(contentsOfFile: filePath, encoding: .utf8) else {\n            return nil\n        }\n\n        let lines = content.components(separatedBy: .newlines)\n\n        // Buscar la directiva «listen»\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed.hasPrefix(";
"无法\\(operation)站点：\\(prefix) 未安装 \\(missingServices.joined(separator: " = "No se pudo \\(operation) el sitio: \\(prefix) no tiene instalados los siguientes servicios: \\(missingServices.joined(separator: ";
"\n            return output.contains(tap)\n        } catch {\n            return false\n        }\n    }\n\n    /// 添加 tap\n    private func addTap(_ tap: String) async -> Bool {\n        guard let brewPath = brewPath else { return false }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [" = "\n            return output.contains(tap)\n        } catch {\n            return false\n        }\n    }\n\n    /// Agregar un tap\n    private func addTap(_ tap: String) async -> Bool {\n        guard let brewPath = brewPath else { return false }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [";
", value: $appSettings.defaultSitePort, format: .number)\n                        .macInput()\n                        .frame(width: 80)\n                }\n\n                // Web 服务器状态\n                webServerStatusRow\n\n                // 操作按钮\n                defaultSiteActions\n\n                // 状态消息\n                if let message = statusMessage {\n                    Text(message)\n                        .font(.caption)\n                        .foregroundColor(message.contains(" = ", value: $appSettings.defaultSitePort, format: .number)\n                        .macInput()\n                        .frame(width: 80)\n                }\n\n                // Estado del servidor web\n                webServerStatusRow\n\n                // Botones de acción\n                defaultSiteActions\n\n                // Mensaje de estado\n                if let message = statusMessage {\n                    Text(message)\n                        .font(.caption)\n                        .foregroundColor(message.contains(";
"\n    }\n\n    // SSL状态视图\n    private var sslStatusView: some View {\n        Button(action: {\n            showSSLDetails()\n        }) {\n            Text(sslStatusText)\n                .font(.system(size: 12))\n                .foregroundColor(sslStatusColor)\n                .frame(maxWidth: .infinity, alignment: .leading)\n        }\n        .buttonStyle(.plain)\n        .help(" = "\n    }\n\n    // Vista de estado SSL\n    private var sslStatusView: some View {\n        Button(action: {\n            showSSLDetails()\n        }) {\n            Text(sslStatusText)\n                .font(.system(size: 12))\n                .foregroundColor(sslStatusColor)\n                .frame(maxWidth: .infinity, alignment: .leading)\n        }\n        .buttonStyle(.plain)\n        .help(";
"）\n    let isDirectory: Bool        // 是否是目录（需要遍历）\n\n    /// 获取实际路径\n    func getActualPath(workspaceRoot: String?, homebrewPrefix: String) -> String? {\n        // 检查是否有工作空间配置\n        if let workspaceRoot = workspaceRoot,\n           let workspacePath = workspacePath,\n           !workspacePath.isEmpty {\n            return " = ")\n    let isDirectory: Bool        // ¿Es un directorio? (requiere recorrido)\n\n    /// Obtener la ruta real\n    func getActualPath(workspaceRoot: String?, homebrewPrefix: String) -> String? {\n        // Verificar si hay una configuración de espacio de trabajo\n        if let workspaceRoot = workspaceRoot,\n           let workspacePath = workspacePath,\n           !workspacePath.isEmpty {\n            return ";
"]\n        for brewPath in brewPaths {\n            if FileManager.default.fileExists(atPath: brewPath) {\n                AppState.cachedBrewPath = brewPath\n                return brewPath\n            }\n        }\n        return nil\n    }\n\n    func startService(_ service: ServiceStatus) {\n        guard let index = services.firstIndex(where: { $0.id == service.id }) else { return }\n        services[index].state = .starting\n\n        // 使用 brew services 启动服务\n        let serviceName = getBrewServiceName(service.name)\n        executeBrewCommand(" = "]\n        for brewPath in brewPaths {\n            if FileManager.default.fileExists(atPath: brewPath) {\n                AppState.cachedBrewPath = brewPath\n                return brewPath\n            }\n        }\n        return nil\n    }\n\n    func startService(_ service: ServiceStatus) {\n        guard let index = services.firstIndex(where: { $0.id == service.id }) else { return }\n        services[index].state = .starting\n\n        // Iniciar el servicio usando «brew services»\n        let serviceName = getBrewServiceName(service.name)\n        executeBrewCommand(";
"\n    ]\n\n    /// 获取兼容的 mysqldump 客户端路径列表（优先 8.0/8.4）\n    static let mysqldump: [String] = [\n        " = "\n    ]\n\n    /// Obtener una lista de rutas compatibles para el cliente mysqldump (prioridad: 8.0/8.4)\n    static let mysqldump: [String] = [\n        ";
"\n                    )\n                }\n\n                // Top IP列表\n                if !results.topIPs.isEmpty {\n                    VStack(alignment: .leading, spacing: 12) {\n                        Text(" = "\n                    )\n                }\n\n                // Lista de las IPs principales\n                if !results.topIPs.isEmpty {\n                    VStack(alignment: .leading, spacing: 12) {\n                        Text(";
" {\n                    // 延伸背景遮挡下方 Content Card 的边框\n                    Rectangle()\n                        .fill(Color.white)\n                        .frame(height: 4)\n                        .offset(y: 2) // 相对于 Tab 底部向下偏移\n                }\n            },\n            alignment: .bottom\n        )\n        .contentShape(Rectangle())\n        .onTapGesture { onSelect() }\n        .onHover { hovering in isHovered = hovering }\n    }\n}\n\n// 顶部圆角矩形（不画底边，用于边框）\nstruct TabBorderShape: Shape {\n    var radius: CGFloat\n    \n    func path(in rect: CGRect) -> Path {\n        var path = Path()\n        let w = rect.width\n        let h = rect.height\n        let r = min(min(w/2, h/2), radius)\n        \n        // 从左下角开始\n        path.move(to: CGPoint(x: 0, y: h))\n        // 向上画左边线\n        path.addLine(to: CGPoint(x: 0, y: r))\n        // 左上圆角\n        path.addArc(center: CGPoint(x: r, y: r), radius: r, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)\n        // 顶部横线\n        path.addLine(to: CGPoint(x: w - r, y: 0))\n        // 右上圆角\n        path.addArc(center: CGPoint(x: w - r, y: r), radius: r, startAngle: Angle(degrees: 270), endAngle: Angle(degrees: 0), clockwise: false)\n        // 向下画右边线\n        path.addLine(to: CGPoint(x: w, y: h))\n        // 不闭合路径，不画底边\n        \n        return path\n    }\n}\n\n// 顶部圆角矩形（闭合路径，用于背景填充）\nstruct TopRoundedRectangle: Shape {\n    var radius: CGFloat\n    \n    func path(in rect: CGRect) -> Path {\n        var path = Path()\n        let w = rect.width\n        let h = rect.height\n        let r = min(min(w/2, h/2), radius)\n        \n        path.move(to: CGPoint(x: 0, y: h))\n        path.addLine(to: CGPoint(x: 0, y: r))\n        path.addArc(center: CGPoint(x: r, y: r), radius: r, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)\n        path.addLine(to: CGPoint(x: w - r, y: 0))\n        path.addArc(center: CGPoint(x: w - r, y: r), radius: r, startAngle: Angle(degrees: 270), endAngle: Angle(degrees: 0), clockwise: false)\n        path.addLine(to: CGPoint(x: w, y: h))\n        path.closeSubpath()\n        return path\n    }\n}\n\n// MARK: - 连接数据库详情视图\nstruct ConnectionDatabasesView: View {\n    let tab: OpenedDatabaseTab\n    let password: String  // 连接密码（本地可为空，远程必须提供）\n    @ObservedObject var databaseManager: DatabaseManager\n    var hideSystemDatabases: Bool = true  // 是否隐藏系统数据库\n    @State private var databases: [String] = []\n    @State private var remoteDatabases: [MySQLDatabaseInfo] = []  // 远程数据库详细信息\n    @State private var isLoading = true\n    @State private var errorMessage: String?\n    @State private var searchText = " = " {\n                    // Extender el fondo para cubrir el borde inferior de la tarjeta de contenido\n                    Rectangle()\n                        .fill(Color.white)\n                        .frame(height: 4)\n                        .offset(y: 2) // Desplazamiento hacia abajo respecto al borde inferior de la pestaña\n                }\n            },\n            alignment: .bottom\n        )\n        .contentShape(Rectangle())\n        .onTapGesture { onSelect() }\n        .onHover { hovering in isHovered = hovering }\n    }\n}\n\n// Forma rectangular con bordes redondeados superiores (sin dibujar el borde inferior, para contornos)\nstruct TabBorderShape: Shape {\n    var radius: CGFloat\n    \n    func path(in rect: CGRect) -> Path {\n        var path = Path()\n        let w = rect.width\n        let h = rect.height\n        let r = min(min(w/2, h/2), radius)\n        \n        // Comenzar desde la esquina inferior izquierda\n        path.move(to: CGPoint(x: 0, y: h))\n        // Dibujar línea vertical izquierda hacia arriba\n        path.addLine(to: CGPoint(x: 0, y: r))\n        // Borde redondeado superior izquierdo\n        path.addArc(center: CGPoint(x: r, y: r), radius: r, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)\n        // Línea horizontal superior\n        path.addLine(to: CGPoint(x: w - r, y: 0))\n        // Borde redondeado superior derecho\n        path.addArc(center: CGPoint(x: w - r, y: r), radius: r, startAngle: Angle(degrees: 270), endAngle: Angle(degrees: 0), clockwise: false)\n        // Dibujar línea vertical derecha hacia abajo\n        path.addLine(to: CGPoint(x: w, y: h))\n        // No cerrar la ruta; no dibujar el borde inferior\n        \n        return path\n    }\n}\n\n// Rectángulo con bordes redondeados superiores (ruta cerrada, para relleno de fondo)\nstruct TopRoundedRectangle: Shape {\n    var radius: CGFloat\n    \n    func path(in rect: CGRect) -> Path {\n        var path = Path()\n        let w = rect.width\n        let h = rect.height\n        let r = min(min(w/2, h/2), radius)\n        \n        path.move(to: CGPoint(x: 0, y: h))\n        path.addLine(to: CGPoint(x: 0, y: r))\n        path.addArc(center: CGPoint(x: r, y: r), radius: r, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)\n        path.addLine(to: CGPoint(x: w - r, y: 0))\n        path.addArc(center: CGPoint(x: w - r, y: r), radius: r, startAngle: Angle(degrees: 270), endAngle: Angle(degrees: 0), clockwise: false)\n        path.addLine(to: CGPoint(x: w, y: h))\n        path.closeSubpath()\n        return path\n    }\n}\n\n// MARK: - Vista de detalles de conexión a bases de datos\nstruct ConnectionDatabasesView: View {\n    let tab: OpenedDatabaseTab\n    let password: String  // Contraseña de conexión (puede estar vacía localmente, pero es obligatoria para conexiones remotas)\n    @ObservedObject var databaseManager: DatabaseManager\n    var hideSystemDatabases: Bool = true  // ¿Ocultar las bases de datos del sistema?\n    @State private var databases: [String] = []\n    @State private var remoteDatabases: [MySQLDatabaseInfo] = []  // Información detallada de bases de datos remotas\n    @State private var isLoading = true\n    @State private var errorMessage: String?\n    @State private var searchText = ";
",\n                    isLoopback: true\n                ))\n\n                // 获取所有网络接口的 IP 地址\n                var ifaddr: UnsafeMutablePointer<ifaddrs>?\n                guard getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr else {\n                    continuation.resume(returning: result)\n                    return\n                }\n\n                defer { freeifaddrs(ifaddr) }\n\n                var ptr = firstAddr\n                while true {\n                    let interface = ptr.pointee\n                    let family = interface.ifa_addr.pointee.sa_family\n\n                    // 只处理 IPv4 地址\n                    if family == UInt8(AF_INET) {\n                        let name = String(cString: interface.ifa_name)\n\n                        // 跳过回环接口（已经手动添加了 127.0.0.1）\n                        if name != " = ",\n                    isLoopback: true\n                ))\n\n                // Obtener direcciones IP de todas las interfaces de red\n                var ifaddr: UnsafeMutablePointer<ifaddrs>?\n                guard getifaddrs(&ifaddr) == 0, let firstAddr = ifaddr else {\n                    continuation.resume(returning: result)\n                    return\n                }\n\n                defer { freeifaddrs(ifaddr) }\n\n                var ptr = firstAddr\n                while true {\n                    let interface = ptr.pointee\n                    let family = interface.ifa_addr.pointee.sa_family\n\n                    // Procesar únicamente direcciones IPv4\n                    if family == UInt8(AF_INET) {\n                        let name = String(cString: interface.ifa_name)\n\n                        // Omitir la interfaz de bucle invertido (ya se agregó manualmente 127.0.0.1)\n                        if name != ";
")\n    }\n\n    /// 打开目录\n    private func openDirectory(_ directoryPath: String) {\n        guard directoryPath != " = ")\n    }\n\n    /// Abrir un directorio\n    private func openDirectory(_ directoryPath: String) {\n        guard directoryPath != ";
"AI 分析" = "Análisis con IA";
"检测到 \\(service.name) 配置文件位于原始路径：\\n\\(migration.sourceFile)\\n\\n是否将其迁移到工作空间？\\n目标路径：\\(migration.targetFile)" = "Se detectó el archivo de configuración de \\(service.name) en la ruta original:\n\\(migration.sourceFile)\n\n¿Desea migrarlo al espacio de trabajo?\nRuta de destino: \\(migration.targetFile)";
"重载 \\(service.displayName) 失败：\\(message)" = "Error al recargar \\(service.displayName): \\(message)";
"]),\n\n        // ===== 连接配置 =====\n        " = "]),\n\n        // ===== Configuración de conexión =====\n        ";
")\n    }\n\n    private func loadBackgroundImage(named filename: String) -> NSImage? {\n        // 先尝试从 Asset Catalog 加载\n        let baseName = filename.replacingOccurrences(of: " = ")\n    }\n\n    private func loadBackgroundImage(named filename: String) -> NSImage? {\n        // 先尝试从 Asset Catalog 加载\n        let baseName = filename.replacingOccurrences(of: ";
"\n\n                # 密码保护: \\(rule.directory)\n                location \\(rule.directory) {\n                    auth_basic " = "\n\n                # 密码保护: \\(rule.directory)\n                location \\(rule.directory) {\n                    auth_basic ";
")\n                // 尝试恢复备份\n                do {\n                    try await storage.restoreBackup()\n                    let loadedDatabases = try await storage.load()\n                    await MainActor.run {\n                        databases = loadedDatabases\n                        errorMessage = " = ")\n                // 尝试恢复备份\n                do {\n                    try await storage.restoreBackup()\n                    let loadedDatabases = try await storage.load()\n                    await MainActor.run {\n                        databases = loadedDatabases\n                        errorMessage = ";
"]),\n\n        // ===== 日志配置 =====\n        " = "]),\n\n        // ===== 日志配置 =====\n        ";
"\n        return FileManager.default.fileExists(atPath: acmePath)\n    }\n\n    // MARK: - 安装 acme.sh\n    /// 安装 acme.sh\n    /// - Throws: 安装失败时抛出错误\n    func installAcme() async throws {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = "\n        return FileManager.default.fileExists(atPath: acmePath)\n    }\n\n    // MARK: - 安装 acme.sh\n    /// 安装 acme.sh\n    /// - Throws: 安装失败时抛出错误\n    func installAcme() async throws {\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: ";
" {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(maxWidth: isEmbedded ? .infinity : 1000, maxHeight: isEmbedded ? .infinity : 700)\n    }\n\n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 内容区域\n            contentArea\n        }\n    }\n\n    private var v2Body: some View {\n        Group {\n            if isEmbedded {\n                v2Content\n            } else {\n                v2Content\n                    .glass(.panel)\n            }\n        }\n    }\n\n    private var v2Content: some View {\n        VStack(spacing: 0) {\n            if !isEmbedded {\n                headerBarV2\n                Divider().background(Color.gray.opacity(0.2))\n            }\n\n            contentAreaV2\n        }\n        .background(isEmbedded ? Color.clear : Color.white.opacity(0.1))\n    }\n\n    init(isEmbedded: Bool = false) {\n        self.isEmbedded = isEmbedded\n    }\n\n    // 标题栏\n    private var headerBar: some View {\n        HStack {\n            Text(" = " {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(maxWidth: isEmbedded ? .infinity : 1000, maxHeight: isEmbedded ? .infinity : 700)\n    }\n\n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 内容区域\n            contentArea\n        }\n    }\n\n    private var v2Body: some View {\n        Group {\n            if isEmbedded {\n                v2Content\n            } else {\n                v2Content\n                    .glass(.panel)\n            }\n        }\n    }\n\n    private var v2Content: some View {\n        VStack(spacing: 0) {\n            if !isEmbedded {\n                headerBarV2\n                Divider().background(Color.gray.opacity(0.2))\n            }\n\n            contentAreaV2\n        }\n        .background(isEmbedded ? Color.clear : Color.white.opacity(0.1))\n    }\n\n    init(isEmbedded: Bool = false) {\n        self.isEmbedded = isEmbedded\n    }\n\n    // 标题栏\n    private var headerBar: some View {\n        HStack {\n            Text(";
"远程服务器" = "远程服务器";
"#\n                            let regex = try? NSRegularExpression(pattern: pattern, options: [])\n\n                            for line in output.components(separatedBy: .newlines) {\n                                // 只匹配 mysqld 进程（不是其他包含 mysql 的进程）\n                                if line.hasPrefix(" = "#\n                            let regex = try? NSRegularExpression(pattern: pattern, options: [])\n\n                            for line in output.components(separatedBy: .newlines) {\n                                // 只匹配 mysqld 进程（不是其他包含 mysql 的进程）\n                                if line.hasPrefix(";
"端口配置失败: \\(error.localizedDescription)" = "端口配置失败: \\(error.localizedDescription)";
"单位: \\(unit)" = "单位: \\(unit)";
"停止占用端口的服务，PHP \\(phpVersion) 将使用默认端口 \\(conflictPort)" = "停止占用端口的服务，PHP \\(phpVersion) 将使用默认端口 \\(conflictPort)";
" }\n\n    // MARK: - Redis 路径\n\n    /// Redis 配置目录\n    var redisConfPath: String { " = " }\n\n    // MARK: - Redis 路径\n\n    /// Redis 配置目录\n    var redisConfPath: String { ";
" }\n\n    /// Redis 数据目录\n    var redisDataPath: String { " = " }\n\n    /// Redis 数据目录\n    var redisDataPath: String { ";
",\n                iconColor: systemStatusColor,\n                iconBgColor: systemStatusColor.opacity(AppTokens.Opacity.light)\n            )\n        }\n    }\n    \n    private var activeSiteCount: Int {\n        siteManager.sites.filter { !$0.isDeleted }.count\n    }\n    \n    private var installedPhpCount: Int {\n        softwareManager.phpVersions.filter { $0.installed }.count\n    }\n    \n    private var installedDbCount: Int {\n        softwareManager.databases.filter { $0.installed }.count\n    }\n    \n    private var systemStatusText: LocalizedStringKey {\n        // 检查是否有服务正在加载\n        let loadingCount = appState.services.filter { $0.state == .loading }.count\n        if loadingCount > 0 {\n            return " = ",\n                iconColor: systemStatusColor,\n                iconBgColor: systemStatusColor.opacity(AppTokens.Opacity.light)\n            )\n        }\n    }\n    \n    private var activeSiteCount: Int {\n        siteManager.sites.filter { !$0.isDeleted }.count\n    }\n    \n    private var installedPhpCount: Int {\n        softwareManager.phpVersions.filter { $0.installed }.count\n    }\n    \n    private var installedDbCount: Int {\n        softwareManager.databases.filter { $0.installed }.count\n    }\n    \n    private var systemStatusText: LocalizedStringKey {\n        // 检查是否有服务正在加载\n        let loadingCount = appState.services.filter { $0.state == .loading }.count\n        if loadingCount > 0 {\n            return ";
", path]\n\n        do {\n            try process.run()\n        } catch {\n            NSWorkspace.shared.open(URL(fileURLWithPath: path))\n        }\n    }\n\n    // 加载备份列表\n    private func loadBackups() {\n        configBackups = ConfigBackupManager.shared.getBackups(for: service.name)\n        showBackupSheet = true\n    }\n\n    // 恢复备份\n    private func restoreBackup(_ backup: ConfigBackup) {\n        statusMessages.removeAll()\n        isOperating = true\n        addStatusMessage(" = ", path]\n\n        do {\n            try process.run()\n        } catch {\n            NSWorkspace.shared.open(URL(fileURLWithPath: path))\n        }\n    }\n\n    // 加载备份列表\n    private func loadBackups() {\n        configBackups = ConfigBackupManager.shared.getBackups(for: service.name)\n        showBackupSheet = true\n    }\n\n    // 恢复备份\n    private func restoreBackup(_ backup: ConfigBackup) {\n        statusMessages.removeAll()\n        isOperating = true\n        addStatusMessage(";
"\n        }\n\n        // 安装 pure-ftpd\n        let success = await runBrewCommandWithProgress(\n            brewPath,\n            arguments: [" = "\n        }\n\n        // 安装 pure-ftpd\n        let success = await runBrewCommandWithProgress(\n            brewPath,\n            arguments: [";
")\n                            .font(.system(size: 13))\n                            .padding(.horizontal, 12)\n                            .padding(.vertical, 8)\n                            .glass(.chip)\n                    }\n                    .buttonStyle(.plain)\n                }\n                .padding(.horizontal, 20)\n                .padding(.vertical, 14)\n                .glass(.section)\n                \n                versionList\n            }\n        }\n    }\n    \n    // V2 玻璃拟物化布局\n    private var v2Body: some View {\n        HStack(spacing: 0) {\n            // 左侧分类列表\n            VStack(alignment: .leading, spacing: 6) {\n                // 顶部留白，匹配原型\n                Color.clear.frame(height: 16)\n                \n                // 菜单列表\n                ForEach(ComponentCategory.allCases, id: \\.self) { category in\n                    Button(action: {\n                        selectedCategory = category\n                    }) {\n                        HStack {\n                            Text(LocalizedStringKey(category.rawValue))\n                                .font(.system(size: 13, weight: category == selectedCategory ? .medium : .regular))\n                                .foregroundColor(category == selectedCategory ? .blue : .primary)\n                            \n                            Spacer()\n                            \n                            // 数量统计 Badge\n                            Text(" = ")\n                            .font(.system(size: 13))\n                            .padding(.horizontal, 12)\n                            .padding(.vertical, 8)\n                            .glass(.chip)\n                    }\n                    .buttonStyle(.plain)\n                }\n                .padding(.horizontal, 20)\n                .padding(.vertical, 14)\n                .glass(.section)\n                \n                versionList\n            }\n        }\n    }\n    \n    // V2 玻璃拟物化布局\n    private var v2Body: some View {\n        HStack(spacing: 0) {\n            // 左侧分类列表\n            VStack(alignment: .leading, spacing: 6) {\n                // 顶部留白，匹配原型\n                Color.clear.frame(height: 16)\n                \n                // 菜单列表\n                ForEach(ComponentCategory.allCases, id: \\.self) { category in\n                    Button(action: {\n                        selectedCategory = category\n                    }) {\n                        HStack {\n                            Text(LocalizedStringKey(category.rawValue))\n                                .font(.system(size: 13, weight: category == selectedCategory ? .medium : .regular))\n                                .foregroundColor(category == selectedCategory ? .blue : .primary)\n                            \n                            Spacer()\n                            \n                            // 数量统计 Badge\n                            Text(";
"\n            }\n\n            // 添加 if 条件（如果有其他类型的条件）\n            for condition in conditions {\n                if !condition.contains(" = "\n            }\n\n            // 添加 if 条件（如果有其他类型的条件）\n            for condition in conditions {\n                if !condition.contains(";
") private var appThemeModeRaw = AppThemeMode.light.rawValue\n\n    var body: some View {\n        ZStack {\n            backgroundLayer\n\n            HStack(spacing: 0) {\n                SidebarView(\n                    selection: $selectedTab,\n                    selectedTool: $selectedTool,\n                    appThemeModeRaw: $appThemeModeRaw\n                )\n\n                Divider()\n                    .background(Color.white.opacity(AppTokens.Opacity.border))\n\n                DetailView(selectedTab: selectedTab, selectedTool: $selectedTool)\n                    .frame(maxWidth: .infinity, maxHeight: .infinity)\n            }\n        }\n        .preferredColorScheme(appThemeMode == .auto ? nil : (appThemeMode == .dark ? .dark : .light))\n        .onAppear {\n            // 首次启动时，如果工作空间未配置，显示设置引导\n            if !workspaceManager.isConfigured {\n                showWorkspaceSetup = true\n            }\n        }\n        .sheetWithLocale(isPresented: $showWorkspaceSetup) {\n            WorkspaceWelcomeSheet()\n        }\n    }\n\n    private var appThemeMode: AppThemeMode {\n        AppThemeMode(rawValue: appThemeModeRaw) ?? .light\n    }\n\n    private var backgroundLayer: some View {\n        ZStack {\n            // 全局唯一底图层：仅保留一张背景图，避免分区各自设置背景导致风格不统一\n            if let activeImage = activeBackgroundImage {\n                Image(nsImage: activeImage)\n                    .resizable()\n                    .scaledToFill()\n                    .ignoresSafeArea()\n                    .opacity(appThemeMode == .dark ? 0.22 : 0.18)\n            } else {\n                // 兜底纯色，避免无图时出现透明黑底\n                (appThemeMode == .dark\n                    ? Color(red: 0.08, green: 0.1, blue: 0.16)\n                    : Color(red: 0.94, green: 0.96, blue: 0.99))\n                .ignoresSafeArea()\n            }\n\n            // 全局统一蒙层：浅色用白，深色用黑，不在各页面再单独叠材质\n            Rectangle()\n                .fill(appThemeMode == .dark ? Color.black.opacity(AppTokens.Opacity.disabled) : Color.white.opacity(AppTokens.Opacity.textSecondary))\n                .ignoresSafeArea()\n        }\n    }\n\n    private var activeBackgroundImage: NSImage? {\n        if appThemeMode == .dark {\n            return loadBackgroundImage(named: " = ") private var appThemeModeRaw = AppThemeMode.light.rawValue\n\n    var body: some View {\n        ZStack {\n            backgroundLayer\n\n            HStack(spacing: 0) {\n                SidebarView(\n                    selection: $selectedTab,\n                    selectedTool: $selectedTool,\n                    appThemeModeRaw: $appThemeModeRaw\n                )\n\n                Divider()\n                    .background(Color.white.opacity(AppTokens.Opacity.border))\n\n                DetailView(selectedTab: selectedTab, selectedTool: $selectedTool)\n                    .frame(maxWidth: .infinity, maxHeight: .infinity)\n            }\n        }\n        .preferredColorScheme(appThemeMode == .auto ? nil : (appThemeMode == .dark ? .dark : .light))\n        .onAppear {\n            // 首次启动时，如果工作空间未配置，显示设置引导\n            if !workspaceManager.isConfigured {\n                showWorkspaceSetup = true\n            }\n        }\n        .sheetWithLocale(isPresented: $showWorkspaceSetup) {\n            WorkspaceWelcomeSheet()\n        }\n    }\n\n    private var appThemeMode: AppThemeMode {\n        AppThemeMode(rawValue: appThemeModeRaw) ?? .light\n    }\n\n    private var backgroundLayer: some View {\n        ZStack {\n            // 全局唯一底图层：仅保留一张背景图，避免分区各自设置背景导致风格不统一\n            if let activeImage = activeBackgroundImage {\n                Image(nsImage: activeImage)\n                    .resizable()\n                    .scaledToFill()\n                    .ignoresSafeArea()\n                    .opacity(appThemeMode == .dark ? 0.22 : 0.18)\n            } else {\n                // 兜底纯色，避免无图时出现透明黑底\n                (appThemeMode == .dark\n                    ? Color(red: 0.08, green: 0.1, blue: 0.16)\n                    : Color(red: 0.94, green: 0.96, blue: 0.99))\n                .ignoresSafeArea()\n            }\n\n            // 全局统一蒙层：浅色用白，深色用黑，不在各页面再单独叠材质\n            Rectangle()\n                .fill(appThemeMode == .dark ? Color.black.opacity(AppTokens.Opacity.disabled) : Color.white.opacity(AppTokens.Opacity.textSecondary))\n                .ignoresSafeArea()\n        }\n    }\n\n    private var activeBackgroundImage: NSImage? {\n        if appThemeMode == .dark {\n            return loadBackgroundImage(named: ";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 内容区域\n    private var contentArea: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 24) {\n                // 统计卡片\n                statisticsCards\n\n                Divider()\n\n                // 访问量图表\n                trafficChart\n\n                Divider()\n\n                // 详细统计\n                detailedStatistics\n            }\n            .padding()\n        }\n    }\n\n    // 统计卡片\n    private var statisticsCards: some View {\n        HStack(spacing: 16) {\n            StatCard(\n                title: " = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 内容区域\n    private var contentArea: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 24) {\n                // 统计卡片\n                statisticsCards\n\n                Divider()\n\n                // 访问量图表\n                trafficChart\n\n                Divider()\n\n                // 详细统计\n                detailedStatistics\n            }\n            .padding()\n        }\n    }\n\n    // 统计卡片\n    private var statisticsCards: some View {\n        HStack(spacing: 16) {\n            StatCard(\n                title: ";
" }\n\n    /// Nginx 站点配置目录\n    var nginxVhostsPath: String { " = " }\n\n    /// Nginx 站点配置目录\n    var nginxVhostsPath: String { ";
", maxSplits: 1)\n                if parts.count == 2 && String(parts[0]) == key {\n                    return String(parts[1])\n                }\n            }\n        }\n        return nil\n    }\n\n    // 恢复建议值\n    private func applySuggestedValues() {\n        let isCommon = displayType == .common\n\n        for i in 0..<configSections.count {\n            for j in 0..<configSections[i].items.count {\n                let item = configSections[i].items[j]\n                // 只更新当前显示类型的配置项\n                if item.isCommon == isCommon {\n                    // 使用动态建议值（路径会根据工作空间配置）\n                    if let suggestedValue = ServiceConfigDefinitions.getDynamicSuggestedValue(\n                        for: item.key,\n                        serviceName: service.name,\n                        version: service.version\n                    ) {\n                        configSections[i].items[j].value = suggestedValue\n                    }\n                }\n            }\n        }\n        hasChanges = true\n    }\n\n    // 将表单值同步到配置内容\n    private func syncFormToContent() {\n        // 如果是表单模式，需要将表单中的值更新到 configContent\n        guard editMode == .form else { return }\n\n        var lines = configContent.components(separatedBy: " = ", maxSplits: 1)\n                if parts.count == 2 && String(parts[0]) == key {\n                    return String(parts[1])\n                }\n            }\n        }\n        return nil\n    }\n\n    // 恢复建议值\n    private func applySuggestedValues() {\n        let isCommon = displayType == .common\n\n        for i in 0..<configSections.count {\n            for j in 0..<configSections[i].items.count {\n                let item = configSections[i].items[j]\n                // 只更新当前显示类型的配置项\n                if item.isCommon == isCommon {\n                    // 使用动态建议值（路径会根据工作空间配置）\n                    if let suggestedValue = ServiceConfigDefinitions.getDynamicSuggestedValue(\n                        for: item.key,\n                        serviceName: service.name,\n                        version: service.version\n                    ) {\n                        configSections[i].items[j].value = suggestedValue\n                    }\n                }\n            }\n        }\n        hasChanges = true\n    }\n\n    // 将表单值同步到配置内容\n    private func syncFormToContent() {\n        // 如果是表单模式，需要将表单中的值更新到 configContent\n        guard editMode == .form else { return }\n\n        var lines = configContent.components(separatedBy: ";
"路径: \\(fullPath)" = "路径: \\(fullPath)";
"]),\n        // 安全配置\n        " = "]),\n        // 安全配置\n        ";
":\n            // 根据版本动态生成路径\n            let majorMinor = extractMajorMinorVersion(service.version)\n            configPaths = [\n                " = ":\n            // 根据版本动态生成路径\n            let majorMinor = extractMajorMinorVersion(service.version)\n            configPaths = [\n                ";
"\n            ]\n\n        default:\n            return nil\n        }\n\n        // 返回第一个存在的配置文件路径\n        return configPaths.first { FileManager.default.fileExists(atPath: $0) }\n    }\n\n    /// 提取主次版本号\n    private nonisolated func extractMajorMinorVersion(_ version: String) -> String {\n        let cleanVersion = version.split(separator: " = "\n            ]\n\n        default:\n            return nil\n        }\n\n        // 返回第一个存在的配置文件路径\n        return configPaths.first { FileManager.default.fileExists(atPath: $0) }\n    }\n\n    /// 提取主次版本号\n    private nonisolated func extractMajorMinorVersion(_ version: String) -> String {\n        let cleanVersion = version.split(separator: ";
"]\n        startProcess.standardOutput = Pipe()\n        startProcess.standardError = Pipe()\n\n        do {\n            try startProcess.run()\n            startProcess.waitUntilExit()\n            return startProcess.terminationStatus == 0\n        } catch {\n            return false\n        }\n    }\n\n    // 修改远程数据库 root 密码\n    private func changeRemoteRootPassword() async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqlPath)\n\n                // 构建修改密码的 SQL（远程数据库）\n                let sql = " = "]\n        startProcess.standardOutput = Pipe()\n        startProcess.standardError = Pipe()\n\n        do {\n            try startProcess.run()\n            startProcess.waitUntilExit()\n            return startProcess.terminationStatus == 0\n        } catch {\n            return false\n        }\n    }\n\n    // 修改远程数据库 root 密码\n    private func changeRemoteRootPassword() async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqlPath)\n\n                // 构建修改密码的 SQL（远程数据库）\n                let sql = ";
")\n        }\n\n        // 未管理条目且要求保持原管理状态时，直接在原始 hosts 中替换该条目\n        if !oldEntry.isManaged && !forceManaged {\n            let newEntry = HostEntry(\n                ip: ip,\n                hostname: hostname,\n                comment: comment,\n                isEnabled: oldEntry.isEnabled,\n                isManaged: false,\n                group: oldEntry.group\n            )\n            try updateUnmanagedEntryInRawHosts(oldEntry: oldEntry, newEntry: newEntry)\n            try await loadHostsAsync()\n            return\n        }\n\n        entries[targetIndex].ip = ip\n        entries[targetIndex].hostname = hostname\n        entries[targetIndex].comment = comment\n        if forceManaged {\n            entries[targetIndex].isManaged = true\n        }\n\n        // 保存到文件（需要管理员权限）\n        try await saveHosts()\n        try await loadHostsAsync()\n    }\n\n    // 读取原始 hosts 文件内容（用于原始模式）\n    func loadRawHostsContent() throws -> String {\n        try String(contentsOfFile: hostsPath, encoding: .utf8)\n    }\n\n    // 保存原始 hosts 文件内容（用于原始模式）\n    func saveRawHostsContent(_ content: String) async throws {\n        try writeWithAdminPrivileges(content: content, toPath: hostsPath)\n        try await loadHostsAsync()\n    }\n\n    private nonisolated func parseHostLine(_ line: String) -> (ip: String, hostname: String, comment: String?)? {\n        let trimmed = line.trimmingCharacters(in: .whitespaces)\n        if trimmed.isEmpty || trimmed.hasPrefix(" = ")\n        }\n\n        // 未管理条目且要求保持原管理状态时，直接在原始 hosts 中替换该条目\n        if !oldEntry.isManaged && !forceManaged {\n            let newEntry = HostEntry(\n                ip: ip,\n                hostname: hostname,\n                comment: comment,\n                isEnabled: oldEntry.isEnabled,\n                isManaged: false,\n                group: oldEntry.group\n            )\n            try updateUnmanagedEntryInRawHosts(oldEntry: oldEntry, newEntry: newEntry)\n            try await loadHostsAsync()\n            return\n        }\n\n        entries[targetIndex].ip = ip\n        entries[targetIndex].hostname = hostname\n        entries[targetIndex].comment = comment\n        if forceManaged {\n            entries[targetIndex].isManaged = true\n        }\n\n        // 保存到文件（需要管理员权限）\n        try await saveHosts()\n        try await loadHostsAsync()\n    }\n\n    // 读取原始 hosts 文件内容（用于原始模式）\n    func loadRawHostsContent() throws -> String {\n        try String(contentsOfFile: hostsPath, encoding: .utf8)\n    }\n\n    // 保存原始 hosts 文件内容（用于原始模式）\n    func saveRawHostsContent(_ content: String) async throws {\n        try writeWithAdminPrivileges(content: content, toPath: hostsPath)\n        try await loadHostsAsync()\n    }\n\n    private nonisolated func parseHostLine(_ line: String) -> (ip: String, hostname: String, comment: String?)? {\n        let trimmed = line.trimmingCharacters(in: .whitespaces)\n        if trimmed.isEmpty || trimmed.hasPrefix(";
")\n    }\n\n    // 执行 MySQL 命令的通用方法\n    private func runMySQLCommand(_ sql: String) async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqlPath)\n                process.arguments = [" = ")\n    }\n\n    // 执行 MySQL 命令的通用方法\n    private func runMySQLCommand(_ sql: String) async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqlPath)\n                process.arguments = [";
"\n    @State private var errorMessage: String?\n    @State private var showError = false\n    @State private var softwareToUninstall: SoftwareVersion?\n    @State private var showUninstallConfirm = false\n    @State private var showNginxInstallSheet = false\n    @State private var showApacheInstallSheet = false\n    @State private var showPureFTPInstallSheet = false\n\n    // 配置界面相关状态（通用，支持所有软件）\n    @State private var selectedSoftwareForConfig: ServiceStatus?\n\n    // 配置迁移相关状态\n    @State private var showMigrationBanner = false\n    @State private var pendingMigration: ConfigPathProvider.ConfigMigrationInfo?\n    @State private var migrationSoftware: SoftwareVersion?\n\n    // 分页相关\n    @State private var currentPage = 1\n    private let itemsPerPage = 50  // 每页显示50个软件（通常不会超过此数量）\n\n    // PHP 端口冲突相关状态\n    @State private var showPHPPortConflictSheet = false\n    @State private var phpPortConflictInfo: PHPPortConflictInfo?\n    @State private var pendingPHPInstall: SoftwareVersion?\n\n    // 修改端口相关状态\n    @State private var showPHPChangePortSheet = false\n    @State private var changingPortPHP: SoftwareVersion?\n\n    var body: some View {\n        Group {\n            if BuildInfo.uiVersion == " = "\n    @State private var errorMessage: String?\n    @State private var showError = false\n    @State private var softwareToUninstall: SoftwareVersion?\n    @State private var showUninstallConfirm = false\n    @State private var showNginxInstallSheet = false\n    @State private var showApacheInstallSheet = false\n    @State private var showPureFTPInstallSheet = false\n\n    // 配置界面相关状态（通用，支持所有软件）\n    @State private var selectedSoftwareForConfig: ServiceStatus?\n\n    // 配置迁移相关状态\n    @State private var showMigrationBanner = false\n    @State private var pendingMigration: ConfigPathProvider.ConfigMigrationInfo?\n    @State private var migrationSoftware: SoftwareVersion?\n\n    // 分页相关\n    @State private var currentPage = 1\n    private let itemsPerPage = 50  // 每页显示50个软件（通常不会超过此数量）\n\n    // PHP 端口冲突相关状态\n    @State private var showPHPPortConflictSheet = false\n    @State private var phpPortConflictInfo: PHPPortConflictInfo?\n    @State private var pendingPHPInstall: SoftwareVersion?\n\n    // 修改端口相关状态\n    @State private var showPHPChangePortSheet = false\n    @State private var changingPortPHP: SoftwareVersion?\n\n    var body: some View {\n        Group {\n            if BuildInfo.uiVersion == ";
", databaseName]\n\n                let outputPipe = Pipe()\n                process.standardOutput = outputPipe\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    if process.terminationStatus == 0 {\n                        let data = outputPipe.fileHandleForReading.readDataToEndOfFile()\n                        try? data.write(to: path)\n                        continuation.resume(returning: true)\n                    } else {\n                        continuation.resume(returning: false)\n                    }\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // 导出数据库\n    func exportDatabase(_ database: Database, to directory: URL) async throws {\n        let fileName = " = ", databaseName]\n\n                let outputPipe = Pipe()\n                process.standardOutput = outputPipe\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    if process.terminationStatus == 0 {\n                        let data = outputPipe.fileHandleForReading.readDataToEndOfFile()\n                        try? data.write(to: path)\n                        continuation.resume(returning: true)\n                    } else {\n                        continuation.resume(returning: false)\n                    }\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // 导出数据库\n    func exportDatabase(_ database: Database, to directory: URL) async throws {\n        let fileName = ";
"\\(days) 天" = "\\(days) 天";
"\n        }\n        return version\n    }\n\n    // 加载配置文件\n    private func loadConfigFile() {\n        isLoading = false\n\n        // 提取版本号\n        let version = extractMajorMinorVersion(service.version)\n\n        // 检查是否需要配置迁移\n        if let migration = ConfigPathProvider.shared.checkConfigMigration(\n            serviceName: service.name,\n            version: version\n        ) {\n            // 需要迁移，显示迁移提示\n            pendingMigration = migration\n            showMigrationAlert = true\n            return\n        }\n\n        // 获取配置文件路径（优先使用工作空间路径）\n        let path = getConfigPathWithWorkspace()\n        currentConfigPath = path\n\n        // 检查文件是否存在\n        if !FileManager.default.fileExists(atPath: path) {\n            // 配置文件不存在，创建默认配置\n            createDefaultConfigFile(at: path)\n            return\n        }\n\n        // 读取文件\n        do {\n            configContent = try String(contentsOfFile: path, encoding: .utf8)\n            // 如果当前是表单模式，立即加载表单配置\n            if editMode == .form {\n                loadFormConfig()\n            }\n        } catch {\n            errorMessage = " = "\n        }\n        return version\n    }\n\n    // 加载配置文件\n    private func loadConfigFile() {\n        isLoading = false\n\n        // 提取版本号\n        let version = extractMajorMinorVersion(service.version)\n\n        // 检查是否需要配置迁移\n        if let migration = ConfigPathProvider.shared.checkConfigMigration(\n            serviceName: service.name,\n            version: version\n        ) {\n            // 需要迁移，显示迁移提示\n            pendingMigration = migration\n            showMigrationAlert = true\n            return\n        }\n\n        // 获取配置文件路径（优先使用工作空间路径）\n        let path = getConfigPathWithWorkspace()\n        currentConfigPath = path\n\n        // 检查文件是否存在\n        if !FileManager.default.fileExists(atPath: path) {\n            // 配置文件不存在，创建默认配置\n            createDefaultConfigFile(at: path)\n            return\n        }\n\n        // 读取文件\n        do {\n            configContent = try String(contentsOfFile: path, encoding: .utf8)\n            // 如果当前是表单模式，立即加载表单配置\n            if editMode == .form {\n                loadFormConfig()\n            }\n        } catch {\n            errorMessage = ";
" }\n\n    /// Nginx 日志目录\n    var nginxLogsPath: String { " = " }\n\n    /// Nginx 日志目录\n    var nginxLogsPath: String { ";
") {\n                            executablePath = String(line.dropFirst(4))\n                        }\n                        // cwd 是当前工作目录\n                        if line.hasPrefix(" = ") {\n                            executablePath = String(line.dropFirst(4))\n                        }\n                        // cwd 是当前工作目录\n                        if line.hasPrefix(";
"\n    }\n\n    /// 获取 MySQL 配置搜索路径列表\n    func getMySQLConfigSearchPaths(version: String? = nil) -> [String] {\n        var paths: [String] = []\n        let ver = version ?? " = "\n    }\n\n    /// 获取 MySQL 配置搜索路径列表\n    func getMySQLConfigSearchPaths(version: String? = nil) -> [String] {\n        var paths: [String] = []\n        let ver = version ?? ";
"))\n        .overlay(\n            Rectangle()\n                .frame(width: 1)\n                .foregroundColor(Color.gray.opacity(AppTokens.Opacity.light)),\n            alignment: .trailing\n        )\n    }\n    \n    // V2 左侧菜单\n    private var v2LeftMenu: some View {\n        ScrollView(showsIndicators: false) {\n            VStack(alignment: .leading, spacing: 6) {\n                ForEach(ConfigTab.allCases, id: \\.self) { tab in\n                    ConfigTabButtonV2(title: LocalizedStringKey(tab.rawValue), isSelected: selectedTab == tab) {\n                        withAnimation(.easeInOut(duration: 0.2)) {\n                            selectedTab = tab\n                        }\n                    }\n                }\n            }\n            .padding(.horizontal, 16)\n            .padding(.top, 16)\n        }\n        .background(Color.black.opacity(0.05))\n    }\n    \n    @ViewBuilder\n    private var configContent: some View {\n        switch selectedTab {\n        case .limit:\n            AccessLimitView(site: site, config: $advancedConfig)\n        case .rewrite:\n            RewriteView(site: site, config: $advancedConfig)\n        case .defaultDoc:\n            DefaultDocumentView(site: site, config: $advancedConfig)\n        case .configFile:\n            ConfigFileView(site: site, config: $advancedConfig)\n        case .ssl:\n            SSLSettingsView(site: site)\n        case .cors:\n            CORSView(site: site, config: $advancedConfig)\n        case .error:\n            ErrorPagesView(site: site, config: $advancedConfig)\n        case .logs:\n            LogsConfigView(site: site, config: $advancedConfig)\n        }\n    }\n}\n\n// MARK: - V2 左侧菜单按钮组件\nstruct ConfigTabButtonV2: View {\n    let title: LocalizedStringKey\n    let isSelected: Bool\n    let action: () -> Void\n    @State private var isHovered = false\n\n    var body: some View {\n        Button(action: action) {\n            Text(title)\n                .font(.system(size: 13))\n                .foregroundColor(isSelected ? .blue : .secondary)\n                .frame(maxWidth: .infinity, alignment: .leading)\n                .padding(.horizontal, 14)\n                .padding(.vertical, 10)\n                .contentShape(Rectangle())\n                .background(\n                    Group {\n                        if isSelected {\n                            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                                .fill(Color.blue.opacity(0.15))\n                        } else if isHovered {\n                            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                                .fill(Color.gray.opacity(0.1))\n                        } else {\n                            Color.clear\n                        }\n                    }\n                )\n        }\n        .buttonStyle(.plain)\n        .onHover { hovering in\n            isHovered = hovering\n        }\n    }\n}\n\n// MARK: - Color Extension\nextension Color {\n    init(hex: String) {\n        let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)\n        var int: UInt64 = 0\n        Scanner(string: hex).scanHexInt64(&int)\n        let a, r, g, b: UInt64\n        switch hex.count {\n        case 3: // RGB (12-bit)\n            (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)\n        case 6: // RGB (24-bit)\n            (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)\n        case 8: // ARGB (32-bit)\n            (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)\n        default:\n            (a, r, g, b) = (255, 0, 0, 0)\n        }\n        self.init(\n            .sRGB,\n            red: Double(r) / 255,\n            green: Double(g) / 255,\n            blue:  Double(b) / 255,\n            opacity: Double(a) / 255\n        )\n    }\n}\n\n// MARK: - 访问限制视图\nstruct AccessLimitView: View {\n    let site: Site\n    @Binding var config: SiteAdvancedConfig\n    @EnvironmentObject var siteManager: SiteManager\n    @State private var showAddRuleForm = false\n    @State private var newRuleDirectory = " = "))\n        .overlay(\n            Rectangle()\n                .frame(width: 1)\n                .foregroundColor(Color.gray.opacity(AppTokens.Opacity.light)),\n            alignment: .trailing\n        )\n    }\n    \n    // V2 左侧菜单\n    private var v2LeftMenu: some View {\n        ScrollView(showsIndicators: false) {\n            VStack(alignment: .leading, spacing: 6) {\n                ForEach(ConfigTab.allCases, id: \\.self) { tab in\n                    ConfigTabButtonV2(title: LocalizedStringKey(tab.rawValue), isSelected: selectedTab == tab) {\n                        withAnimation(.easeInOut(duration: 0.2)) {\n                            selectedTab = tab\n                        }\n                    }\n                }\n            }\n            .padding(.horizontal, 16)\n            .padding(.top, 16)\n        }\n        .background(Color.black.opacity(0.05))\n    }\n    \n    @ViewBuilder\n    private var configContent: some View {\n        switch selectedTab {\n        case .limit:\n            AccessLimitView(site: site, config: $advancedConfig)\n        case .rewrite:\n            RewriteView(site: site, config: $advancedConfig)\n        case .defaultDoc:\n            DefaultDocumentView(site: site, config: $advancedConfig)\n        case .configFile:\n            ConfigFileView(site: site, config: $advancedConfig)\n        case .ssl:\n            SSLSettingsView(site: site)\n        case .cors:\n            CORSView(site: site, config: $advancedConfig)\n        case .error:\n            ErrorPagesView(site: site, config: $advancedConfig)\n        case .logs:\n            LogsConfigView(site: site, config: $advancedConfig)\n        }\n    }\n}\n\n// MARK: - V2 左侧菜单按钮组件\nstruct ConfigTabButtonV2: View {\n    let title: LocalizedStringKey\n    let isSelected: Bool\n    let action: () -> Void\n    @State private var isHovered = false\n\n    var body: some View {\n        Button(action: action) {\n            Text(title)\n                .font(.system(size: 13))\n                .foregroundColor(isSelected ? .blue : .secondary)\n                .frame(maxWidth: .infinity, alignment: .leading)\n                .padding(.horizontal, 14)\n                .padding(.vertical, 10)\n                .contentShape(Rectangle())\n                .background(\n                    Group {\n                        if isSelected {\n                            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                                .fill(Color.blue.opacity(0.15))\n                        } else if isHovered {\n                            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                                .fill(Color.gray.opacity(0.1))\n                        } else {\n                            Color.clear\n                        }\n                    }\n                )\n        }\n        .buttonStyle(.plain)\n        .onHover { hovering in\n            isHovered = hovering\n        }\n    }\n}\n\n// MARK: - Color Extension\nextension Color {\n    init(hex: String) {\n        let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)\n        var int: UInt64 = 0\n        Scanner(string: hex).scanHexInt64(&int)\n        let a, r, g, b: UInt64\n        switch hex.count {\n        case 3: // RGB (12-bit)\n            (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)\n        case 6: // RGB (24-bit)\n            (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)\n        case 8: // ARGB (32-bit)\n            (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)\n        default:\n            (a, r, g, b) = (255, 0, 0, 0)\n        }\n        self.init(\n            .sRGB,\n            red: Double(r) / 255,\n            green: Double(g) / 255,\n            blue:  Double(b) / 255,\n            opacity: Double(a) / 255\n        )\n    }\n}\n\n// MARK: - 访问限制视图\nstruct AccessLimitView: View {\n    let site: Site\n    @Binding var config: SiteAdvancedConfig\n    @EnvironmentObject var siteManager: SiteManager\n    @State private var showAddRuleForm = false\n    @State private var newRuleDirectory = ";
" ? Color.clear : Color(NSColor.controlBackgroundColor))\n    }\n\n    // 数据库连接选择下拉菜单\n    private var databaseSelectorMenu: some View {\n        Menu {\n            // 本地数据库连接\n            Section(" = " ? Color.clear : Color(NSColor.controlBackgroundColor))\n    }\n\n    // 数据库连接选择下拉菜单\n    private var databaseSelectorMenu: some View {\n        Menu {\n            // 本地数据库连接\n            Section(";
"\n        if let regex = try? NSRegularExpression(pattern: ipv4Pattern),\n           regex.firstMatch(in: directoryName, range: NSRange(directoryName.startIndex..., in: directoryName)) != nil {\n            return (directoryName, .needsConfirm)\n        }\n\n        // 规则4: 其他情况需要人工输入\n        return (directoryName, .needsManual)\n    }\n\n    /// 扫描目录以进行批量导入\n    /// - Parameters:\n    ///   - parentDirectory: 父目录路径\n    ///   - documentRootSubPath: 运行目录子路径（如 " = "\n        if let regex = try? NSRegularExpression(pattern: ipv4Pattern),\n           regex.firstMatch(in: directoryName, range: NSRange(directoryName.startIndex..., in: directoryName)) != nil {\n            return (directoryName, .needsConfirm)\n        }\n\n        // 规则4: 其他情况需要人工输入\n        return (directoryName, .needsManual)\n    }\n\n    /// 扫描目录以进行批量导入\n    /// - Parameters:\n    ///   - parentDirectory: 父目录路径\n    ///   - documentRootSubPath: 运行目录子路径（如 ";
"\n                ]\n\n                guard let mysqldSafePath = mysqldSafePaths.first(where: { FileManager.default.fileExists(atPath: $0) }) else {\n                    // 如果找不到 mysqld_safe，尝试使用 init-file 方式\n                    let result = self.forceResetWithInitFile(brewPath: brewPath)\n                    continuation.resume(returning: result)\n                    return\n                }\n\n                // 启动安全模式\n                let safeProcess = Process()\n                safeProcess.executableURL = URL(fileURLWithPath: mysqldSafePath)\n                safeProcess.arguments = [" = "\n                ]\n\n                guard let mysqldSafePath = mysqldSafePaths.first(where: { FileManager.default.fileExists(atPath: $0) }) else {\n                    // 如果找不到 mysqld_safe，尝试使用 init-file 方式\n                    let result = self.forceResetWithInitFile(brewPath: brewPath)\n                    continuation.resume(returning: result)\n                    return\n                }\n\n                // 启动安全模式\n                let safeProcess = Process()\n                safeProcess.executableURL = URL(fileURLWithPath: mysqldSafePath)\n                safeProcess.arguments = [";
"]\n                safeProcess.standardOutput = Pipe()\n                safeProcess.standardError = Pipe()\n\n                do {\n                    try safeProcess.run()\n                } catch {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                // 等待 MySQL 安全模式启动\n                Thread.sleep(forTimeInterval: 3)\n\n                // 步骤3: 修改密码\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    safeProcess.terminate()\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                let resetProcess = Process()\n                resetProcess.executableURL = URL(fileURLWithPath: mysqlPath)\n                let sql = " = "]\n                safeProcess.standardOutput = Pipe()\n                safeProcess.standardError = Pipe()\n\n                do {\n                    try safeProcess.run()\n                } catch {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                // Esperar a que se inicie MySQL en modo seguro\n                Thread.sleep(forTimeInterval: 3)\n\n                // Paso 3: Cambiar la contraseña\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    safeProcess.terminate()\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                let resetProcess = Process()\n                resetProcess.executableURL = URL(fileURLWithPath: mysqlPath)\n                let sql = ";
")\n        \n        // 检查源文件是否存在\n        guard fileManager.fileExists(atPath: errorPageIndexFile) else {\n            print(" = ")\n        \n        // Verificar si el archivo de origen existe\n        guard fileManager.fileExists(atPath: errorPageIndexFile) else {\n            print(";
"端口 \\(newPort) 已被占用，请选择其他端口" = "El puerto \\(newPort) ya está en uso; seleccione otro puerto";
", text: $domains)\n                            .macInput()\n                            .onChange(of: domains) { newValue in\n                                // 将全角/中文逗号转换为英文逗号\n                                let normalized = newValue\n                                    .replacingOccurrences(of: " = ", text: $domains)\n                            .macInput()\n                            .onChange(of: domains) { newValue in\n                                // Convertir comas chinas o de ancho completo a comas inglesas\n                                let normalized = newValue\n                                    .replacingOccurrences(of: ";
"\n\n        guard let url = URL(string: verificationURL) else {\n            // 清理验证文件\n            try? FileManager.default.removeItem(atPath: verificationFilePath)\n            return .failed(error: " = "\n\n        guard let url = URL(string: verificationURL) else {\n            // Limpiar el archivo de verificación\n            try? FileManager.default.removeItem(atPath: verificationFilePath)\n            return .failed(error: ";
"\n    }\n\n    /// 应用内置的 phpMyAdmin 路径\n    private var bundledPhpMyAdminPath: String? {\n        Bundle.main.resourcePath.map { " = "\n    }\n\n    /// Aplicar la ruta integrada de phpMyAdmin\n    private var bundledPhpMyAdminPath: String? {\n        Bundle.main.resourcePath.map { ";
"))\n                .font(.system(size: 14, weight: .semibold))\n\n            // 验证状态显示\n            verificationStatusView\n\n            // 验证按钮\n            if case .notStarted = verificationStatus {\n                Button(action: verifyDomain) {\n                    HStack(spacing: 6) {\n                        Image(systemName: " = "))\n                .font(.system(size: 14, weight: .semibold))\n\n            // Visualización del estado de verificación\n            verificationStatusView\n\n            // Botón de verificación\n            if case .notStarted = verificationStatus {\n                Button(action: verifyDomain) {\n                    HStack(spacing: 6) {\n                        Image(systemName: ";
"\n    }\n\n    /// 获取 Nginx 日志目录\n    func getNginxLogsPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.nginxLogsPath\n        }\n        return " = "\n    }\n\n    /// Obtener el directorio de registros de Nginx\n    func getNginxLogsPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.nginxLogsPath\n        }\n        return ";
"\n        // 优先检查工作空间路径\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.mysqlConfigFile(version: ver)\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        // 回退到 Homebrew 路径\n        return " = "\n        // Priorizar la verificación de la ruta del espacio de trabajo\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.mysqlConfigFile(version: ver)\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        // Alternativa: ruta de Homebrew\n        return ";
"\n\n        do {\n            // 确保 servers 目录存在\n            try FileManager.default.createDirectory(atPath: serversDir, withIntermediateDirectories: true)\n\n            // 写入配置\n            try config.write(toFile: configPath, atomically: true, encoding: .utf8)\n\n            // 重载 Nginx\n            let reloadSuccess = await reloadNginx()\n            if !reloadSuccess {\n                lastError = " = "\n\n        do {\n            // Asegurarse de que el directorio 'servers' exista\n            try FileManager.default.createDirectory(atPath: serversDir, withIntermediateDirectories: true)\n\n            // Escribir la configuración\n            try config.write(toFile: configPath, atomically: true, encoding: .utf8)\n\n            // Recargar Nginx\n            let reloadSuccess = await reloadNginx()\n            if !reloadSuccess {\n                lastError = ";
") {\n                selectCustomLogPath()\n            }\n            .buttonStyle(.bordered)\n            .controlSize(.regular)\n\n            // 如果设置了自定义路径，显示重置按钮\n            if customLogPath != nil {\n                Button(" = ") {\n                selectCustomLogPath()\n            }\n            .buttonStyle(.bordered)\n            .controlSize(.regular)\n\n            // Si se ha establecido una ruta personalizada, mostrar el botón de restablecimiento\n            if customLogPath != nil {\n                Button(";
"✓ 版本匹配: \\(site.phpVersion)" = "✓ Coincidencia de versión: \\(site.phpVersion)";
")\n                        .font(.headline)\n                    Spacer()\n\n                    // 模板选择器\n                    Menu {\n                        ForEach(RuleTemplate.allCases, id: \\.self) { template in\n                            Button(template.rawValue) {\n                                loadTemplate(template)\n                            }\n                        }\n                    } label: {\n                        Label(" = ")\n                        .font(.headline)\n                    Spacer()\n\n                    // Selector de plantillas\n                    Menu {\n                        ForEach(RuleTemplate.allCases, id: \\.self) { template in\n                            Button(template.rawValue) {\n                                loadTemplate(template)\n                            }\n                        }\n                    } label: {\n                        Label(";
"导入选中项 (\\(selectedCandidates.count))" = "Importar elementos seleccionados (\\(selectedCandidates.count))";
"\n        \n        // 尝试从 Asset Catalog 加载图片\n        if let image = NSImage(named: assetName) {\n            return image\n        }\n        \n        // 回退逻辑：尝试从用户的 Documents 目录加载（如果未来支持用户自定义壁纸）\n        let documentDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first\n        let filename = colorScheme == .dark ? " = "\n        \n        // Intentar cargar la imagen desde el catálogo de recursos\n        if let image = NSImage(named: assetName) {\n            return image\n        }\n        \n        // Lógica alternativa: intentar cargar desde el directorio Documents del usuario (si se admite personalización futura de fondos de pantalla)\n        let documentDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first\n        let filename = colorScheme == .dark ? ";
"\n        let grantResult = await executeSQL(grantSQL)\n\n        if !grantResult.success {\n            // 授权失败，回滚\n            _ = await executeSQL(" = "\n        let grantResult = await executeSQL(grantSQL)\n\n        if !grantResult.success {\n            // Fallo al otorgar permisos; deshacer cambios\n            _ = await executeSQL(";
"))\n\n        menu.addItem(NSMenuItem.separator())\n\n        // 完全退出\n        menu.addItem(NSMenuItem(title: " = "))\n\n        menu.addItem(NSMenuItem.separator())\n\n        // Salida completa\n        menu.addItem(NSMenuItem(title: ";
", text: $editValue)\n                    .macInput()\n                    .frame(width: 80)\n                    .onChange(of: editValue) { newValue in\n                        value = newValue\n                    }\n\n                if let unit = unit {\n                    Text(unit)\n                        .font(.caption)\n                        .foregroundColor(.secondary)\n                }\n            }\n            .frame(width: 120, alignment: .leading)\n\n            // 推荐值（可点击应用）\n            Button(action: { applyRecommendedValue() }) {\n                HStack(spacing: 2) {\n                    Text(recommendedValue)\n                        .font(.system(.body, design: .monospaced))\n                    if let unit = unit, recommendedValue != " = ", text: $editValue)\n                    .macInput()\n                    .frame(width: 80)\n                    .onChange(of: editValue) { newValue in\n                        value = newValue\n                    }\n\n                if let unit = unit {\n                    Text(unit)\n                        .font(.caption)\n                        .foregroundColor(.secondary)\n                }\n            }\n            .frame(width: 120, alignment: .leading)\n\n            // Valor recomendado (pulsable para aplicarlo)\n            Button(action: { applyRecommendedValue() }) {\n                HStack(spacing: 2) {\n                    Text(recommendedValue)\n                        .font(.system(.body, design: .monospaced))\n                    if let unit = unit, recommendedValue != ";
"\n        【基本信息】\n        服务名称: \\(port.service)\n        端口号: \\(port.port)\n        状态: \\(port.status)\n\n        【进程信息】\n        进程 PID: \\(port.pid)\n        进程名称: \\(port.processName)\n        父进程 PID: \\(port.parentPid)\n        运行用户: \\(port.user)\n        启动时间: \\(port.startTime)\n\n        【路径信息】\n        可执行文件: \\(port.executablePath)\n        工作目录: \\(port.workingDirectory)\n\n        【命令行】\n        \\(port.commandLine)\n        " = "\n        【Información básica】\n        Nombre del servicio: \\(port.service)\n        Número de puerto: \\(port.port)\n        Estado: \\(port.status)\n\n        【Información del proceso】\n        PID del proceso: \\(port.pid)\n        Nombre del proceso: \\(port.processName)\n        PID del proceso padre: \\(port.parentPid)\n        Usuario en ejecución: \\(port.user)\n        Hora de inicio: \\(port.startTime)\n\n        【Información de rutas】\n        Archivo ejecutable: \\(port.executablePath)\n        Directorio de trabajo: \\(port.workingDirectory)\n\n        【Línea de comandos】\n        \\(port.commandLine)\n        ";
", selection: $appThemeModeRaw) {\n                        Text(currentLanguage.autoText).tag(AppThemeMode.auto.rawValue)\n                        Text(currentLanguage.lightText).tag(AppThemeMode.light.rawValue)\n                        Text(currentLanguage.darkText).tag(AppThemeMode.dark.rawValue)\n                    }\n                    .pickerStyle(.segmented)\n                    .frame(width: 150)\n\n                    // 抖音引流按钮\n                    Button(action: {\n                        if let url = URL(string: " = ", selection: $appThemeModeRaw) {\n                        Text(currentLanguage.autoText).tag(AppThemeMode.auto.rawValue)\n                        Text(currentLanguage.lightText).tag(AppThemeMode.light.rawValue)\n                        Text(currentLanguage.darkText).tag(AppThemeMode.dark.rawValue)\n                    }\n                    .pickerStyle(.segmented)\n                    .frame(width: 150)\n\n                    // Botón de tráfico de Douyin\n                    Button(action: {\n                        if let url = URL(string: ";
"⏭️  跳过非配置文件: \\(file)" = "⏭️  跳过非配置文件: \\(file)";
"# \\(service.name) 配置文件\\n" = "# \\(service.name) 配置文件\\n";
"修改 \\(softwareName) \\(version) 的端口配置" = "修改 \\(softwareName) \\(version) 的端口配置";
"]\n\n                let pipe = Pipe()\n                process.standardOutput = pipe\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    let isRunning = process.terminationStatus == 0\n                    continuation.resume(returning: isRunning)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n\n        isNginxRunning = result\n\n        if result {\n            nginxPort = await detectNginxPort()\n        } else {\n            nginxPort = nil\n        }\n    }\n\n    /// 检测 Nginx 监听端口\n    private func detectNginxPort() async -> Int? {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: " = "]\n\n                let pipe = Pipe()\n                process.standardOutput = pipe\n                process.standardError = Pipe()\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    let isRunning = process.terminationStatus == 0\n                    continuation.resume(returning: isRunning)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n\n        isNginxRunning = result\n\n        if result {\n            nginxPort = await detectNginxPort()\n        } else {\n            nginxPort = nil\n        }\n    }\n\n    /// 检测 Nginx 监听端口\n    private func detectNginxPort() async -> Int? {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: ";
"\n            ]\n\n        default:\n            return nil\n        }\n\n        for path in configPaths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n        return nil\n    }\n\n    // 提取主次版本号\n    private func extractMajorMinorVersion(_ version: String) -> String {\n        let cleanVersion = version.split(separator: " = "\n            ]\n\n        default:\n            return nil\n        }\n\n        for path in configPaths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n        return nil\n    }\n\n    // 提取主次版本号\n    private func extractMajorMinorVersion(_ version: String) -> String {\n        let cleanVersion = version.split(separator: ";
"共 \\(ports.count) 个端口" = "共 \\(ports.count) 个端口";
"剩余\\(days)天" = "剩余\\(days)天";
", maxSplits: 1)\n                        if parts.count >= 2 {\n                            let name = String(parts[0])\n                            let version = String(parts[1]).trimmingCharacters(in: .whitespaces)\n                            packages[name] = version\n                        }\n                    }\n                    continuation.resume(returning: packages)\n                } catch {\n                    continuation.resume(returning: [:])\n                }\n            }\n        }\n    }\n\n    /// 获取运行中的服务列表\n    private nonisolated func fetchRunningServices(brewPath: String) async -> Set<String> {\n        await withCheckedContinuation { continuation in\n            // 在后台线程执行整个操作，避免阻塞\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: brewPath)\n                process.arguments = [" = ", maxSplits: 1)\n                        if parts.count >= 2 {\n                            let name = String(parts[0])\n                            let version = String(parts[1]).trimmingCharacters(in: .whitespaces)\n                            packages[name] = version\n                        }\n                    }\n                    continuation.resume(returning: packages)\n                } catch {\n                    continuation.resume(returning: [:])\n                }\n            }\n        }\n    }\n\n    /// 获取运行中的服务列表\n    private nonisolated func fetchRunningServices(brewPath: String) async -> Set<String> {\n        await withCheckedContinuation { continuation in\n            // 在后台线程执行整个操作，避免阻塞\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: brewPath)\n                process.arguments = [";
"\n\n    init() {\n        checkFirstLaunch()\n    }\n\n    // MARK: - 首次启动检测\n\n    func checkFirstLaunch() {\n        isFirstLaunch = !userDefaults.bool(forKey: firstLaunchKey)\n    }\n\n    func markAsLaunched() {\n        userDefaults.set(true, forKey: firstLaunchKey)\n    }\n\n    // MARK: - 依赖检测\n\n    func checkAllDependencies() async {\n        await checkMkcert()\n        await checkHomebrew()\n    }\n\n    func checkMkcert() async {\n        mkcertStatus = .checking\n\n        // 首先检查内置的 mkcert\n        if let bundledMkcert = getBundledMkcertPath() {\n            NSLog(" = "\n\n    init() {\n        checkFirstLaunch()\n    }\n\n    // MARK: - 首次启动检测\n\n    func checkFirstLaunch() {\n        isFirstLaunch = !userDefaults.bool(forKey: firstLaunchKey)\n    }\n\n    func markAsLaunched() {\n        userDefaults.set(true, forKey: firstLaunchKey)\n    }\n\n    // MARK: - 依赖检测\n\n    func checkAllDependencies() async {\n        await checkMkcert()\n        await checkHomebrew()\n    }\n\n    func checkMkcert() async {\n        mkcertStatus = .checking\n\n        // 首先检查内置的 mkcert\n        if let bundledMkcert = getBundledMkcertPath() {\n            NSLog(";
"确定要删除数据库 \\(database.name) 吗？此操作不可恢复。" = "确定要删除数据库 \\(database.name) 吗？此操作不可恢复。";
")\n                    .foregroundColor(.secondary)\n            }\n            .menuStyle(.borderlessButton)\n        }\n    }\n\n    // 进度详情视图\n    private func progressDetailView(_ progress: InstallProgress) -> some View {\n        VStack(alignment: .leading, spacing: 8) {\n            HStack {\n                Text(" = ")\n                    .foregroundColor(.secondary)\n            }\n            .menuStyle(.borderlessButton)\n        }\n    }\n\n    // 进度详情视图\n    private func progressDetailView(_ progress: InstallProgress) -> some View {\n        VStack(alignment: .leading, spacing: 8) {\n            HStack {\n                Text(";
", text: $hostname)\n                        .macInput()\n                }\n\n                // 备注\n                FormField(" = ", text: $hostname)\n                        .macInput()\n                }\n\n                // 备注\n                FormField(";
",\n                errorMessage: error.localizedDescription,\n                errorDetail: errorDetail\n            )\n            throw error\n        }\n    }\n\n    /// 诊断启动失败原因并返回详细错误\n    private func diagnoseStartFailure(software: SoftwareVersion) async -> SoftwareError {\n        // 获取软件可能使用的端口\n        let portsToCheck = getServicePorts(for: software)\n\n        for port in portsToCheck {\n            let portInfo = await PortDetectionService.shared.checkPort(port)\n            if !portInfo.isAvailable {\n                let suggestion = generatePortConflictSuggestion(\n                    port: port,\n                    processName: portInfo.processName,\n                    software: software\n                )\n                return .portConflict(\n                    port: port,\n                    processName: portInfo.processName,\n                    suggestion: suggestion\n                )\n            }\n        }\n\n        // 如果不是端口问题，返回通用错误（具体的配置检测信息由调用方写入 errorDetail）\n        return .operationFailed(" = ",\n                errorMessage: error.localizedDescription,\n                errorDetail: errorDetail\n            )\n            throw error\n        }\n    }\n\n    /// 诊断启动失败原因并返回详细错误\n    private func diagnoseStartFailure(software: SoftwareVersion) async -> SoftwareError {\n        // 获取软件可能使用的端口\n        let portsToCheck = getServicePorts(for: software)\n\n        for port in portsToCheck {\n            let portInfo = await PortDetectionService.shared.checkPort(port)\n            if !portInfo.isAvailable {\n                let suggestion = generatePortConflictSuggestion(\n                    port: port,\n                    processName: portInfo.processName,\n                    software: software\n                )\n                return .portConflict(\n                    port: port,\n                    processName: portInfo.processName,\n                    suggestion: suggestion\n                )\n            }\n        }\n\n        // 如果不是端口问题，返回通用错误（具体的配置检测信息由调用方写入 errorDetail）\n        return .operationFailed(";
", at: arguments.count - 1)\n                }\n\n                process.arguments = arguments\n\n                let outputPipe = Pipe()\n                let errorPipe = Pipe()\n                process.standardOutput = outputPipe\n                process.standardError = errorPipe\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    if process.terminationStatus == 0 {\n                        let data = outputPipe.fileHandleForReading.readDataToEndOfFile()\n                        if let output = String(data: data, encoding: .utf8) {\n                            // 取第一行非空内容作为版本号\n                            let versionLine = output\n                                .components(separatedBy: .newlines)\n                                .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n                                .first { !$0.isEmpty }\n                            continuation.resume(returning: versionLine)\n                            return\n                        }\n                    }\n                    continuation.resume(returning: nil)\n                } catch {\n                    continuation.resume(returning: nil)\n                }\n            }\n        }\n    }\n    \n    // 测试 MySQL 连接\n    private func testMySQLConnection(password: String) async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: false)\n                    return\n                }\n                \n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqlPath)\n                var arguments = [\n                    " = ", at: arguments.count - 1)\n                }\n\n                process.arguments = arguments\n\n                let outputPipe = Pipe()\n                let errorPipe = Pipe()\n                process.standardOutput = outputPipe\n                process.standardError = errorPipe\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    if process.terminationStatus == 0 {\n                        let data = outputPipe.fileHandleForReading.readDataToEndOfFile()\n                        if let output = String(data: data, encoding: .utf8) {\n                            // 取第一行非空内容作为版本号\n                            let versionLine = output\n                                .components(separatedBy: .newlines)\n                                .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }\n                                .first { !$0.isEmpty }\n                            continuation.resume(returning: versionLine)\n                            return\n                        }\n                    }\n                    continuation.resume(returning: nil)\n                } catch {\n                    continuation.resume(returning: nil)\n                }\n            }\n        }\n    }\n    \n    // 测试 MySQL 连接\n    private func testMySQLConnection(password: String) async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: false)\n                    return\n                }\n                \n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqlPath)\n                var arguments = [\n                    ";
"彻删" = "彻删";
",\n                isEnabled: true,\n                isManaged: true,\n                group: finalName\n            )\n            do {\n                try await hostsManager.addEntry(hostEntry)\n            } catch {\n                // 如果添加 hosts 条目失败，记录错误但不中断站点创建流程\n                NSLog(" = ",\n                isEnabled: true,\n                isManaged: true,\n                group: finalName\n            )\n            do {\n                try await hostsManager.addEntry(hostEntry)\n            } catch {\n                // 如果添加 hosts 条目失败，记录错误但不中断站点创建流程\n                NSLog(";
"\n                }\n                .buttonStyle(.bordered)\n            }\n        }\n        .frame(maxWidth: .infinity)\n        .padding(.vertical, 40)\n    }\n\n    // 本地 MySQL 数据库列表区域\n    private var localMySQLDatabasesSection: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            // 区域标题\n            HStack {\n                Image(systemName: " = "\n                }\n                .buttonStyle(.bordered)\n            }\n        }\n        .frame(maxWidth: .infinity)\n        .padding(.vertical, 40)\n    }\n\n    // 本地 MySQL 数据库列表区域\n    private var localMySQLDatabasesSection: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            // 区域标题\n            HStack {\n                Image(systemName: ";
")\n                            .font(.caption)\n                    }\n                    .frame(width: 100, height: 80)\n                }\n                .buttonStyle(.bordered)\n            }\n            .padding(.top, 16)\n\n            Spacer()\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // 复制连接信息到剪贴板（Navicat 格式）\n    private func copyToNavicat() {\n        let info = " = ")\n                            .font(.caption)\n                    }\n                    .frame(width: 100, height: 80)\n                }\n                .buttonStyle(.bordered)\n            }\n            .padding(.top, 16)\n\n            Spacer()\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // 复制连接信息到剪贴板（Navicat 格式）\n    private func copyToNavicat() {\n        let info = ";
" }\n        return nil\n    }\n\n    // 从 bundle 加载软件图标\n    private func loadSoftwareIcon(_ name: String) -> NSImage? {\n        let bundlePath = Bundle.module.bundlePath\n        let imagePath = " = " }\n        return nil\n    }\n\n    // 从 bundle 加载软件图标\n    private func loadSoftwareIcon(_ name: String) -> NSImage? {\n        let bundlePath = Bundle.module.bundlePath\n        let imagePath = ";
"\n    }\n\n    // MARK: - 网站路径\n\n    /// 获取网站根目录\n    func getWwwPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.wwwPath\n        }\n        let home = FileManager.default.homeDirectoryForCurrentUser.path\n        return " = "\n    }\n\n    // MARK: - 网站路径\n\n    /// 获取网站根目录\n    func getWwwPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.wwwPath\n        }\n        let home = FileManager.default.homeDirectoryForCurrentUser.path\n        return ";
"\n\n        // 使用 openssl 生成加密密码\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = "\n\n        // Usar openssl para generar una contraseña cifrada\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: ";
"\n            throw InstallationError.installationFailed(output)\n        }\n    }\n\n    // MARK: - 解析 Brew 输出\n    nonisolated private func parseBrewOutput(_ output: String) {\n        let lines = output.components(separatedBy: .newlines)\n\n        for line in lines.reversed() {\n            let trimmedLine = line.trimmingCharacters(in: .whitespaces)\n\n            if trimmedLine.contains(" = "\n            throw InstallationError.installationFailed(output)\n        }\n    }\n\n    // MARK: - Analizar la salida de Brew\n    nonisolated private func parseBrewOutput(_ output: String) {\n        let lines = output.components(separatedBy: .newlines)\n\n        for line in lines.reversed() {\n            let trimmedLine = line.trimmingCharacters(in: .whitespaces)\n\n            if trimmedLine.contains(";
",\n                    isActive: databaseManager.activeTabId == nil,\n                    isLocal: true,\n                    onSelect: { databaseManager.activeTabId = nil },\n                    onClose: nil\n                )\n\n                // 已打开的数据库标签\n                ForEach(databaseManager.openedDatabaseTabs) { tab in\n                    DatabaseTabItem(\n                        title: LocalizedStringKey(tab.name),\n                        isActive: databaseManager.activeTabId == tab.id,\n                        isLocal: tab.isLocal,\n                        onSelect: { databaseManager.activeTabId = tab.id },\n                        // 只剩一个标签时不允许关闭\n                        onClose: databaseManager.openedDatabaseTabs.count > 1 ? { closeDatabaseTab(tab) } : nil\n                    )\n                }\n            }\n            .padding(.horizontal, 24)\n            // 底部留出足够空间，容纳 active tab 的遮罩延伸，防止被 ScrollView 裁剪\n            .padding(.bottom, 10)\n            .padding(.top, 10)\n        }\n        // 固定高度，确保布局稳定\n        // 预估高度: 文本(16) + 垂直padding(20) + 底部padding(10) + 顶部padding(10) ≈ 56\n        .frame(height: 60)\n        // 向上负边距，让下方的 Content Card 上移，与 Tab 产生重叠\n        // 这里的数值需要配合上面的 padding(.bottom, 10)\n        // 我们希望 Card 的顶部边框正好位于 Tab 底部边界之上\n        // 如果 ScrollView 内容底部有 10px 空白，我们需要上移大约 12px\n        .padding(.bottom, BuildInfo.uiVersion == " = ",\n                    isActive: databaseManager.activeTabId == nil,\n                    isLocal: true,\n                    onSelect: { databaseManager.activeTabId = nil },\n                    onClose: nil\n                )\n\n                // Pestañas de bases de datos abiertas\n                ForEach(databaseManager.openedDatabaseTabs) { tab in\n                    DatabaseTabItem(\n                        title: LocalizedStringKey(tab.name),\n                        isActive: databaseManager.activeTabId == tab.id,\n                        isLocal: tab.isLocal,\n                        onSelect: { databaseManager.activeTabId = tab.id },\n                        // No se permite cerrar si solo queda una pestaña\n                        onClose: databaseManager.openedDatabaseTabs.count > 1 ? { closeDatabaseTab(tab) } : nil\n                    )\n                }\n            }\n            .padding(.horizontal, 24)\n            // Dejar suficiente espacio en la parte inferior para que la superposición de la pestaña activa no sea recortada por ScrollView\n            .padding(.bottom, 10)\n            .padding(.top, 10)\n        }\n        // Altura fija para garantizar estabilidad del diseño\n        // Altura estimada: texto (16) + padding vertical (20) + padding inferior (10) + padding superior (10) ≈ 56\n        .frame(height: 60)\n        // Margen negativo hacia arriba para desplazar hacia arriba la tarjeta de contenido y provocar solapamiento con la pestaña\n        // Este valor debe ajustarse junto con el padding(.bottom, 10) anterior\n        // Queremos que el borde superior de la tarjeta coincida exactamente con el borde inferior de la pestaña\n        // Si hay un espacio vacío de 10 px en la parte inferior del contenido de ScrollView, necesitamos desplazarla aproximadamente 12 px hacia arriba\n        .padding(.bottom, BuildInfo.uiVersion == ";
") {\n                // 提取查询字符串参数\n                if replacement.contains(" = ") {\n                // Extraer parámetros de la cadena de consulta\n                if replacement.contains(";
", nil)\n    }\n\n    /// 加载 PHP-FPM 端口数据\n    private func loadPHPFPMPortData() -> (port: String, error: String?) {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        let version = extractMajorMinorVersion(service.version)\n\n        // 检查工作空间配置\n        if let config = WorkspaceManager.shared.config, config.isInitialized {\n            let wwwConfPath = " = ", nil)\n    }\n\n    /// Cargar los datos del puerto PHP-FPM\n    private func loadPHPFPMPortData() -> (port: String, error: String?) {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        let version = extractMajorMinorVersion(service.version)\n\n        // Verificar la configuración del espacio de trabajo\n        if let config = WorkspaceManager.shared.config, config.isInitialized {\n            let wwwConfPath = ";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(20)\n        .frame(height: 70)\n    }\n\n    // MARK: - 标题栏\n    private var headerBar: some View {\n        HStack {\n            VStack(alignment: .leading, spacing: 4) {\n                Text(LocalizedStringKey(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(20)\n        .frame(height: 70)\n    }\n\n    // MARK: - Barra de título\n    private var headerBar: some View {\n        HStack {\n            VStack(alignment: .leading, spacing: 4) {\n                Text(LocalizedStringKey(";
"\n\n    // MARK: - 检查域名是否为本地域名\n    func isLocalDomain(_ domain: String) -> Bool {\n        let localPatterns = [\n            " = "\n\n    // MARK: - Verificar si un dominio es local\n    func isLocalDomain(_ domain: String) -> Bool {\n        let localPatterns = [\n            ";
"\\(webServerDisplayName(webServer)) 未安装，无法校验配置。" = "\\(webServerDisplayName(webServer)) no está instalado; no se puede validar la configuración.";
"\n\n        // 生成 auth_basic location 块\n        let backendUrl = site.sslMode == .nginxTermination ? " = "\n\n        // Generar el bloque 'auth_basic location'\n        let backendUrl = site.sslMode == .nginxTermination ? ";
")\n    }\n\n    /// 生成单行配置\n    /// - Parameters:\n    ///   - item: 配置项\n    ///   - commentPrefix: 注释符\n    /// - Returns: 配置行内容\n    private static func generateConfigLine(item: ConfigItem, commentPrefix: String) -> String {\n        let configLine = " = ")\n    }\n\n    /// Generar una línea de configuración individual\n    /// - Parámetros:\n    ///   - item: elemento de configuración\n    ///   - commentPrefix: prefijo de comentario\n    /// - Devuelve: contenido de la línea de configuración\n    private static func generateConfigLine(item: ConfigItem, commentPrefix: String) -> String {\n        let configLine = ";
", isOn: Binding(\n                get: { isSelected },\n                set: { _ in onToggleSelection() }\n            ))\n            .labelsHidden()\n            .frame(width: 30)\n\n            // IP 地址\n            Text(entry.ip)\n                .font(.system(size: 12, design: .monospaced))\n                .frame(width: 120, alignment: .leading)\n\n            // 域名\n            Text(entry.hostname)\n                .font(.system(size: 12))\n                .frame(minWidth: 200, alignment: .leading)\n\n            // 备注\n            Text(entry.comment ?? " = ", isOn: Binding(\n                get: { isSelected },\n                set: { _ in onToggleSelection() }\n            ))\n            .labelsHidden()\n            .frame(width: 30)\n\n            // Dirección IP\n            Text(entry.ip)\n                .font(.system(size: 12, design: .monospaced))\n                .frame(width: 120, alignment: .leading)\n\n            // Nombre de dominio\n            Text(entry.hostname)\n                .font(.system(size: 12))\n                .frame(minWidth: 200, alignment: .leading)\n\n            // Comentario\n            Text(entry.comment ?? ";
"已启用 \\(Self.allCases.count) / \\(Self.maxLanguageCapacity)" = "Habilitado \\(Self.allCases.count) / \\(Self.maxLanguageCapacity)";
")\n        }\n        \n        // 检查是否有写权限\n        guard fileManager.isWritableFile(atPath: path) else {\n            return (true, false, " = ")\n        }\n        \n        // Verificar permisos de escritura\n        guard fileManager.isWritableFile(atPath: path) else {\n            return (true, false, ";
")\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 100, alignment: .leading)\n\n            // 容量\n            Text(databaseSize)\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 80, alignment: .leading)\n\n            // 备份\n            Text(" = ")\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 100, alignment: .leading)\n\n            // Capacidad\n            Text(databaseSize)\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 80, alignment: .leading)\n\n            // Copia de seguridad\n            Text(";
", maxSplits: 3, omittingEmptySubsequences: true)\n\n                if components.count >= 4 {\n                    user = String(components[0])\n                    parentPid = String(components[1])\n                    // 提取启动时间（格式：Mon Jan 1 12:00:00 2024）\n                    let remainingText = String(components[2...].joined(separator: " = ", maxSplits: 3, omittingEmptySubsequences: true)\n\n                if components.count >= 4 {\n                    user = String(components[0])\n                    parentPid = String(components[1])\n                    // Extraer la hora de inicio (formato: lun ene 1 12:00:00 2024)\n                    let remainingText = String(components[2...].joined(separator: ";
"迁移失败: \\(error.localizedDescription)" = "Error al migrar: \\(error.localizedDescription)";
" + lines[j]\n                            }\n                        }\n                        inServerBlock = false\n                        serverBlockStart = -1\n                    }\n                }\n\n                // http 块结束\n                if httpBraceCount == 0 {\n                    break\n                }\n            }\n        }\n\n        // 写回文件\n        let newContent = lines.joined(separator: " = " + lines[j]\n                            }\n                        }\n                        inServerBlock = false\n                        serverBlockStart = -1\n                    }\n                }\n\n                // Fin del bloque 'http'\n                if httpBraceCount == 0 {\n                    break\n                }\n            }\n        }\n\n        // Escribir de nuevo en el archivo\n        let newContent = lines.joined(separator: ";
", isDefault: false),\n        // 国外大模型\n        AIModelProvider(name: " = ", isDefault: false),\n        // Modelos de lenguaje grandes extranjeros\n        AIModelProvider(name: ";
")\n                    .font(.system(size: 14, weight: .medium))\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    private var contentView: some View {\n        VStack(alignment: .leading, spacing: 20) {\n            // 数据库名\n            HStack(spacing: 16) {\n                Text(" = ")\n                    .font(.system(size: 14, weight: .medium))\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    private var contentView: some View {\n        VStack(alignment: .leading, spacing: 20) {\n            // Nombre de la base de datos\n            HStack(spacing: 16) {\n                Text(";
",\n                password: password\n            )\n            Task {\n                await remoteConnector.fetchDatabases()\n                await MainActor.run {\n                    if let error = remoteConnector.errorMessage {\n                        errorMessage = error\n                    } else {\n                        remoteDatabases = remoteConnector.databases\n                    }\n                    isLoading = false\n                }\n            }\n        }\n    }\n\n    // 从服务器获取数据库列表\n    private func fetchDatabasesFromServer() async -> (databases: [String]?, error: String?) {\n        // 在主线程捕获需要的值\n        let host = tab.host\n        let port = tab.port\n        let pwd = password\n\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                // 使用 MySQLClientPaths 查找可用客户端\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: (nil, " = ",\n                password: password\n            )\n            Task {\n                await remoteConnector.fetchDatabases()\n                await MainActor.run {\n                    if let error = remoteConnector.errorMessage {\n                        errorMessage = error\n                    } else {\n                        remoteDatabases = remoteConnector.databases\n                    }\n                    isLoading = false\n                }\n            }\n        }\n    }\n\n    // Obtener la lista de bases de datos desde el servidor\n    private func fetchDatabasesFromServer() async -> (databases: [String]?, error: String?) {\n        // Capturar en el hilo principal los valores necesarios\n        let host = tab.host\n        let port = tab.port\n        let pwd = password\n\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                // Usar MySQLClientPaths para buscar un cliente disponible\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: (nil, ";
") {\n                    showPermissionsSheet = true\n                }\n                .buttonStyle(.bordered)\n                .controlSize(.small)\n                .tint(.green)\n            }\n\n            // 改密按钮\n            Button(" = ") {\n                    showPermissionsSheet = true\n                }\n                .buttonStyle(.bordered)\n                .controlSize(.small)\n                .tint(.green)\n            }\n\n            // Botón para cambiar la contraseña\n            Button(";
"过期时间: \\(formatDate(certificate.validUntil))" = "Fecha de caducidad: \\(formatDate(certificate.validUntil))";
")\n        }\n\n        inputStream = input\n        outputStream = output\n\n        // 配置流\n        input.setProperty(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as Stream.PropertyKey)\n        output.setProperty(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as Stream.PropertyKey)\n\n        input.open()\n        output.open()\n\n        // 等待连接建立\n        let deadline = Date().addingTimeInterval(connectionTimeout)\n        while input.streamStatus != .open && Date() < deadline {\n            Thread.sleep(forTimeInterval: 0.01)\n        }\n\n        guard input.streamStatus == .open else {\n            throw MySQLError.connectionFailed(" = ")\n        }\n\n        inputStream = input\n        outputStream = output\n\n        // Configurar los flujos\n        input.setProperty(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as Stream.PropertyKey)\n        output.setProperty(kCFBooleanTrue, forKey: kCFStreamPropertyShouldCloseNativeSocket as Stream.PropertyKey)\n\n        input.open()\n        output.open()\n\n        // Esperar a que se establezca la conexión\n        let deadline = Date().addingTimeInterval(connectionTimeout)\n        while input.streamStatus != .open && Date() < deadline {\n            Thread.sleep(forTimeInterval: 0.01)\n        }\n\n        guard input.streamStatus == .open else {\n            throw MySQLError.connectionFailed(";
"\n                        conversionWarnings.removeAll()\n                    }\n                }\n            }\n            .frame(maxWidth: .infinity)\n            .padding()\n\n            Divider().background(Color.gray.opacity(0.2))\n\n            // 中间：转换按钮和警告\n            VStack(spacing: 16) {\n                MacButton(icon: " = "\n                        conversionWarnings.removeAll()\n                    }\n                }\n            }\n            .frame(maxWidth: .infinity)\n            .padding()\n\n            Divider().background(Color.gray.opacity(0.2))\n\n            // Centro: botón de conversión y advertencias\n            VStack(spacing: 16) {\n                MacButton(icon: ";
"]),\n        // 性能配置\n        " = "]),\n        // Configuración de rendimiento\n        ";
")\n            }\n            .buttonStyle(.bordered)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 10)\n    }\n\n    // 表头\n    private var databasesTableHeader: some View {\n        HStack(spacing: 8) {\n            // 复选框占位\n            Color.clear.frame(width: 30)\n            Text(" = ")\n            }\n            .buttonStyle(.bordered)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 10)\n    }\n\n    // Encabezado de la tabla de bases de datos\n    private var databasesTableHeader: some View {\n        HStack(spacing: 8) {\n            // Marcador de posición para casilla de verificación\n            Color.clear.frame(width: 30)\n            Text(";
")\n            }\n            .buttonStyle(.bordered)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 12)\n    }\n\n    // 打开设置\n    private func openSettings() {\n        let alert = NSAlert()\n        alert.messageText = " = ")\n            }\n            .buttonStyle(.bordered)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 12)\n    }\n\n    // Abrir ajustes\n    private func openSettings() {\n        let alert = NSAlert()\n        alert.messageText = ";
")\n    }\n    \n    // 检查 Web 服务器运行状态并给出提示\n    private func checkWebServerStatus(webServer: WebServerType) async {\n        // 查找对应的服务\n        let serviceName: String\n        switch webServer {\n        case .nginx:\n            serviceName = " = ")\n    }\n    \n    // Comprobar el estado del servidor web y mostrar una notificación\n    private func checkWebServerStatus(webServer: WebServerType) async {\n        // Buscar el servicio correspondiente\n        let serviceName: String\n        switch webServer {\n        case .nginx:\n            serviceName = ";
"安装 acme.sh 失败：\\(output)" = "Error al instalar acme.sh: \\(output)";
", 21, 32),\n                ]\n\n                // 检测所有服务（使用缓存的已安装包列表）\n                var detectedWithPriority: [(service: ServiceStatus, priority: Int)] = []\n                for def in serviceDefinitions {\n                    if let info = self.detectServiceFromCache(name: def.name, brewName: def.brewName, port: def.port) {\n                        detectedWithPriority.append((info, def.priority))\n                    }\n                }\n\n                // 动态检测额外的 PHP 版本（不在预定义列表中的）\n                let predefinedBrewNames = Set(serviceDefinitions.map { $0.brewName })\n                let additionalPHPVersions = self.detectAdditionalPHPVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // 检测额外的 PHP 版本\n                for def in additionalPHPVersions {\n                    if let info = self.detectServiceFromCache(name: def.name, brewName: def.brewName, port: def.port) {\n                        detectedWithPriority.append((info, def.priority))\n                    }\n                }\n\n                // 动态检测额外的 MySQL 版本（不在预定义列表中的）\n                let additionalMySQLVersions = self.detectAdditionalMySQLVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // 检测额外的 MySQL 版本\n                for def in additionalMySQLVersions {\n                    if let info = self.detectServiceFromCache(name: def.name, brewName: def.brewName, port: def.port) {\n                        detectedWithPriority.append((info, def.priority))\n                    }\n                }\n\n                // 动态检测额外的 MariaDB 版本（不在预定义列表中的）\n                let additionalMariaDBVersions = self.detectAdditionalMariaDBVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // 检测额外的 MariaDB 版本\n                for def in additionalMariaDBVersions {\n                    if let info = self.detectServiceFromCache(name: def.name, brewName: def.brewName, port: def.port) {\n                        detectedWithPriority.append((info, def.priority))\n                    }\n                }\n\n                // 动态检测额外的 PostgreSQL 版本（不在预定义列表中的）\n                let additionalPostgreSQLVersions = self.detectAdditionalPostgreSQLVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // 检测额外的 PostgreSQL 版本\n                for def in additionalPostgreSQLVersions {\n                    if let info = self.detectServiceFromCache(name: def.name, brewName: def.brewName, port: def.port) {\n                        detectedWithPriority.append((info, def.priority))\n                    }\n                }\n\n                // 动态检测额外的 Redis 版本（不在预定义列表中的）\n                let additionalRedisVersions = self.detectAdditionalRedisVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // 检测额外的 Redis 版本\n                for def in additionalRedisVersions {\n                    if let info = self.detectServiceFromCache(name: def.name, brewName: def.brewName, port: def.port) {\n                        detectedWithPriority.append((info, def.priority))\n                    }\n                }\n\n                // 按优先级排序\n                detectedWithPriority.sort { $0.priority < $1.priority }\n                services = detectedWithPriority.map { $0.service }\n\n                continuation.resume(returning: services)\n            }\n        }\n\n        self.services = detectedServices\n        self.isLoadingServices = false\n        // 保存到缓存\n        saveServicesToCache()\n    }\n\n    /// 动态检测所有已安装的 PHP 版本（不在预定义列表中的）\n    private nonisolated func detectAdditionalPHPVersions(\n        predefinedBrewNames: Set<String>\n    ) -> [(name: String, brewName: String, port: Int, priority: Int)] {\n        guard let packages = AppState.cachedInstalledPackages else { return [] }\n\n        var additionalVersions: [(name: String, brewName: String, port: Int, priority: Int)] = []\n\n        for (packageName, _) in packages {\n            // 匹配 php 或 php@x.x 格式\n            if packageName == " = ", 21, 32),\n                ]\n\n                // Detectar todos los servicios (usando la lista en caché de paquetes instalados)\n                var detectedWithPriority: [(service: ServiceStatus, priority: Int)] = []\n                for def in serviceDefinitions {\n                    if let info = self.detectServiceFromCache(name: def.name, brewName: def.brewName, port: def.port) {\n                        detectedWithPriority.append((info, def.priority))\n                    }\n                }\n\n                // Detección dinámica de versiones adicionales de PHP (no incluidas en la lista predefinida)\n                let predefinedBrewNames = Set(serviceDefinitions.map { $0.brewName })\n                let additionalPHPVersions = self.detectAdditionalPHPVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // Detectar versiones adicionales de PHP\n                for def in additionalPHPVersions {\n                    if let info = self.detectServiceFromCache(name: def.name, brewName: def.brewName, port: def.port) {\n                        detectedWithPriority.append((info, def.priority))\n                    }\n                }\n\n                // Detección dinámica de versiones adicionales de MySQL (no incluidas en la lista predefinida)\n                let additionalMySQLVersions = self.detectAdditionalMySQLVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // Detectar versiones adicionales de MySQL\n                for def in additionalMySQLVersions {\n                    if let info = self.detectServiceFromCache(name: def.name, brewName: def.brewName, port: def.port) {\n                        detectedWithPriority.append((info, def.priority))\n                    }\n                }\n\n                // Detección dinámica de versiones adicionales de MariaDB (no incluidas en la lista predefinida)\n                let additionalMariaDBVersions = self.detectAdditionalMariaDBVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // Detectar versiones adicionales de MariaDB\n                for def in additionalMariaDBVersions {\n                    if let info = self.detectServiceFromCache(name: def.name, brewName: def.brewName, port: def.port) {\n                        detectedWithPriority.append((info, def.priority))\n                    }\n                }\n\n                // Detección dinámica de versiones adicionales de PostgreSQL (no incluidas en la lista predefinida)\n                let additionalPostgreSQLVersions = self.detectAdditionalPostgreSQLVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // Detectar versiones adicionales de PostgreSQL\n                for def in additionalPostgreSQLVersions {\n                    if let info = self.detectServiceFromCache(name: def.name, brewName: def.brewName, port: def.port) {\n                        detectedWithPriority.append((info, def.priority))\n                    }\n                }\n\n                // Detección dinámica de versiones adicionales de Redis (no incluidas en la lista predefinida)\n                let additionalRedisVersions = self.detectAdditionalRedisVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // Detectar versiones adicionales de Redis\n                for def in additionalRedisVersions {\n                    if let info = self.detectServiceFromCache(name: def.name, brewName: def.brewName, port: def.port) {\n                        detectedWithPriority.append((info, def.priority))\n                    }\n                }\n\n                // Ordenar por prioridad\n                detectedWithPriority.sort { $0.priority < $1.priority }\n                services = detectedWithPriority.map { $0.service }\n\n                continuation.resume(returning: services)\n            }\n        }\n\n        self.services = detectedServices\n        self.isLoadingServices = false\n        // Guardar en caché\n        saveServicesToCache()\n    }\n\n    /// Detectar dinámicamente todas las versiones de PHP instaladas (no incluidas en la lista predefinida)\n    private nonisolated func detectAdditionalPHPVersions(\n        predefinedBrewNames: Set<String>\n    ) -> [(name: String, brewName: String, port: Int, priority: Int)] {\n        guard let packages = AppState.cachedInstalledPackages else { return [] }\n\n        var additionalVersions: [(name: String, brewName: String, port: Int, priority: Int)] = []\n\n        for (packageName, _) in packages {\n            // Coincidir con formatos php o php@x.x\n            if packageName == ";
"发版版暂未开放自动创建" = "La versión actual no admite aún la creación automática";
"\n            if let port = readPHPFPMPortFromFile(wwwConfPath) {\n                return (port, nil)\n            }\n        }\n\n        // 使用 Homebrew 路径\n        let wwwConfPath = " = "\n            if let port = readPHPFPMPortFromFile(wwwConfPath) {\n                return (port, nil)\n            }\n        }\n\n        // Usar la ruta de Homebrew\n        let wwwConfPath = ";
"]\n                process.standardOutput = Pipe()\n                process.standardError = Pipe()\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    /// 使用 mkcert 生成证书\n    private func generateWithMkcert(domains: [String], certPath: String, keyPath: String) async throws {\n        let domainArgs = domains.map { " = "]\n                process.standardOutput = Pipe()\n                process.standardError = Pipe()\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    /// Generar certificado usando mkcert\n    private func generateWithMkcert(domains: [String], certPath: String, keyPath: String) async throws {\n        let domainArgs = domains.map { ";
"正在安装 \\(software.name)..." = "Instalando \\(software.name)...";
"配置 PHP-FPM 端口失败: \\(error.localizedDescription)" = "Error al configurar el puerto de PHP-FPM: \\(error.localizedDescription)";
"\n        let authBasicBlocks = generateAuthBasicLocationBlocks(site: site, backendUrl: backendUrl)\n\n        // 根据SSL配置生成不同的代理配置\n        if site.sslEnabled && !certPath.isEmpty {\n            if site.forceHttps {\n                // 强制HTTPS：HTTP重定向 + HTTPS代理\n                configContent = " = "\n        let authBasicBlocks = generateAuthBasicLocationBlocks(site: site, backendUrl: backendUrl)\n\n        // Generar configuraciones de proxy distintas según la configuración SSL\n        if site.sslEnabled && !certPath.isEmpty {\n            if site.forceHttps {\n                // Forzar HTTPS: redirección HTTP + proxy HTTPS\n                configContent = ";
")\n                }\n                .buttonStyle(.bordered)\n            }\n            .padding()\n\n            Divider()\n\n            // 用户列表\n            List {\n                ForEach(connector.users) { user in\n                    userRow(user)\n                }\n            }\n        }\n        .sheetWithLocale(isPresented: $showAddUser) {\n            AddUserSheet(connector: connector)\n        }\n    }\n\n    // 用户行\n    private func userRow(_ user: MySQLUserInfo) -> some View {\n        HStack {\n            Image(systemName: " = ")\n                }\n                .buttonStyle(.bordered)\n            }\n            .padding()\n\n            Divider()\n\n            // Lista de usuarios\n            List {\n                ForEach(connector.users) { user in\n                    userRow(user)\n                }\n            }\n        }\n        .sheetWithLocale(isPresented: $showAddUser) {\n            AddUserSheet(connector: connector)\n        }\n    }\n\n    // Fila de usuario\n    private func userRow(_ user: MySQLUserInfo) -> some View {\n        HStack {\n            Image(systemName: ";
", Color(red: 0.0, green: 0.59, blue: 0.31)) // Nginx绿色\n        } else if name.contains(" = ", Color(red: 0.0, green: 0.59, blue: 0.31)) // Verde de Nginx\n        } else if name.contains(";
")\n        }\n        \n        // 检查是否是目录\n        guard isDirectory.boolValue else {\n            return (false, false, " = ")\n        }\n        \n        // Verificar si es un directorio\n        guard isDirectory.boolValue else {\n            return (false, false, ";
"]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        // 实时读取输出\n        var outputData = Data()\n        let outputHandle = pipe.fileHandleForReading\n\n        // 启动进程\n        try process.run()\n\n        // 在后台线程读取输出\n        let readQueue = DispatchQueue(label: " = "]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        // Leer la salida en tiempo real\n        var outputData = Data()\n        let outputHandle = pipe.fileHandleForReading\n\n        // Iniciar el proceso\n        try process.run()\n\n        // Leer la salida en un hilo en segundo plano\n        let readQueue = DispatchQueue(label: ";
"\n    }\n\n    // MARK: - Nginx 路径\n\n    /// Nginx 配置目录\n    var nginxConfPath: String { " = "\n    }\n\n    // MARK: - Nginx Paths\n\n    /// Nginx configuration directory\n    var nginxConfPath: String { ";
"]\n\n    // MARK: - MySQL 配置字典\n    static var mysql: [String: ConfigMeta] {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        return [\n            // ===== 基础配置 =====\n            " = "]\n\n    // MARK: - MySQL Configuration Dictionary\n    static var mysql: [String: ConfigMeta] {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        return [\n            // ===== Basic Configuration =====\n            ";
"]\n\n                let outputPipe = Pipe()\n                let errorPipe = Pipe()\n                process.standardOutput = outputPipe\n                process.standardError = errorPipe\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    if process.terminationStatus == 0 {\n                        let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()\n                        if let output = String(data: outputData, encoding: .utf8) {\n                            // 收集所有 mysqld 监听的端口\n                            var mysqlPorts: [Int] = []\n                            let pattern = #" = "]\n\n                let outputPipe = Pipe()\n                let errorPipe = Pipe()\n                process.standardOutput = outputPipe\n                process.standardError = errorPipe\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    if process.terminationStatus == 0 {\n                        let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()\n                        if let output = String(data: outputData, encoding: .utf8) {\n                            // Collect all ports listened to by mysqld\n                            var mysqlPorts: [Int] = []\n                            let pattern = #";
"\n        return formatter.string(from: date)\n    }\n\n    // 获取站点关联的证书\n    private var siteCertificate: Certificate? {\n        if let certId = site.certificateId {\n            return certificateManager.certificates.first { $0.id == certId }\n        }\n        return nil\n    }\n\n    // 动态SSL状态文本\n    private var sslStatusText: String {\n        if let cert = siteCertificate {\n            return cert.statusDisplayText\n        }\n        return " = "\n        return formatter.string(from: date)\n    }\n\n    // Retrieve the certificate associated with the site\n    private var siteCertificate: Certificate? {\n        if let certId = site.certificateId {\n            return certificateManager.certificates.first { $0.id == certId }\n        }\n        return nil\n    }\n\n    // Dynamic SSL status text\n    private var sslStatusText: String {\n        if let cert = siteCertificate {\n            return cert.statusDisplayText\n        }\n        return ";
"\n            }\n        }\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            HStack {\n                Text(" = "\n            }\n        }\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Title bar\n            HStack {\n                Text(";
" {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(width: 560, height: 520)\n        .onAppear {\n            if let config = workspaceManager.config {\n                workspacePath = config.rootPath\n            } else {\n                workspacePath = workspaceManager.getDefaultPath()\n            }\n        }\n    }\n\n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerView\n\n            Divider()\n\n            // 内容区域\n            ScrollView {\n                VStack(alignment: .leading, spacing: 24) {\n                    // 说明文字\n                    descriptionView\n\n                    // 路径选择\n                    pathSelectionView\n\n                    // 目录结构预览\n                    directoryPreviewView\n                }\n                .padding(24)\n            }\n\n            Divider()\n\n            // 底部按钮\n            footerView\n        }\n    }\n\n    private var v2Body: some View {\n        VStack(spacing: 0) {\n            headerViewV2\n                .padding(16)\n\n            VStack(spacing: 0) {\n                ScrollView {\n                    VStack(alignment: .leading, spacing: 24) {\n                        descriptionView\n                        pathSelectionViewV2\n                        directoryPreviewViewV2\n                    }\n                    .padding(24)\n                }\n                \n                Divider()\n                    .background(Color.gray.opacity(0.2))\n                \n                footerViewV2\n            }\n            .background(Color.white.opacity(0.1))\n            .cornerRadius(AppTokens.Radius.medium)\n            .padding(.horizontal, 16)\n            .padding(.bottom, 16)\n        }\n    }\n}\n\n// MARK: - 子视图\nextension WorkspaceSetupSheet {\n    private var headerView: some View {\n        HStack {\n            Text(" = " {\n                v2Body\n            } else {\n                v1Body\n            }\n        }\n        .frame(width: 560, height: 520)\n        .onAppear {\n            if let config = workspaceManager.config {\n                workspacePath = config.rootPath\n            } else {\n                workspacePath = workspaceManager.getDefaultPath()\n            }\n        }\n    }\n\n    private var v1Body: some View {\n        VStack(spacing: 0) {\n            // Title bar\n            headerView\n\n            Divider()\n\n            // Content area\n            ScrollView {\n                VStack(alignment: .leading, spacing: 24) {\n                    // Description text\n                    descriptionView\n\n                    // Path selection\n                    pathSelectionView\n\n                    // Directory structure preview\n                    directoryPreviewView\n                }\n                .padding(24)\n            }\n\n            Divider()\n\n            // Bottom buttons\n            footerView\n        }\n    }\n\n    private var v2Body: some View {\n        VStack(spacing: 0) {\n            headerViewV2\n                .padding(16)\n\n            VStack(spacing: 0) {\n                ScrollView {\n                    VStack(alignment: .leading, spacing: 24) {\n                        descriptionView\n                        pathSelectionViewV2\n                        directoryPreviewViewV2\n                    }\n                    .padding(24)\n                }\n                \n                Divider()\n                    .background(Color.gray.opacity(0.2))\n                \n                footerViewV2\n            }\n            .background(Color.white.opacity(0.1))\n            .cornerRadius(AppTokens.Radius.medium)\n            .padding(.horizontal, 16)\n            .padding(.bottom, 16)\n        }\n    }\n}\n\n// MARK: - Subviews\nextension WorkspaceSetupSheet {\n    private var headerView: some View {\n        HStack {\n            Text(";
"\n    @State private var isAcmeInstalled = false\n    @State private var isCheckingAcme = true\n    @State private var isInstallingAcme = false\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 表单内容\n            formContent\n\n            Divider()\n\n            // 底部按钮\n            bottomButtons\n        }\n        .frame(width: 600, height: 650)\n        .alert(" = "\n    @State private var isAcmeInstalled = false\n    @State private var isCheckingAcme = true\n    @State private var isInstallingAcme = false\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Title bar\n            headerBar\n\n            Divider()\n\n            // Form content\n            formContent\n\n            Divider()\n\n            // Bottom buttons\n            bottomButtons\n        }\n        .frame(width: 600, height: 650)\n        .alert(";
"  // 备注\n\n    static let systemDatabases = [" = "  // Notes\n\n    static let systemDatabases = [";
"\n        }\n    }\n}\n\n// MARK: - 错误定义\n\n/// 配置解析器错误\nenum ConfigParserError: LocalizedError {\n    case fileNotFound(String)\n    case invalidFormat(String)\n    case parseError(String)\n\n    var errorDescription: String? {\n        switch self {\n        case .fileNotFound(let path):\n            return " = "\n        }\n    }\n}\n\n// MARK: - Error Definitions\n\n/// Configuration parser error\nenum ConfigParserError: LocalizedError {\n    case fileNotFound(String)\n    case invalidFormat(String)\n    case parseError(String)\n\n    var errorDescription: String? {\n        switch self {\n        case .fileNotFound(let path):\n            return ";
", selection: $charset) {\n                ForEach(charsetOptions, id: \\.self) { option in\n                    Text(option).tag(option)\n                }\n            }\n            .frame(width: 110)\n        }\n    }\n\n    // 用户名字段\n    private var usernameField: some View {\n        HStack(alignment: .center, spacing: 12) {\n            Text(" = ", selection: $charset) {\n                ForEach(charsetOptions, id: \\.self) { option in\n                    Text(option).tag(option)\n                }\n            }\n            .frame(width: 110)\n        }\n    }\n\n    // Username field\n    private var usernameField: some View {\n        HStack(alignment: .center, spacing: 12) {\n            Text(";
")\n    }\n\n    /// 默认工作空间路径\n    private var defaultWorkspacePath: String {\n        let home = FileManager.default.homeDirectoryForCurrentUser.path\n        return " = ")\n    }\n\n    /// Default workspace path\n    private var defaultWorkspacePath: String {\n        let home = FileManager.default.homeDirectoryForCurrentUser.path\n        return ";
")\n                    .font(.system(size: 8))\n                    .foregroundColor(.secondary)\n            }\n            .frame(width: 60, alignment: .leading)\n            .contentShape(Rectangle())\n            .onTapGesture {\n                withAnimation(.easeInOut(duration: 0.2)) {\n                    showPHPDropdown.toggle()\n                    showWebServerDropdown = false\n                }\n            }\n            .background(alignment: .topLeading) {\n                // PHP版本下拉菜单（浮动在最上层）\n                if showPHPDropdown {\n                    VStack(alignment: .leading, spacing: 0) {\n                        // 纯静态选项\n                        Button(action: {\n                            changePHPVersion(" = ")\n                    .font(.system(size: 8))\n                    .foregroundColor(.secondary)\n            }\n            .frame(width: 60, alignment: .leading)\n            .contentShape(Rectangle())\n            .onTapGesture {\n                withAnimation(.easeInOut(duration: 0.2)) {\n                    showPHPDropdown.toggle()\n                    showWebServerDropdown = false\n                }\n            }\n            .background(alignment: .topLeading) {\n                // PHP version dropdown (floating on top)\n                if showPHPDropdown {\n                    VStack(alignment: .leading, spacing: 0) {\n                        // Static-only option\n                        Button(action: {\n                            changePHPVersion(";
", packageName]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            // 使用带超时的等待，避免 brew 命令卡住\n            let completed = waitForProcessWithTimeout(process, timeout: 5.0)\n            if !completed {\n                process.terminate()\n                return false\n            }\n            return process.terminationStatus == 0\n        } catch {\n            return false\n        }\n    }\n\n    /// 带超时的进程等待\n    private nonisolated func waitForProcessWithTimeout(_ process: Process, timeout: TimeInterval) -> Bool {\n        let semaphore = DispatchSemaphore(value: 0)\n\n        DispatchQueue.global(qos: .userInitiated).async {\n            process.waitUntilExit()\n            semaphore.signal()\n        }\n\n        let result = semaphore.wait(timeout: .now() + timeout)\n        return result == .success\n    }\n\n    /// 从缓存检测服务是否安装并获取信息\n    private nonisolated func detectServiceFromCache(name: String, brewName: String, port: Int) -> ServiceStatus? {\n        // 从缓存中检查包是否已安装\n        guard let packages = AppState.cachedInstalledPackages,\n              let version = packages[brewName] else {\n            return nil\n        }\n\n        // 获取运行状态\n        let state = getServiceState(brewName)\n\n        // 获取配置文件路径\n        let configPath = findConfigPath(for: name, version: version)\n\n        return ServiceStatus(\n            name: name,\n            version: version,\n            state: state,\n            port: port,\n            installed: true,\n            configPath: configPath,\n            brewName: brewName\n        )\n    }\n\n    /// 获取 Homebrew 包的版本\n    private nonisolated func getBrewPackageVersion(_ packageName: String) -> String {\n        guard let brewPath = resolveBrewPath() else {\n            return " = ", packageName]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            // Use timeout-based waiting to prevent brew commands from hanging\n            let completed = waitForProcessWithTimeout(process, timeout: 5.0)\n            if !completed {\n                process.terminate()\n                return false\n            }\n            return process.terminationStatus == 0\n        } catch {\n            return false\n        }\n    }\n\n    /// Process waiting with timeout\n    private nonisolated func waitForProcessWithTimeout(_ process: Process, timeout: TimeInterval) -> Bool {\n        let semaphore = DispatchSemaphore(value: 0)\n\n        DispatchQueue.global(qos: .userInitiated).async {\n            process.waitUntilExit()\n            semaphore.signal()\n        }\n\n        let result = semaphore.wait(timeout: .now() + timeout)\n        return result == .success\n    }\n\n    /// Detect service installation and retrieve information from cache\n    private nonisolated func detectServiceFromCache(name: String, brewName: String, port: Int) -> ServiceStatus? {\n        // Check whether package is installed from cache\n        guard let packages = AppState.cachedInstalledPackages,\n              let version = packages[brewName] else {\n            return nil\n        }\n\n        // Retrieve runtime state\n        let state = getServiceState(brewName)\n\n        // Retrieve configuration file path\n        let configPath = findConfigPath(for: name, version: version)\n\n        return ServiceStatus(\n            name: name,\n            version: version,\n            state: state,\n            port: port,\n            installed: true,\n            configPath: configPath,\n            brewName: brewName\n        )\n    }\n\n    /// Retrieve Homebrew package version\n    private nonisolated func getBrewPackageVersion(_ packageName: String) -> String {\n        guard let brewPath = resolveBrewPath() else {\n            return ";
"端口已更新为 \\(port)，相关配置已同步" = "Port updated to \\(port); related configurations synchronized";
"  // 扫描状态信息\n    @Published var scannedPath: String = " = "  // Scan status information\n    @Published var scannedPath: String = ";
") {\n        // 使用非侵入式提示\n        serviceErrorTitle = title\n        serviceErrorMessage = message\n        failedServiceName = serviceName\n        showServiceErrorTip = true\n    }\n\n    // MARK: - 服务检测\n\n    /// 检查 Homebrew 包是否已安装\n    private nonisolated func isBrewPackageInstalled(_ packageName: String) -> Bool {\n        guard let brewPath = resolveBrewPath() else {\n            return false\n        }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n\n        process.arguments = [" = ") {\n        // Use non-intrusive notification\n        serviceErrorTitle = title\n        serviceErrorMessage = message\n        failedServiceName = serviceName\n        showServiceErrorTip = true\n    }\n\n    // MARK: - Service Detection\n\n    /// Check whether Homebrew package is installed\n    private nonisolated func isBrewPackageInstalled(_ packageName: String) -> Bool {\n        guard let brewPath = resolveBrewPath() else {\n            return false\n        }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n\n        process.arguments = [";
")\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 10)\n    }\n\n    // 打开 phpMyAdmin\n    private func openPhpMyAdmin() {\n        if let url = URL(string: " = ")\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 10)\n    }\n\n    // Open phpMyAdmin\n    private func openPhpMyAdmin() {\n        if let url = URL(string: ";
" : database.note)\n                .font(.system(size: 11))\n                .foregroundColor(.secondary)\n                .lineLimit(1)\n                .frame(width: 100, alignment: .leading)\n\n            Spacer()\n\n            // 操作\n            actionColumn\n                .frame(width: 180, alignment: .trailing)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 8)\n        .background(isHovered ? Color.gray.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n        .onHover { hovering in isHovered = hovering }\n        .onAppear { loadBackupCount() }\n        .sheetWithLocale(isPresented: $showPermissionsSheet) {\n            RemotePermissionsSheet(\n                databaseName: database.name,\n                username: database.username.isEmpty ? database.name : database.username,\n                connector: connector\n            )\n        }\n        .sheetWithLocale(isPresented: $showChangePasswordSheet) {\n            RemoteChangePasswordSheet(\n                databaseName: database.name,\n                username: database.username.isEmpty ? database.name : database.username,\n                connector: connector\n            )\n        }\n        .sheetWithLocale(isPresented: $showImportSheet) {\n            ImportDatabaseSheet(databaseName: database.name, connector: connector)\n        }\n        .sheetWithLocale(isPresented: $showBackupListSheet) {\n            RemoteBackupListSheet(databaseName: database.name, connector: connector)\n        }\n    }\n\n    // 密码列\n    private var passwordColumn: some View {\n        HStack(spacing: 4) {\n            Text(showPassword ? (database.userPassword.isEmpty ? " = " : database.note)\n                .font(.system(size: 11))\n                .foregroundColor(.secondary)\n                .lineLimit(1)\n                .frame(width: 100, alignment: .leading)\n\n            Spacer()\n\n            // Actions\n            actionColumn\n                .frame(width: 180, alignment: .trailing)\n        }\n        .padding(.horizontal, 16)\n        .padding(.vertical, 8)\n        .background(isHovered ? Color.gray.opacity(AppTokens.Opacity.ultraLight) : Color.clear)\n        .onHover { hovering in isHovered = hovering }\n        .onAppear { loadBackupCount() }\n        .sheetWithLocale(isPresented: $showPermissionsSheet) {\n            RemotePermissionsSheet(\n                databaseName: database.name,\n                username: database.username.isEmpty ? database.name : database.username,\n                connector: connector\n            )\n        }\n        .sheetWithLocale(isPresented: $showChangePasswordSheet) {\n            RemoteChangePasswordSheet(\n                databaseName: database.name,\n                username: database.username.isEmpty ? database.name : database.username,\n                connector: connector\n            )\n        }\n        .sheetWithLocale(isPresented: $showImportSheet) {\n            ImportDatabaseSheet(databaseName: database.name, connector: connector)\n        }\n        .sheetWithLocale(isPresented: $showBackupListSheet) {\n            RemoteBackupListSheet(databaseName: database.name, connector: connector)\n        }\n    }\n\n    // Password column\n    private var passwordColumn: some View {\n        HStack(spacing: 4) {\n            Text(showPassword ? (database.userPassword.isEmpty ? ";
")\n\n        // 确保目录存在\n        if !FileManager.default.fileExists(atPath: backupDir.path) {\n            try? FileManager.default.createDirectory(at: backupDir, withIntermediateDirectories: true)\n        }\n        return backupDir\n    }\n\n    /// 备份记录文件路径\n    private var recordsFilePath: URL {\n        backupDirectory.appendingPathComponent(" = ")\n\n        // Ensure directory exists\n        if !FileManager.default.fileExists(atPath: backupDir.path) {\n            try? FileManager.default.createDirectory(at: backupDir, withIntermediateDirectories: true)\n        }\n        return backupDir\n    }\n\n    /// Backup records file path\n    private var recordsFilePath: URL {\n        backupDirectory.appendingPathComponent(";
"未找到 \\(service.name) 的配置文件。\\n\\n可能的原因:\\n1. 服务未安装\\n2. 配置文件位置不在预期路径\\n\\n请确认服务已正确安装。" = "Configuration file for \\(service.name) not found.\n\nPossible causes:\n1. Service not installed\n2. Configuration file not located at expected path\n\nPlease verify that the service is properly installed.";
" {\n            return false\n        }\n\n        // 排除链路本地地址 (169.254.x.x)\n        if address.hasPrefix(" = " {\n            return false\n        }\n\n        // Excluir direcciones de enlace local (169.254.x.x)\n        if address.hasPrefix(";
").first {\n                        running.insert(String(name))\n                    }\n                }\n            }\n            return running\n        } catch {\n            return []\n        }\n    }\n\n    /// 检查单个服务是否正在运行\n    private nonisolated func isServiceRunning(_ brewName: String) -> Bool {\n        guard let brewPath = brewPath else { return false }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [" = ").first {\n                        running.insert(String(name))\n                    }\n                }\n            }\n            return running\n        } catch {\n            return []\n        }\n    }\n\n    /// Verificar si un servicio específico está en ejecución\n    private nonisolated func isServiceRunning(_ brewName: String) -> Bool {\n        guard let brewPath = brewPath else { return false }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [";
"\n            counter += 1\n        }\n\n        // 创建站点\n        try await addSite(\n            name: finalName,\n            domains: parsed.domains,\n            documentRoot: parsed.documentRoot,\n            publicDirectory: " = "\n            counter += 1\n        }\n\n        // Crear sitio\n        try await addSite(\n            name: finalName,\n            domains: parsed.domains,\n            documentRoot: parsed.documentRoot,\n            publicDirectory: ";
", text: $note)\n                            .macInput()\n                }\n            }\n            .padding(.horizontal)\n            .padding(.top)\n            .padding(.bottom, 20)\n        }\n    }\n\n    // V2 底部按钮\n    private var bottomButtonsV2: some View {\n        HStack(spacing: 12) {\n            Spacer()\n            MacButton(icon: " = ", text: $note)\n                            .macInput()\n                }\n            }\n            .padding(.horizontal)\n            .padding(.top)\n            .padding(.bottom, 20)\n        }\n    }\n\n    // Botones inferiores V2\n    private var bottomButtonsV2: some View {\n        HStack(spacing: 12) {\n            Spacer()\n            MacButton(icon: ";
") 配置语法错误" = ") Error de sintaxis en la configuración";
" }\n\n    // MARK: - MySQL 路径\n\n    /// MySQL 配置目录 (按版本)\n    func mysqlConfPath(version: String) -> String {\n        " = " }\n\n    // MARK: - Ruta de MySQL\n\n    /// Directorio de configuración de MySQL (por versión)\n    func mysqlConfPath(version: String) -> String {\n        ";
"\n        \n        // 分离管理区域和非管理区域\n        let lines = existingContent.components(separatedBy: .newlines)\n        var beforeManaged: [String] = []\n        var afterManaged: [String] = []\n        var inManagedSection = false\n        var foundStart = false\n        \n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed == markerStart {\n                inManagedSection = true\n                foundStart = true\n                continue\n            }\n            if trimmed == markerEnd {\n                inManagedSection = false\n                continue\n            }\n            \n            if inManagedSection {\n                // 跳过管理区域内的旧内容\n                continue\n            } else if foundStart {\n                afterManaged.append(line)\n            } else {\n                beforeManaged.append(line)\n            }\n        }\n        \n        // 构建新的 hosts 文件内容\n        var newContent = beforeManaged.joined(separator: " = "\n        \n        // Separar la zona administrada y la no administrada\n        let lines = existingContent.components(separatedBy: .newlines)\n        var beforeManaged: [String] = []\n        var afterManaged: [String] = []\n        var inManagedSection = false\n        var foundStart = false\n        \n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed == markerStart {\n                inManagedSection = true\n                foundStart = true\n                continue\n            }\n            if trimmed == markerEnd {\n                inManagedSection = false\n                continue\n            }\n            \n            if inManagedSection {\n                // Omitir el contenido antiguo dentro de la zona administrada\n                continue\n            } else if foundStart {\n                afterManaged.append(line)\n            } else {\n                beforeManaged.append(line)\n            }\n        }\n        \n        // Construir el nuevo contenido del archivo hosts\n        var newContent = beforeManaged.joined(separator: ";
"数据库 \\(database.name) 已成功备份" = "La base de datos \\(database.name) se ha respaldado correctamente";
", brewName])\n\n        if result.success {\n            // 只更新单个软件的运行状态，不重新加载整个列表\n            updateSoftwareRunningState(software.id, isRunning: true)\n        } else {\n            // 启动失败，检测端口占用情况并提供详细建议\n            let error = await diagnoseStartFailure(software: software)\n            // 收集详细诊断信息\n            let configTestOutput = await getConfigTestError(software: software)\n            var detailParts: [String] = []\n\n            // 添加配置文件路径信息\n            if let configInfo = getConfigFileInfo(software: software) {\n                detailParts.append(" = ", brewName])\n\n        if result.success {\n            // Actualizar únicamente el estado de ejecución del software específico, sin recargar toda la lista\n            updateSoftwareRunningState(software.id, isRunning: true)\n        } else {\n            // Fallo al iniciar; detectar ocupación de puertos y ofrecer recomendaciones detalladas\n            let error = await diagnoseStartFailure(software: software)\n            // Recopilar información detallada de diagnóstico\n            let configTestOutput = await getConfigTestError(software: software)\n            var detailParts: [String] = []\n\n            // Agregar información sobre la ruta del archivo de configuración\n            if let configInfo = getConfigFileInfo(software: software) {\n                detailParts.append(";
")\n                            .font(.system(size: 11))\n                            .foregroundColor(.secondary.opacity(AppTokens.Opacity.disabled))\n                    }\n\n                    // 状态指示器\n                    HStack(spacing: 4) {\n                        if version.isRunning {\n                            Circle()\n                                .fill(Color.green)\n                                .frame(width: 6, height: 6)\n                            Text(" = ")\n                            .font(.system(size: 11))\n                            .foregroundColor(.secondary.opacity(AppTokens.Opacity.disabled))\n                    }\n\n                    // Indicador de estado\n                    HStack(spacing: 4) {\n                        if version.isRunning {\n                            Circle()\n                                .fill(Color.green)\n                                .frame(width: 6, height: 6)\n                            Text(";
"\n            if FileManager.default.fileExists(atPath: wsDefaultConf) {\n                try updateNginxListenPort(at: wsDefaultConf, port: port)\n                modified = true\n            }\n\n            // 检查工作空间 vhosts 目录中的配置文件\n            let wsVhostsPath = config.nginxVhostsPath\n            if FileManager.default.fileExists(atPath: wsVhostsPath) {\n                let vhostFiles = try? FileManager.default.contentsOfDirectory(atPath: wsVhostsPath)\n                for file in vhostFiles ?? [] where file.hasSuffix(" = "\n            if FileManager.default.fileExists(atPath: wsDefaultConf) {\n                try updateNginxListenPort(at: wsDefaultConf, port: port)\n                modified = true\n            }\n\n            // Verificar los archivos de configuración en el directorio vhosts del espacio de trabajo\n            let wsVhostsPath = config.nginxVhostsPath\n            if FileManager.default.fileExists(atPath: wsVhostsPath) {\n                let vhostFiles = try? FileManager.default.contentsOfDirectory(atPath: wsVhostsPath)\n                for file in vhostFiles ?? [] where file.hasSuffix(";
")\n        guard parts.count == 4 else { return false }\n\n        for part in parts {\n            guard let num = Int(part), num >= 0 && num <= 255 else {\n                return false\n            }\n        }\n\n        return true\n    }\n\n    // MARK: - 令牌管理\n\n    /// 生成新的认证令牌\n    func generateToken(forIP ip: String) -> AuthToken {\n        let token = generateSecureToken()\n        let now = Date()\n        let expires = now.addingTimeInterval(tokenValidityDuration)\n\n        let authToken = AuthToken(\n            token: token,\n            createdAt: now,\n            expiresAt: expires,\n            allowedIP: ip\n        )\n\n        currentToken = authToken\n        saveToken(authToken)\n\n        return authToken\n    }\n\n    /// 生成安全的随机令牌\n    private func generateSecureToken() -> String {\n        var bytes = [UInt8](repeating: 0, count: 32)\n        _ = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)\n        return bytes.map { String(format: " = ")\n        guard parts.count == 4 else { return false }\n\n        for part in parts {\n            guard let num = Int(part), num >= 0 && num <= 255 else {\n                return false\n            }\n        }\n\n        return true\n    }\n\n    // MARK: - Gestión de tokens\n\n    /// Generar un nuevo token de autenticación\n    func generateToken(forIP ip: String) -> AuthToken {\n        let token = generateSecureToken()\n        let now = Date()\n        let expires = now.addingTimeInterval(tokenValidityDuration)\n\n        let authToken = AuthToken(\n            token: token,\n            createdAt: now,\n            expiresAt: expires,\n            allowedIP: ip\n        )\n\n        currentToken = authToken\n        saveToken(authToken)\n\n        return authToken\n    }\n\n    /// Generar un token aleatorio seguro\n    private func generateSecureToken() -> String {\n        var bytes = [UInt8](repeating: 0, count: 32)\n        _ = SecRandomCopyBytes(kSecRandomDefault, bytes.count, &bytes)\n        return bytes.map { String(format: ";
"\n    }\n\n    /// 获取 PHP 日志目录\n    func getPHPLogsPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.phpLogsPath\n        }\n        return " = "\n    }\n\n    /// Obtener el directorio de registros de PHP\n    func getPHPLogsPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.phpLogsPath\n        }\n        return ";
"\n        ]\n\n        // 常见扫描工具User-Agent\n        let scannerAgents = [\n            " = "\n        ]\n\n        // User-Agents comunes de herramientas de escaneo\n        let scannerAgents = [\n            ";
", isOn: $isSelected)\n                .labelsHidden()\n                .frame(width: 30)\n\n            // 数据库名\n            Text(database.name)\n                .font(.system(size: 12))\n                .frame(width: 120, alignment: .leading)\n\n            // 用户名\n            Text(database.username.isEmpty ? database.name : database.username)\n                .font(.system(size: 12))\n                .frame(width: 100, alignment: .leading)\n\n            // 密码\n            passwordColumn\n                .frame(width: 100, alignment: .leading)\n\n            // 备份\n            backupColumn\n                .frame(width: 120, alignment: .leading)\n\n            // 权限（可点击修改）\n            permissionColumn\n                .frame(width: 90, alignment: .leading)\n\n            // 备注\n            Text(database.note.isEmpty ? " = ", isOn: $isSelected)\n                .labelsHidden()\n                .frame(width: 30)\n\n            // Nombre de la base de datos\n            Text(database.name)\n                .font(.system(size: 12))\n                .frame(width: 120, alignment: .leading)\n\n            // Nombre de usuario\n            Text(database.username.isEmpty ? database.name : database.username)\n                .font(.system(size: 12))\n                .frame(width: 100, alignment: .leading)\n\n            // Contraseña\n            passwordColumn\n                .frame(width: 100, alignment: .leading)\n\n            // Respaldo\n            backupColumn\n                .frame(width: 120, alignment: .leading)\n\n            // Permisos (clic para modificar)\n            permissionColumn\n                .frame(width: 90, alignment: .leading)\n\n            // Nota\n            Text(database.note.isEmpty ? ";
"\n        ]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = Pipe()\n\n        try process.run()\n        process.waitUntilExit()\n\n        guard process.terminationStatus == 0 else {\n            throw GoAccessError.executionFailed\n        }\n\n        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n        guard let output = String(data: data, encoding: .utf8) else {\n            throw GoAccessError.invalidOutput\n        }\n\n        return output\n    }\n\n    // MARK: - 解析 JSON\n    nonisolated private func parseJSON(_ jsonString: String) throws -> GoAccessReport {\n        guard let data = jsonString.data(using: .utf8) else {\n            throw GoAccessError.invalidOutput\n        }\n\n        let decoder = JSONDecoder()\n        do {\n            let report = try decoder.decode(GoAccessReport.self, from: data)\n            return report\n        } catch {\n            throw GoAccessError.parsingFailed(error)\n        }\n    }\n}\n\n// MARK: - 错误类型\nenum GoAccessError: LocalizedError {\n    case notInstalled\n    case logFileNotFound\n    case executionFailed\n    case invalidOutput\n    case parsingFailed(Error)\n\n    var errorDescription: String? {\n        switch self {\n        case .notInstalled:\n            return " = "\n        ]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = Pipe()\n\n        try process.run()\n        process.waitUntilExit()\n\n        guard process.terminationStatus == 0 else {\n            throw GoAccessError.executionFailed\n        }\n\n        let data = pipe.fileHandleForReading.readDataToEndOfFile()\n        guard let output = String(data: data, encoding: .utf8) else {\n            throw GoAccessError.invalidOutput\n        }\n\n        return output\n    }\n\n    // MARK: - Analizar JSON\n    nonisolated private func parseJSON(_ jsonString: String) throws -> GoAccessReport {\n        guard let data = jsonString.data(using: .utf8) else {\n            throw GoAccessError.invalidOutput\n        }\n\n        let decoder = JSONDecoder()\n        do {\n            let report = try decoder.decode(GoAccessReport.self, from: data)\n            return report\n        } catch {\n            throw GoAccessError.parsingFailed(error)\n        }\n    }\n}\n\n// MARK: - Tipos de error\nenum GoAccessError: LocalizedError {\n    case notInstalled\n    case logFileNotFound\n    case executionFailed\n    case invalidOutput\n    case parsingFailed(Error)\n\n    var errorDescription: String? {\n        switch self {\n        case .notInstalled:\n            return ";
"\n        return formatter.string(from: date)\n    }\n}\n\n// MARK: - SSL设置视图\nstruct SSLSettingsView: View {\n    let site: Site\n    \n    var body: some View {\n        VStack(alignment: .leading, spacing: 20) {\n            Text(LocalizedStringKey(" = "\n        return formatter.string(from: date)\n    }\n}\n\n// MARK: - Vista de configuración SSL\nstruct SSLSettingsView: View {\n    let site: Site\n    \n    var body: some View {\n        VStack(alignment: .leading, spacing: 20) {\n            Text(LocalizedStringKey(";
"\n        ]\n\n        for path in possiblePaths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n\n        return nil\n    }\n\n    /// 获取进程的详细信息\n    private func getProcessDetails(pid: String) -> (executablePath: String, commandLine: String, user: String, parentPid: String, startTime: String, workingDirectory: String) {\n        var executablePath = " = "\n        ]\n\n        for path in possiblePaths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n\n        return nil\n    }\n\n    /// Obtener detalles del proceso\n    private func getProcessDetails(pid: String) -> (executablePath: String, commandLine: String, user: String, parentPid: String, startTime: String, workingDirectory: String) {\n        var executablePath = ";
"\n        }\n        return item.suggestedValue\n    }\n\n    private func applySuggestedValue() {\n        editValue = item.suggestedValue\n        onValueChange(item.suggestedValue)\n    }\n}\n\n// MARK: - 配置备份列表 Sheet\nstruct ConfigBackupListSheet: View {\n    @Environment(\\.dismiss) var dismiss\n    let serviceName: String\n    let backups: [ConfigBackup]\n    let onRestore: (ConfigBackup) -> Void\n    let onDelete: (ConfigBackup) -> Void\n\n    private let dateFormatter: DateFormatter = {\n        let formatter = DateFormatter()\n        formatter.dateFormat = " = "\n        }\n        return item.suggestedValue\n    }\n\n    private func applySuggestedValue() {\n        editValue = item.suggestedValue\n        onValueChange(item.suggestedValue)\n    }\n}\n\n// MARK: - Hoja de lista de copias de seguridad de configuración\nstruct ConfigBackupListSheet: View {\n    @Environment(\\.dismiss) var dismiss\n    let serviceName: String\n    let backups: [ConfigBackup]\n    let onRestore: (ConfigBackup) -> Void\n    let onDelete: (ConfigBackup) -> Void\n\n    private let dateFormatter: DateFormatter = {\n        let formatter = DateFormatter()\n        formatter.dateFormat = ";
")\n                    .font(.system(size: 12))\n            }\n            .toggleStyle(.checkbox)\n\n            Spacer()\n\n            // 分页信息\n            Text(" = ")\n                    .font(.system(size: 12))\n            }\n            .toggleStyle(.checkbox)\n\n            Spacer()\n\n            // Información de paginación\n            Text(";
")\n            )\n            loadSoftwareVersions()\n            throw error\n        }\n\n        loadSoftwareVersions()\n    }\n\n    /// 修改 Apache 配置文件中的监听端口（同时修改所有存在的配置文件）\n    private func configureApachePort(port: Int) async throws {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        var modified = false\n\n        // 修改工作空间路径的配置\n        if let config = WorkspaceManager.shared.config, config.isInitialized {\n            let wsPath = " = ")\n            )\n            loadSoftwareVersions()\n            throw error\n        }\n\n        loadSoftwareVersions()\n    }\n\n    /// Modifica el puerto de escucha en los archivos de configuración de Apache (modificando todos los archivos de configuración existentes)\n    private func configureApachePort(port: Int) async throws {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        var modified = false\n\n        // Modificar la configuración de la ruta del espacio de trabajo\n        if let config = WorkspaceManager.shared.config, config.isInitialized {\n            let wsPath = ";
"#\n                                if let regex = try? NSRegularExpression(pattern: pattern),\n                                   let match = regex.firstMatch(in: line, range: NSRange(line.startIndex..., in: line)),\n                                   match.numberOfRanges > 1,\n                                   let range = Range(match.range(at: 1), in: line),\n                                   let port = Int(String(line[range])) {\n                                    continuation.resume(returning: port)\n                                    return\n                                }\n                            }\n                        }\n                    }\n                    continuation.resume(returning: nil)\n                } catch {\n                    continuation.resume(returning: nil)\n                }\n            }\n        }\n    }\n\n    // MARK: - 默认站点路径\n\n    /// 获取当前活动的 Web 服务器默认站点路径\n    func getActiveDefaultSitePath() -> String? {\n        let settings = AppSettings.shared\n        let preferred = settings.preferredWebServer.lowercased()\n\n        // 根据首选服务器和运行状态决定\n        if preferred == " = "#\n                                if let regex = try? NSRegularExpression(pattern: pattern),\n                                   let match = regex.firstMatch(in: line, range: NSRange(line.startIndex..., in: line)),\n                                   match.numberOfRanges > 1,\n                                   let range = Range(match.range(at: 1), in: line),\n                                   let port = Int(String(line[range])) {\n                                    continuation.resume(returning: port)\n                                    return\n                                }\n                            }\n                        }\n                    }\n                    continuation.resume(returning: nil)\n                } catch {\n                    continuation.resume(returning: nil)\n                }\n            }\n        }\n    }\n\n    // MARK: - Ruta del sitio predeterminado\n\n    /// Obtiene la ruta del sitio predeterminado del servidor web activo actualmente\n    func getActiveDefaultSitePath() -> String? {\n        let settings = AppSettings.shared\n        let preferred = settings.preferredWebServer.lowercased()\n\n        // Determinar según el servidor preferido y su estado de ejecución\n        if preferred == ";
"\n                # ============================================\n                # 🔄 Nginx 反向代理配置 (Apache 后端)\n                # 此配置将请求转发到 Apache 后端服务器\n                # 前端端口: 80/443 (Nginx)\n                # 后端端口: \\(backendHttpPort)/\\(backendHttpsPort) (Apache)\n                # ============================================\n\n                # HTTP 服务器块 - 重定向到 HTTPS\n                server {\n                    listen 80;\n                    server_name \\(serverNames);\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # HTTPS 服务器块 - 反向代理到 Apache\n                server {\n                    listen 443 ssl;\n                    server_name \\(serverNames);\n\n                    # SSL 证书配置\n                    ssl_certificate \\(certPath);\n                    ssl_certificate_key \\(keyPath);\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                \\(authBasicBlocks)\n                    # 反向代理配置\n                    location / {\n                        proxy_pass \\(site.sslMode == .nginxTermination ? " = "\n                # ============================================\n                # 🔄 Configuración de proxy inverso de Nginx (backend Apache)\n                # Esta configuración redirige las solicitudes al servidor backend Apache\n                # Puerto frontend: 80/443 (Nginx)\n                # Puerto backend: \\(backendHttpPort)/\\(backendHttpsPort) (Apache)\n                # ============================================\n\n                # Bloque del servidor HTTP - Redirección a HTTPS\n                server {\n                    listen 80;\n                    server_name \\(serverNames);\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # Bloque del servidor HTTPS - Proxy inverso a Apache\n                server {\n                    listen 443 ssl;\n                    server_name \\(serverNames);\n\n                    # Configuración del certificado SSL\n                    ssl_certificate \\(certPath);\n                    ssl_certificate_key \\(keyPath);\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                \\(authBasicBlocks)\n                    # Configuración del proxy inverso\n                    location / {\n                        proxy_pass \\(site.sslMode == .nginxTermination ? ";
"]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n            return process.terminationStatus == 0\n        } catch {\n            return true // 无法执行时不判定为配置错误\n        }\n    }\n\n    /// 检查 Apache 配置语法（nonisolated，可在后台线程调用）\n    private nonisolated static func checkApacheConfigSyntax(homebrewPrefix: String) -> Bool {\n        let apacheBin = " = "]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n            return process.terminationStatus == 0\n        } catch {\n            return true // Si no se puede ejecutar, no se considera un error de configuración\n        }\n    }\n\n    /// Verifica la sintaxis de la configuración de Apache (nonisolated, puede llamarse desde un hilo en segundo plano)\n    private nonisolated static func checkApacheConfigSyntax(homebrewPrefix: String) -> Bool {\n        let apacheBin = ";
"\n    }\n}\n\n// MARK: - MySQL 连接器\n@MainActor\nclass MySQLConnector: ObservableObject {\n    @Published var isConnected = false\n    @Published var databases: [MySQLDatabaseInfo] = []\n    @Published var users: [MySQLUserInfo] = []\n    @Published var isLoading = false\n    @Published var errorMessage: String?\n\n    private var host: String = " = "\n    }\n}\n\n// MARK: - Conector MySQL\n@MainActor\nclass MySQLConnector: ObservableObject {\n    @Published var isConnected = false\n    @Published var databases: [MySQLDatabaseInfo] = []\n    @Published var users: [MySQLUserInfo] = []\n    @Published var isLoading = false\n    @Published var errorMessage: String?\n\n    private var host: String = ";
")\n                        .font(.system(size: 20))\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n\n            // 消息\n            Text(message)\n                .font(.system(size: 15))\n                .foregroundColor(.secondary)\n\n            Divider()\n\n            // 操作按钮\n            HStack(spacing: 16) {\n                Button(action: onViewLog) {\n                    Label(" = ")\n                        .font(.system(size: 20))\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n\n            // Mensaje\n            Text(message)\n                .font(.system(size: 15))\n                .foregroundColor(.secondary)\n\n            Divider()\n\n            // Botones de acción\n            HStack(spacing: 16) {\n                Button(action: onViewLog) {\n                    Label(";
" }\n\n    /// SSL 证书目录\n    var sslPath: String { " = " }\n\n    /// Directorio de certificados SSL\n    var sslPath: String { ";
"\n\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n\n            // 检测 location 块\n            if trimmed.hasPrefix(" = "\n\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n\n            // Detectar bloque location\n            if trimmed.hasPrefix(";
"确定要删除远程数据库连接「\\(db.name)」吗？此操作不会删除远程服务器上的数据。" = "¿Desea eliminar realmente la conexión remota a la base de datos «\\(db.name)»? Esta acción no eliminará los datos en el servidor remoto.";
")\n    }\n\n    // 获取带单位的配置值\n    private func getValueWithUnit(_ item: ConfigItem) -> String {\n        guard let unit = item.unit else { return item.value }\n\n        // MySQL 配置单位转换\n        switch unit {\n        case " = ")\n    }\n\n    // Obtener valor con unidad\n    private func getValueWithUnit(_ item: ConfigItem) -> String {\n        guard let unit = item.unit else { return item.value }\n\n        // Conversión de unidades para la configuración de MySQL\n        switch unit {\n        case ";
",\n                configLocations: [\n                    // Nginx 配置目录\n                    ConfigFileLocation(\n                        workspacePath: " = ",\n                configLocations: [\n                    // Directorio de configuración de Nginx\n                    ConfigFileLocation(\n                        workspacePath: ";
"数据库 \\(name) 已删除" = "La base de datos \\(name) ha sido eliminada";
"  // 数据库关联的用户名\n    @State private var showPermissionsSheet = false\n    @State private var showChangePasswordSheet = false\n    @State private var showImportSheet = false\n\n    private let systemDatabases = [" = "  // Nombre de usuario asociado a la base de datos\n    @State private var showPermissionsSheet = false\n    @State private var showChangePasswordSheet = false\n    @State private var showImportSheet = false\n\n    private let systemDatabases = [";
"\n                    }\n                }\n                continue\n            }\n\n            // 检测块结束\n            if trimmed.hasPrefix(" = "\n                    }\n                }\n                continue\n            }\n\n            // Detectar final del bloque\n            if trimmed.hasPrefix(";
") {\n            if !checkApacheConfigSyntax(homebrewPrefix: homebrewPrefix) {\n                return .configError\n            }\n        }\n\n        // 4. 检查 brew services 是否报告错误退出\n        let serviceStatus = getBrewServiceStatus(brewName, brewPath: brewPath)\n        if serviceStatus == " = ") {\n            if !checkApacheConfigSyntax(homebrewPrefix: homebrewPrefix) {\n                return .configError\n            }\n        }\n\n        // 4. Verificar si brew services informa una salida con error\n        let serviceStatus = getBrewServiceStatus(brewName, brewPath: brewPath)\n        if serviceStatus == ";
"]) {\n                    return .configError\n                }\n            }\n        }\n\n        // 4. 检查 brew services 状态\n        if let brewPath = brewPath, !brewName.isEmpty {\n            let status = getBrewServiceStatusSync(brewName, brewPath: brewPath)\n            if status == " = "]) {\n                    return .configError\n                }\n            }\n        }\n\n        // 4. Comprobar el estado de los servicios de brew\n        if let brewPath = brewPath, !brewName.isEmpty {\n            let status = getBrewServiceStatusSync(brewName, brewPath: brewPath)\n            if status == ";
", brewName])\n        }\n\n        // 2. Link 目标版本\n        let targetBrewName = software.brewName ?? software.id\n        // 使用 --overwrite --force 确保链接成功\n        let success = await runBrewCommand(brewPath, arguments: [" = ", brewName])\n        }\n\n        // 2. Enlazar la versión objetivo\n        let targetBrewName = software.brewName ?? software.id\n        // Usar --overwrite --force para asegurar que el enlace se realice correctamente\n        let success = await runBrewCommand(brewPath, arguments: [";
"\n\n                if failedCount == 0 {\n                    // 全部成功，关闭弹窗\n                    dismiss()\n                } else {\n                    // 有失败的，显示错误信息\n                    errorMessage = " = "\n\n                if failedCount == 0 {\n                    // Todo correcto, cerrar el cuadro de diálogo\n                    dismiss()\n                } else {\n                    // Algunas operaciones fallaron, mostrar mensaje de error\n                    errorMessage = ";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 工具栏\n    private var toolbar: some View {\n        HStack(spacing: 12) {\n            Button(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // Barra de herramientas\n    private var toolbar: some View {\n        HStack(spacing: 12) {\n            Button(";
"保存 Nginx 配置失败: \\(error)" = "Error al guardar la configuración de Nginx: \\(error)";
"))\n                }) {\n                    return (installed: service.installed, running: service.isRunning)\n                }\n\n                // 如果找不到服务，检查 installedWebServers\n                let isInstalled = siteManager.installedWebServers.contains(webServer)\n                return (installed: isInstalled, running: false)\n            }\n\n            // 启动定时计算站点容量（每5分钟计算一次）\n            siteManager.startPeriodicSizeCalculation(interval: 300)\n        }\n        .onDisappear {\n            // 停止定时计算\n            siteManager.stopPeriodicSizeCalculation()\n        }\n        .sheetWithLocale(isPresented: $showAdvancedSettings) {\n            AdvancedSettingsSheet()\n        }\n        .sheetWithLocale(isPresented: $showAddSiteSheet) {\n            AddSiteSheet()\n                .environmentObject(siteManager)\n        }\n        .sheetWithLocale(isPresented: $showRepairSheet) {\n            SiteRepairSheet()\n                .environmentObject(siteManager)\n        }\n        .sheetWithLocale(isPresented: $showVhostSyncSheet) {\n            VhostSyncSheet()\n                .environmentObject(siteManager)\n                .environmentObject(appState)\n        }\n        .sheetWithLocale(isPresented: $showBatchImportSheet) {\n            BatchImportByDirectorySheet()\n                .environmentObject(siteManager)\n                .environmentObject(appState)\n        }\n        .sheetWithLocale(isPresented: $showHealthCheckSheet) {\n            let sitesToCheck = siteManager.sites.filter { selectedSites.contains($0.id) }\n            SiteHealthCheckView(sites: sitesToCheck)\n        }\n    }\n\n    // 表头\n    private var tableHeader: some View {\n        HStack(alignment: .center, spacing: 8) {\n            // 全选复选框\n            Button(action: {\n                if !filteredSites.isEmpty && selectedSites.count == filteredSites.count {\n                    // 取消全选\n                    selectedSites.removeAll()\n                } else {\n                    // 全选\n                    selectedSites = Set(filteredSites.map { $0.id })\n                }\n            }) {\n                ZStack {\n                    Circle()\n                        .fill(Color.white)\n                        .frame(width: 18, height: 18)\n                        .overlay(\n                            Circle()\n                                .stroke(AppColors.border, lineWidth: 1)\n                        )\n\n                    if !filteredSites.isEmpty && selectedSites.count == filteredSites.count {\n                        Image(systemName: " = "))\n                }) {\n                    return (installed: service.installed, running: service.isRunning)\n                }\n\n                // Si no se encuentra el servicio, verificar installedWebServers\n                let isInstalled = siteManager.installedWebServers.contains(webServer)\n                return (installed: isInstalled, running: false)\n            }\n\n            // Iniciar el cálculo periódico del tamaño de los sitios (cada 5 minutos)\n            siteManager.startPeriodicSizeCalculation(interval: 300)\n        }\n        .onDisappear {\n            // Detener el cálculo periódico\n            siteManager.stopPeriodicSizeCalculation()\n        }\n        .sheetWithLocale(isPresented: $showAdvancedSettings) {\n            AdvancedSettingsSheet()\n        }\n        .sheetWithLocale(isPresented: $showAddSiteSheet) {\n            AddSiteSheet()\n                .environmentObject(siteManager)\n        }\n        .sheetWithLocale(isPresented: $showRepairSheet) {\n            SiteRepairSheet()\n                .environmentObject(siteManager)\n        }\n        .sheetWithLocale(isPresented: $showVhostSyncSheet) {\n            VhostSyncSheet()\n                .environmentObject(siteManager)\n                .environmentObject(appState)\n        }\n        .sheetWithLocale(isPresented: $showBatchImportSheet) {\n            BatchImportByDirectorySheet()\n                .environmentObject(siteManager)\n                .environmentObject(appState)\n        }\n        .sheetWithLocale(isPresented: $showHealthCheckSheet) {\n            let sitesToCheck = siteManager.sites.filter { selectedSites.contains($0.id) }\n            SiteHealthCheckView(sites: sitesToCheck)\n        }\n    }\n\n    // Encabezado de la tabla\n    private var tableHeader: some View {\n        HStack(alignment: .center, spacing: 8) {\n            // Casilla de verificación para seleccionar todo\n            Button(action: {\n                if !filteredSites.isEmpty && selectedSites.count == filteredSites.count {\n                    // Deseleccionar todo\n                    selectedSites.removeAll()\n                } else {\n                    // Seleccionar todo\n                    selectedSites = Set(filteredSites.map { $0.id })\n                }\n            }) {\n                ZStack {\n                    Circle()\n                        .fill(Color.white)\n                        .frame(width: 18, height: 18)\n                        .overlay(\n                            Circle()\n                                .stroke(AppColors.border, lineWidth: 1)\n                        )\n\n                    if !filteredSites.isEmpty && selectedSites.count == filteredSites.count {\n                        Image(systemName: ";
"\n        }\n        \n        // 更新 documentRoot\n        documentRoot = fullPath\n        \n        // 获取父目录路径\n        let parentDir = (siteDir as NSString).deletingLastPathComponent\n        \n        // 先创建所有需要的目录\n        // 1. 创建父目录（如果不存在）\n        if !parentDir.isEmpty && parentDir != " = "\n        }\n        \n        // Actualizar documentRoot\n        documentRoot = fullPath\n        \n        // Obtener la ruta del directorio padre\n        let parentDir = (siteDir as NSString).deletingLastPathComponent\n        \n        // Primero crear todos los directorios necesarios\n        // 1. Crear el directorio padre (si no existe)\n        if !parentDir.isEmpty && parentDir != ";
"))\n                        .font(.system(size: 14, weight: .semibold))\n                        .foregroundColor(.primary)\n\n                    // Web 服务器标签\n                    Text(config.parsed.webServer.rawValue)\n                        .font(.system(size: 11))\n                        .padding(.horizontal, 8)\n                        .padding(.vertical, 3)\n                        .background(config.parsed.webServer == .nginx ? Color.green.opacity(AppTokens.Opacity.hover) : Color.blue.opacity(AppTokens.Opacity.hover))\n                        .foregroundColor(config.parsed.webServer == .nginx ? .green : .blue)\n                        .cornerRadius(4)\n\n                    // SSL 标签\n                    if config.parsed.hasSSL {\n                        HStack(spacing: 3) {\n                            Image(systemName: " = "))\n                        .font(.system(size: 14, weight: .semibold))\n                        .foregroundColor(.primary)\n\n                    // Etiqueta del servidor web\n                    Text(config.parsed.webServer.rawValue)\n                        .font(.system(size: 11))\n                        .padding(.horizontal, 8)\n                        .padding(.vertical, 3)\n                        .background(config.parsed.webServer == .nginx ? Color.green.opacity(AppTokens.Opacity.hover) : Color.blue.opacity(AppTokens.Opacity.hover))\n                        .foregroundColor(config.parsed.webServer == .nginx ? .green : .blue)\n                        .cornerRadius(4)\n\n                    // Etiqueta SSL\n                    if config.parsed.hasSSL {\n                        HStack(spacing: 3) {\n                            Image(systemName: ";
"))\n                    .font(.system(size: 13))\n            }\n            .toggleStyle(.switch)\n            .controlSize(.small)\n\n            // 更改日志路径按钮\n            Button(" = "))\n                    .font(.system(size: 13))\n            }\n            .toggleStyle(.switch)\n            .controlSize(.small)\n\n            // Botón para cambiar la ruta del registro\n            Button(";
"\n        }\n\n        do {\n            // 读取文件内容（限制最后 1000 行）\n            let content = try String(contentsOfFile: path, encoding: .utf8)\n            let lines = content.components(separatedBy: .newlines)\n\n            // 只保留最后 1000 行\n            let maxLines = 1000\n            if lines.count > maxLines {\n                let lastLines = lines.suffix(maxLines)\n                return " = "\n        }\n\n        do {\n            // Leer el contenido del archivo (limitando a las últimas 1000 líneas)\n            let content = try String(contentsOfFile: path, encoding: .utf8)\n            let lines = content.components(separatedBy: .newlines)\n\n            // Conservar solo las últimas 1000 líneas\n            let maxLines = 1000\n            if lines.count > maxLines {\n                let lastLines = lines.suffix(maxLines)\n                return ";
"未知的 caching_sha2_password 状态: \\(status)" = "Estado desconocido de caching_sha2_password: \\(status)";
")\n\n        // 检查站点名称是否已存在\n        var finalName = siteName\n        var counter = 1\n        while sites.contains(where: { $0.name == finalName }) {\n            finalName = " = ")\n\n        // Verificar si el nombre del sitio ya existe\n        var finalName = siteName\n        var counter = 1\n        while sites.contains(where: { $0.name == finalName }) {\n            finalName = ";
"\n\n    // MARK: - 连接管理\n\n    /// 设置连接信息\n    func configure(host: String, port: Int, username: String, password: String) {\n        self.host = host\n        self.port = port\n        self.username = username\n        self.password = password\n    }\n\n    /// 使用原生客户端执行 SQL\n    private func executeWithNativeClient(_ sql: String) async -> (success: Bool, output: String?, error: String?) {\n        do {\n            let client = NativeMySQLClient(host: host, port: port, username: username, password: password)\n            try await client.connect()\n            let result = try await client.query(sql)\n            client.disconnect()\n\n            if let error = result.errorMessage {\n                return (false, nil, error)\n            }\n\n            // 将结果转换为制表符分隔的文本格式\n            var output = " = "\n\n    // MARK: - Gestión de conexiones\n\n    /// Configurar la información de conexión\n    func configure(host: String, port: Int, username: String, password: String) {\n        self.host = host\n        self.port = port\n        self.username = username\n        self.password = password\n    }\n\n    /// Ejecutar SQL con el cliente nativo\n    private func executeWithNativeClient(_ sql: String) async -> (success: Bool, output: String?, error: String?) {\n        do {\n            let client = NativeMySQLClient(host: host, port: port, username: username, password: password)\n            try await client.connect()\n            let result = try await client.query(sql)\n            client.disconnect()\n\n            if let error = result.errorMessage {\n                return (false, nil, error)\n            }\n\n            // Convertir el resultado al formato de texto separado por tabuladores\n            var output = ";
"\n    }\n\n    // 更新预估内存\n    private func updateEstimatedMemory() {\n        estimatedMemory = PerformancePresets.calculateEstimatedMemory(configs: configValues)\n    }\n\n    // 获取 MySQL 配置文件路径\n    private func getMySQLConfigPath() -> String? {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        let paths = [\n            " = "\n    }\n\n    // Actualizar la memoria estimada\n    private func updateEstimatedMemory() {\n        estimatedMemory = PerformancePresets.calculateEstimatedMemory(configs: configValues)\n    }\n\n    // Obtener la ruta del archivo de configuración de MySQL\n    private func getMySQLConfigPath() -> String? {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        let paths = [\n            ";
"智谱" = "Zhipu";
") {\n                                // 解析端口号\n                                let pattern = #" = ") {\n                                // Analizar el número de puerto\n                                let pattern = #";
", value: 端口号)\n    @Published var phpPorts: [String: Int] {\n        didSet {\n            if let data = try? JSONEncoder().encode(phpPorts) {\n                UserDefaults.standard.set(data, forKey: " = ", value: Número de puerto)\n    @Published var phpPorts: [String: Int] {\n        didSet {\n            if let data = try? JSONEncoder().encode(phpPorts) {\n                UserDefaults.standard.set(data, forKey: ";
"版本 \\(software.version)" = "Versión \\(software.version)";
"#\n\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {\n            return nil\n        }\n\n        let nsString = line as NSString\n        let range = NSRange(location: 0, length: nsString.length)\n\n        guard let match = regex.firstMatch(in: line, options: [], range: range) else {\n            return nil\n        }\n\n        let ip = nsString.substring(with: match.range(at: 1))\n        let timestampStr = nsString.substring(with: match.range(at: 2))\n        let method = nsString.substring(with: match.range(at: 3))\n        let path = nsString.substring(with: match.range(at: 4))\n        let statusCodeStr = nsString.substring(with: match.range(at: 5))\n        let sizeStr = nsString.substring(with: match.range(at: 6))\n        let referer = nsString.substring(with: match.range(at: 7))\n        let userAgent = nsString.substring(with: match.range(at: 8))\n\n        // 解析时间戳\n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = " = "#\n\n        guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else {\n            return nil\n        }\n\n        let nsString = line as NSString\n        let range = NSRange(location: 0, length: nsString.length)\n\n        guard let match = regex.firstMatch(in: line, options: [], range: range) else {\n            return nil\n        }\n\n        let ip = nsString.substring(with: match.range(at: 1))\n        let timestampStr = nsString.substring(with: match.range(at: 2))\n        let method = nsString.substring(with: match.range(at: 3))\n        let path = nsString.substring(with: match.range(at: 4))\n        let statusCodeStr = nsString.substring(with: match.range(at: 5))\n        let sizeStr = nsString.substring(with: match.range(at: 6))\n        let referer = nsString.substring(with: match.range(at: 7))\n        let userAgent = nsString.substring(with: match.range(at: 8))\n\n        // Analizar la marca de tiempo\n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = ";
")\n                        .foregroundColor(.secondary)\n                        .frame(width: 70, alignment: .trailing)\n                    Text(databaseName)\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                }\n\n                // 用户名\n                HStack(spacing: 16) {\n                    Text(" = ")\n                        .foregroundColor(.secondary)\n                        .frame(width: 70, alignment: .trailing)\n                    Text(databaseName)\n                        .frame(maxWidth: .infinity, alignment: .leading)\n                }\n\n                // Nombre de usuario\n                HStack(spacing: 16) {\n                    Text(";
"\n                }\n\n                // 使用较低的优先级（130+）\n                additionalVersions.append((displayName, packageName, 5432, 130 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// 动态检测所有已安装的 Redis 版本（不在预定义列表中的）\n    private nonisolated func detectAdditionalRedisVersions(\n        predefinedBrewNames: Set<String>\n    ) -> [(name: String, brewName: String, port: Int, priority: Int)] {\n        guard let packages = AppState.cachedInstalledPackages else { return [] }\n\n        var additionalVersions: [(name: String, brewName: String, port: Int, priority: Int)] = []\n\n        for (packageName, _) in packages {\n            // 匹配 redis 或 redis@x.x 格式\n            if packageName == " = "\n                }\n\n                // Usar una prioridad más baja (130+)\n                additionalVersions.append((displayName, packageName, 5432, 130 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// Detectar dinámicamente todas las versiones de Redis instaladas (no incluidas en la lista predefinida)\n    private nonisolated func detectAdditionalRedisVersions(\n        predefinedBrewNames: Set<String>\n    ) -> [(name: String, brewName: String, port: Int, priority: Int)] {\n        guard let packages = AppState.cachedInstalledPackages else { return [] }\n\n        var additionalVersions: [(name: String, brewName: String, port: Int, priority: Int)] = []\n\n        for (packageName, _) in packages {\n            // Coincidir con formatos como redis o redis@x.x\n            if packageName == ";
")\n                        .font(.system(size: 14, weight: .medium))\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            VStack(alignment: .leading, spacing: 20) {\n                // 数据库名\n                HStack(spacing: 16) {\n                    Text(" = ")\n                        .font(.system(size: 14, weight: .medium))\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            VStack(alignment: .leading, spacing: 20) {\n                // Nombre de la base de datos\n                HStack(spacing: 16) {\n                    Text(";
"\n                )\n            } catch {\n                // 备份失败不阻止保存，只记录警告\n                DispatchQueue.main.async {\n                    self.updateLastStatusMessage(" = "\n                )\n            } catch {\n                // El fallo en la copia de seguridad no impide guardar; solo se registra una advertencia\n                DispatchQueue.main.async {\n                    self.updateLastStatusMessage(";
").first?.trimmingCharacters(in: .whitespaces) ?? firstDomain\n\n        guard !cleanDomain.isEmpty else { return }\n        \n        // 如果网站目录为空，或者网站目录不包含完整域名，自动生成\n        let defaultRoot = appSettings.defaultSiteRoot.isEmpty ? " = ").first?.trimmingCharacters(in: .whitespaces) ?? firstDomain\n\n        guard !cleanDomain.isEmpty else { return }\n        \n        // Si el directorio del sitio está vacío, o no contiene el nombre de dominio completo, generar uno automáticamente\n        let defaultRoot = appSettings.defaultSiteRoot.isEmpty ? ";
") {\n                            workingDirectory = String(line.dropFirst(4))\n                        }\n                    }\n                }\n            } catch {\n                // 忽略错误\n            }\n        }\n\n        return (executablePath, commandLine, user, parentPid, startTime, workingDirectory)\n    }\n\n    /// 检查指定端口是否被占用\n    private func checkPort(_ port: Int, serviceName: String) -> PortItem? {\n        guard let lsofPath = findLsofPath() else {\n            return nil\n        }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: lsofPath)\n        process.arguments = [" = ") {\n                            workingDirectory = String(line.dropFirst(4))\n                        }\n                    }\n                }\n            } catch {\n                // Ignorar el error\n            }\n        }\n\n        return (executablePath, commandLine, user, parentPid, startTime, workingDirectory)\n    }\n\n    /// Verificar si un puerto específico está ocupado\n    private func checkPort(_ port: Int, serviceName: String) -> PortItem? {\n        guard let lsofPath = findLsofPath() else {\n            return nil\n        }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: lsofPath)\n        process.arguments = [";
"\n                continue\n            }\n\n            // 检测 IfModule 结束\n            if trimmed.contains(" = "\n                continue\n            }\n\n            // 检测 IfModule 结束\n            if trimmed.contains(";
"\n        default:\n            return defaultValue\n        }\n    }\n\n    /// Nginx 动态路径\n    private static func getNginxDynamicPath(key: String, config: WorkspaceConfig, defaultValue: String) -> String {\n        switch key {\n        case " = "\n        default:\n            return defaultValue\n        }\n    }\n\n    /// Nginx 动态路径\n    private static func getNginxDynamicPath(key: String, config: WorkspaceConfig, defaultValue: String) -> String {\n        switch key {\n        case ";
")\n                        .font(.headline)\n                        .padding(.horizontal)\n                    TextEditor(text: .constant(source))\n                        .font(.system(.body, design: .monospaced))\n                        .macTextEditor()\n                }\n                .frame(maxWidth: .infinity)\n\n                Divider()\n\n                // 转换后规则\n                VStack(alignment: .leading, spacing: 8) {\n                    Text(" = ")\n                        .font(.headline)\n                        .padding(.horizontal)\n                    TextEditor(text: .constant(source))\n                        .font(.system(.body, design: .monospaced))\n                        .macTextEditor()\n                }\n                .frame(maxWidth: .infinity)\n\n                Divider()\n\n                // 转换后规则\n                VStack(alignment: .leading, spacing: 8) {\n                    Text(";
") ? String(siteDirectory.dropLast()) : siteDirectory\n        let fileManager = FileManager.default\n        \n        guard let contents = try? fileManager.contentsOfDirectory(atPath: siteDir) else {\n            availableSubdirs = []\n            return\n        }\n        \n        var subdirs: [String] = []\n        for item in contents {\n            var isDirectory: ObjCBool = false\n            let fullPath = (siteDir as NSString).appendingPathComponent(item)\n            if fileManager.fileExists(atPath: fullPath, isDirectory: &isDirectory) && isDirectory.boolValue {\n                // 排除隐藏目录和常见目录\n                if !item.hasPrefix(" = ") ? String(siteDirectory.dropLast()) : siteDirectory\n        let fileManager = FileManager.default\n        \n        guard let contents = try? fileManager.contentsOfDirectory(atPath: siteDir) else {\n            availableSubdirs = []\n            return\n        }\n        \n        var subdirs: [String] = []\n        for item in contents {\n            var isDirectory: ObjCBool = false\n            let fullPath = (siteDir as NSString).appendingPathComponent(item)\n            if fileManager.fileExists(atPath: fullPath, isDirectory: &isDirectory) && isDirectory.boolValue {\n                // 排除隐藏目录和常见目录\n                if !item.hasPrefix(";
"确定要删除选中的 \\(selectedSites.count) 个站点吗？站点将被移到回收站，可以恢复。" = "确定要删除选中的 \\(selectedSites.count) 个站点吗？站点将被移到回收站，可以恢复。";
"\n    }\n\n    // Nginx html 目录（默认站点根目录）\n    private var nginxHtmlPath: String {\n        " = "\n    }\n\n    // Nginx html 目录（默认站点根目录）\n    private var nginxHtmlPath: String {\n        ";
"❌ 扫描证书目录失败: \\(error)" = "❌ 扫描证书目录失败: \\(error)";
"].contains(where: { service.name.lowercased().contains($0) })\n\n        if !supportsReload {\n            // 不支持reload的服务，使用restart\n            showServiceAlert(\n                title: " = "].contains(where: { service.name.lowercased().contains($0) })\n\n        if !supportsReload {\n            // 不支持reload的服务，使用restart\n            showServiceAlert(\n                title: ";
"  // 导入进度文本\n\n    // 批量设置的值\n    @State private var batchCreateFTP = false\n    @State private var batchCreateDatabase = false\n    @State private var batchEnableSSL = false\n    @State private var batchSSLMode: SSLMode = .nginxTermination\n    @State private var batchPHPVersion = " = "  // 导入进度文本\n\n    // 批量设置的值\n    @State private var batchCreateFTP = false\n    @State private var batchCreateDatabase = false\n    @State private var batchEnableSSL = false\n    @State private var batchSSLMode: SSLMode = .nginxTermination\n    @State private var batchPHPVersion = ";
"扫描时间: \\(formatDate(results.scanTime))" = "扫描时间: \\(formatDate(results.scanTime))";
")\n        }\n\n        // 如果需要更新密码且是本地数据库，修改 MySQL 用户密码\n        if let password = password, database.type == .local {\n            let success = await changeMySQLUserPassword(username: database.username, newPassword: password)\n            if !success {\n                throw DatabaseError.operationFailed(" = ")\n        }\n\n        // 如果需要更新密码且是本地数据库，修改 MySQL 用户密码\n        if let password = password, database.type == .local {\n            let success = await changeMySQLUserPassword(username: database.username, newPassword: password)\n            if !success {\n                throw DatabaseError.operationFailed(";
", version: ver,\n                    installed: isInstalled, isDefault: isDefault, isRunning: isRunning,\n                    brewName: pkg, port: port\n                ))\n            } else {\n                // 可安装的版本\n                let displayVer = pkg.replacingOccurrences(of: " = ", version: ver,\n                    installed: isInstalled, isDefault: isDefault, isRunning: isRunning,\n                    brewName: pkg, port: port\n                ))\n            } else {\n                // 可安装的版本\n                let displayVer = pkg.replacingOccurrences(of: ";
")\n                        .font(.system(size: 8))\n                        .foregroundColor(.secondary)\n                        .padding(.leading, 2)\n                }\n            }\n            .frame(width: 120, alignment: .leading)\n            .contentShape(Rectangle())\n            .onHover { hovering in\n                isHoveringName = hovering\n                if hovering {\n                    NSCursor.pointingHand.push()\n                } else {\n                    NSCursor.pop()\n                }\n                \n                // 多唯一域名时，hover显示下拉菜单\n                if uniqueDomainsCount > 1 && hovering {\n                    withAnimation(.easeInOut(duration: 0.2)) {\n                        showDomainDropdown = true\n                        showPHPDropdown = false\n                        showWebServerDropdown = false\n                    }\n                }\n            }\n            .onTapGesture {\n                // 如果只有一个唯一域名，直接打开\n                if uniqueDomainsCount == 1 {\n                    openDomainInBrowser(site.primaryDomain)\n                } else {\n                    // 多个域名时，切换下拉菜单\n                    withAnimation(.easeInOut(duration: 0.2)) {\n                        showDomainDropdown.toggle()\n                        showPHPDropdown = false\n                        showWebServerDropdown = false\n                    }\n                }\n            }\n            .overlay(alignment: .topLeading) {\n                // 域名下拉菜单\n                if showDomainDropdown && uniqueDomainsCount > 1 {\n                    VStack(alignment: .leading, spacing: 0) {\n                        ForEach(site.domains, id: \\.self) { domain in\n                            Button(action: {\n                                openDomainInBrowser(domain)\n                                withAnimation(.easeInOut(duration: 0.2)) {\n                                    showDomainDropdown = false\n                                }\n                            }) {\n                                HStack(spacing: 6) {\n                                    Image(systemName: " = ")\n                        .font(.system(size: 8))\n                        .foregroundColor(.secondary)\n                        .padding(.leading, 2)\n                }\n            }\n            .frame(width: 120, alignment: .leading)\n            .contentShape(Rectangle())\n            .onHover { hovering in\n                isHoveringName = hovering\n                if hovering {\n                    NSCursor.pointingHand.push()\n                } else {\n                    NSCursor.pop()\n                }\n                \n                // 多唯一域名时，hover显示下拉菜单\n                if uniqueDomainsCount > 1 && hovering {\n                    withAnimation(.easeInOut(duration: 0.2)) {\n                        showDomainDropdown = true\n                        showPHPDropdown = false\n                        showWebServerDropdown = false\n                    }\n                }\n            }\n            .onTapGesture {\n                // 如果只有一个唯一域名，直接打开\n                if uniqueDomainsCount == 1 {\n                    openDomainInBrowser(site.primaryDomain)\n                } else {\n                    // 多个域名时，切换下拉菜单\n                    withAnimation(.easeInOut(duration: 0.2)) {\n                        showDomainDropdown.toggle()\n                        showPHPDropdown = false\n                        showWebServerDropdown = false\n                    }\n                }\n            }\n            .overlay(alignment: .topLeading) {\n                // 域名下拉菜单\n                if showDomainDropdown && uniqueDomainsCount > 1 {\n                    VStack(alignment: .leading, spacing: 0) {\n                        ForEach(site.domains, id: \\.self) { domain in\n                            Button(action: {\n                                openDomainInBrowser(domain)\n                                withAnimation(.easeInOut(duration: 0.2)) {\n                                    showDomainDropdown = false\n                                }\n                            }) {\n                                HStack(spacing: 6) {\n                                    Image(systemName: ";
"🔍 处理配置文件: \\(filePath)" = "🔍 处理配置文件: \\(filePath)";
")\n\n        return paths\n    }\n\n    // MARK: - Apache 路径\n\n    /// 获取 Apache 配置文件路径（优先返回存在的文件）\n    func getApacheConfigPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.apacheConfigFile\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        return " = ")\n\n        return paths\n    }\n\n    // MARK: - Apache 路径\n\n    /// 获取 Apache 配置文件路径（优先返回存在的文件）\n    func getApacheConfigPath() -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.apacheConfigFile\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        return ";
")\n                    .font(.subheadline)\n                    .foregroundColor(.secondary)\n            }\n\n            // 提示信息\n            Text(" = ")\n                    .font(.subheadline)\n                    .foregroundColor(.secondary)\n            }\n\n            // 提示信息\n            Text(";
"修复配置失败: \\(error.localizedDescription)" = "修复配置失败: \\(error.localizedDescription)";
")\n                .foregroundColor(.secondary)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n    }\n\n    // MARK: - 扩展列表视图\n    private var extensionListView: some View {\n        ScrollView {\n            LazyVStack(spacing: 0) {\n                extensionTableHeader\n                Divider()\n\n                ForEach(filteredExtensions) { ext in\n                    ExtensionRowView(\n                        extensionInfo: ext,\n                        isOperating: extensionManager.operatingExtension == ext.meta.name,\n                        onInstall: { installExtension(ext.meta) },\n                        onUninstall: { uninstallExtension(ext.meta) }\n                    )\n                    Divider()\n                }\n            }\n        }\n    }\n\n    // 过滤后的扩展列表\n    private var filteredExtensions: [PHPExtensionInfo] {\n        let extensions = extensionManager.getExtensions(for: selectedVersion)\n        if displayMode == .installed {\n            return extensions.filter { $0.isEnabled }\n        }\n        return extensions\n    }\n\n    // 表头\n    private var extensionTableHeader: some View {\n        HStack(spacing: 0) {\n            Text(" = ")\n                .foregroundColor(.secondary)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n    }\n\n    // MARK: - 扩展列表视图\n    private var extensionListView: some View {\n        ScrollView {\n            LazyVStack(spacing: 0) {\n                extensionTableHeader\n                Divider()\n\n                ForEach(filteredExtensions) { ext in\n                    ExtensionRowView(\n                        extensionInfo: ext,\n                        isOperating: extensionManager.operatingExtension == ext.meta.name,\n                        onInstall: { installExtension(ext.meta) },\n                        onUninstall: { uninstallExtension(ext.meta) }\n                    )\n                    Divider()\n                }\n            }\n        }\n    }\n\n    // 过滤后的扩展列表\n    private var filteredExtensions: [PHPExtensionInfo] {\n        let extensions = extensionManager.getExtensions(for: selectedVersion)\n        if displayMode == .installed {\n            return extensions.filter { $0.isEnabled }\n        }\n        return extensions\n    }\n\n    // 表头\n    private var extensionTableHeader: some View {\n        HStack(spacing: 0) {\n            Text(";
"[SiteManager] 删除 hosts 条目失败: \\(domain) - \\(error)" = "[SiteManager] 删除 hosts 条目失败: \\(domain) - \\(error)";
"]),\n\n        // ===== 复制配置 =====\n        " = "]),\n\n        // ===== 复制配置 =====\n        ";
")\n            return nil\n        }\n    }\n\n    /// 保存服务列表到缓存\n    private func saveServicesToCache() {\n        do {\n            let data = try JSONEncoder().encode(services)\n            try data.write(to: servicesCacheURL)\n        } catch {\n            print(" = ")\n            return nil\n        }\n    }\n\n    /// Save service list to cache\n    private func saveServicesToCache() {\n        do {\n            let data = try JSONEncoder().encode(services)\n            try data.write(to: servicesCacheURL)\n        } catch {\n            print(";
" ? 0 : (Int(sizeStr) ?? 0)\n\n        return LogEntry(\n            timestamp: timestamp,\n            ip: ip,\n            method: method,\n            path: path,\n            statusCode: statusCode,\n            size: size,\n            referer: referer,\n            userAgent: userAgent,\n            rawLine: line\n        )\n    }\n\n    // MARK: - 解析错误日志\n    private func parseErrorLog(path: String, limit: Int) throws -> [SiteErrorLogEntry] {\n        // 使用tail命令读取最后N行\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = " ? 0 : (Int(sizeStr) ?? 0)\n\n        return LogEntry(\n            timestamp: timestamp,\n            ip: ip,\n            method: method,\n            path: path,\n            statusCode: statusCode,\n            size: size,\n            referer: referer,\n            userAgent: userAgent,\n            rawLine: line\n        )\n    }\n\n    // MARK: - Parse error logs\n    private func parseErrorLog(path: String, limit: Int) throws -> [SiteErrorLogEntry] {\n        // Use tail command to read the last N lines\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: ";
")\n                            .font(.system(size: 10))\n                            .foregroundColor(.secondary)\n                    }\n                    .buttonStyle(.plain)\n                }\n            }\n\n            // 进度条或错误信息\n            if isFailed {\n                failedContentView\n            } else {\n                progressContentView\n            }\n        }\n        .padding(10)\n        .background(statusColor.opacity(AppTokens.Opacity.ultraLight))\n        .cornerRadius(6)\n        .overlay(\n            RoundedRectangle(cornerRadius: 6)\n                .stroke(statusColor.opacity(AppTokens.Opacity.border), lineWidth: 1)\n        )\n    }\n\n    private var progressContentView: some View {\n        VStack(alignment: .leading, spacing: 4) {\n            ProgressView(value: installing.progress.progress)\n                .progressViewStyle(.linear)\n                .tint(.blue)\n\n            Text(installing.progress.currentStep)\n                .font(.system(size: 10))\n                .foregroundColor(.secondary)\n                .lineLimit(1)\n        }\n    }\n\n    private var failedContentView: some View {\n        VStack(alignment: .leading, spacing: 4) {\n            Text(installing.progress.errorMessage ?? " = ")\n                            .font(.system(size: 10))\n                            .foregroundColor(.secondary)\n                    }\n                    .buttonStyle(.plain)\n                }\n            }\n\n            // Progress bar or error message\n            if isFailed {\n                failedContentView\n            } else {\n                progressContentView\n            }\n        }\n        .padding(10)\n        .background(statusColor.opacity(AppTokens.Opacity.ultraLight))\n        .cornerRadius(6)\n        .overlay(\n            RoundedRectangle(cornerRadius: 6)\n                .stroke(statusColor.opacity(AppTokens.Opacity.border), lineWidth: 1)\n        )\n    }\n\n    private var progressContentView: some View {\n        VStack(alignment: .leading, spacing: 4) {\n            ProgressView(value: installing.progress.progress)\n                .progressViewStyle(.linear)\n                .tint(.blue)\n\n            Text(installing.progress.currentStep)\n                .font(.system(size: 10))\n                .foregroundColor(.secondary)\n                .lineLimit(1)\n        }\n    }\n\n    private var failedContentView: some View {\n        VStack(alignment: .leading, spacing: 4) {\n            Text(installing.progress.errorMessage ?? ";
"\n    @StateObject private var remoteConnector = MySQLConnector()  // 远程连接器\n    @State private var showAddDatabaseSheet = false  // 添加数据库弹窗\n    @State private var isExporting = false  // 导出状态\n    @State private var exportingDatabase: String? = nil  // 正在导出的数据库\n\n    var body: some View {\n        VStack(spacing: 0) {\n            if isLoading {\n                loadingView\n            } else if let error = errorMessage {\n                errorView(error)\n            } else {\n                if tab.isLocal {\n                    databasesList\n                } else {\n                    remoteDatabasesList\n                }\n            }\n        }\n        .onAppear {\n            fetchDatabases()\n        }\n    }\n\n    // 远程连接简化视图\n    private var remoteConnectionView: some View {\n        VStack(spacing: 24) {\n            Spacer()\n\n            // 连接图标\n            Image(systemName: " = "\n    @StateObject private var remoteConnector = MySQLConnector()  // Remote connector\n    @State private var showAddDatabaseSheet = false  // Add database sheet\n    @State private var isExporting = false  // Export status\n    @State private var exportingDatabase: String? = nil  // Database being exported\n\n    var body: some View {\n        VStack(spacing: 0) {\n            if isLoading {\n                loadingView\n            } else if let error = errorMessage {\n                errorView(error)\n            } else {\n                if tab.isLocal {\n                    databasesList\n                } else {\n                    remoteDatabasesList\n                }\n            }\n        }\n        .onAppear {\n            fetchDatabases()\n        }\n    }\n\n    // Simplified remote connection view\n    private var remoteConnectionView: some View {\n        VStack(spacing: 24) {\n            Spacer()\n\n            // Connection icon\n            Image(systemName: ";
"复制默认首页失败: \\(error.localizedDescription)" = "Failed to copy default homepage: \\(error.localizedDescription)";
" {\n                inMysqldSection = true\n                mysqldSectionIndex = i\n                continue\n            }\n\n            // 检测其他段开始\n            if trimmed.hasPrefix(" = " {\n                inMysqldSection = true\n                mysqldSectionIndex = i\n                continue\n            }\n\n            // Detect other section starts\n            if trimmed.hasPrefix(";
"有 \\(missingPorts.count) 个端口未监听" = "\\(missingPorts.count) port(s) not listening";
")\n        }\n\n        // 更新备份状态\n        if let index = databases.firstIndex(where: { $0.id == database.id }) {\n            databases[index].backupStatus = .hasBackup\n            databases[index].backupCount += 1\n            saveDatabases()\n        }\n    }\n\n    // 执行 mysqldump 备份\n    private func performMySQLDump(databaseName: String, to path: URL) async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqldumpPath = MySQLClientPaths.findMysqldump() else {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqldumpPath)\n                process.arguments = [" = ")\n        }\n\n        // Update backup status\n        if let index = databases.firstIndex(where: { $0.id == database.id }) {\n            databases[index].backupStatus = .hasBackup\n            databases[index].backupCount += 1\n            saveDatabases()\n        }\n    }\n\n    // Perform mysqldump backup\n    private func performMySQLDump(databaseName: String, to path: URL) async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqldumpPath = MySQLClientPaths.findMysqldump() else {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqldumpPath)\n                process.arguments = [";
"  // 通过 SQL 检测到的 MySQL 版本\n    @State private var showRemoteAddDatabaseSheet = false  // 远程数据库创建弹窗\n    @StateObject private var remoteConnector = MySQLConnector()  // 远程数据库连接器\n    @State private var hideSystemDatabases = true  // 是否隐藏系统数据库，默认隐藏\n    @State private var showMySQLConfigSheet = false  // MySQL 配置文件 Sheet\n    @State private var showMySQLPerformanceSheet = false  // MySQL 性能优化 Sheet\n\n    // 浮动提示相关\n    @State private var showPasswordTip = false  // 显示密码检测失败提示\n    @State private var passwordTipMessage = " = "  // MySQL version detected via SQL\n    @State private var showRemoteAddDatabaseSheet = false  // Remote database creation sheet\n    @StateObject private var remoteConnector = MySQLConnector()  // Remote database connector\n    @State private var hideSystemDatabases = true  // Whether to hide system databases (default: hidden)\n    @State private var showMySQLConfigSheet = false  // MySQL configuration file sheet\n    @State private var showMySQLPerformanceSheet = false  // MySQL performance optimization sheet\n\n    // Floating tip related\n    @State private var showPasswordTip = false  // Show password validation failure tip\n    @State private var passwordTipMessage = ";
"\n        return URL(string: urlString)\n    }\n\n    // MARK: - Nginx 配置管理\n\n    /// phpMyAdmin 安装目录\n    private var phpMyAdminPath: String {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        // 优先检查 Homebrew 安装路径\n        let brewPaths = [\n            " = "\n        return URL(string: urlString)\n    }\n\n    // MARK: - Nginx Configuration Management\n\n    /// phpMyAdmin installation directory\n    private var phpMyAdminPath: String {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        // Prioritize checking Homebrew installation path\n        let brewPaths = [\n            ";
"\n        return String((0..<length).map { _ in characters.randomElement()! })\n    }\n    \n    // 将域名转换为用户名（将点替换为下划线）\n    private func domainToUsername(_ domain: String) -> String {\n        // 去掉端口号\n        let cleanDomain = domain.split(separator: " = "\n        return String((0..<length).map { _ in characters.randomElement()! })\n    }\n    \n    // Convert domain name to username (replace dots with underscores)\n    private func domainToUsername(_ domain: String) -> String {\n        // Remove port number\n        let cleanDomain = domain.split(separator: ";
")\n                        .font(.headline)\n                        .padding(.horizontal)\n                    TextEditor(text: .constant(converted))\n                        .font(.system(.body, design: .monospaced))\n                        .macTextEditor()\n                }\n                .frame(maxWidth: .infinity)\n            }\n            .padding()\n        }\n    }\n\n    private var v2Body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            HStack {\n                Text(" = ")\n                        .font(.headline)\n                        .padding(.horizontal)\n                    TextEditor(text: .constant(converted))\n                        .font(.system(.body, design: .monospaced))\n                        .macTextEditor()\n                }\n                .frame(maxWidth: .infinity)\n            }\n            .padding()\n        }\n    }\n\n    private var v2Body: some View {\n        VStack(spacing: 0) {\n            // Title bar\n            HStack {\n                Text(";
"有备份(\\(database.backupCount))" = "Has backup(\\(database.backupCount))";
"✅ 已恢复配置: \\(backup.originalPath)" = "✅ Configuration restored: \\(backup.originalPath)";
") {\n            NSWorkspace.shared.open(url)\n        }\n    }\n\n    // 连接信息头部\n    private var connectionHeader: some View {\n        HStack(spacing: 16) {\n            // 连接图标\n            Image(systemName: tab.isLocal ? " = ") {\n            NSWorkspace.shared.open(url)\n        }\n    }\n\n    // Connection info header\n    private var connectionHeader: some View {\n        HStack(spacing: 16) {\n            // Connection icon\n            Image(systemName: tab.isLocal ? ";
"✅ Nginx 配置文件已生成: \\(configFilePath)" = "✅ Nginx configuration file generated: \\(configFilePath)";
")\n        let canWrite = fileManager.createFile(atPath: testFile, contents: nil, attributes: nil)\n        if canWrite {\n            // 删除测试文件\n            try? fileManager.removeItem(atPath: testFile)\n            return (false, true, nil, normalizedPath)\n        } else {\n            // 如果创建测试文件失败，再检查 isWritableFile\n            if fileManager.isWritableFile(atPath: parentPath) {\n                return (false, true, nil, normalizedPath)\n            } else {\n                return (false, false, " = ")\n        let canWrite = fileManager.createFile(atPath: testFile, contents: nil, attributes: nil)\n        if canWrite {\n            // Delete test file\n            try? fileManager.removeItem(atPath: testFile)\n            return (false, true, nil, normalizedPath)\n        } else {\n            // If test file creation fails, check isWritableFile instead\n            if fileManager.isWritableFile(atPath: parentPath) {\n                return (false, true, nil, normalizedPath)\n            } else {\n                return (false, false, ";
")\n                        }\n                        continuation.resume(returning: (nil, errorMsg))\n                    }\n                } catch {\n                    continuation.resume(returning: (nil, error.localizedDescription))\n                }\n            }\n        }\n    }\n}\n\n// MARK: - 连接数据库行视图\nstruct ConnectionDatabaseRow: View {\n    let name: String\n    let host: String\n    let port: Int\n    let isLocal: Bool\n    let password: String  // 连接密码\n\n    @State private var isHovered = false\n    @State private var isSelected = false\n    @State private var showPassword = false\n    @State private var dbUsername: String = " = ")\n                        }\n                        continuation.resume(returning: (nil, errorMsg))\n                    }\n                } catch {\n                    continuation.resume(returning: (nil, error.localizedDescription))\n                }\n            }\n        }\n    }\n}\n\n// MARK: - Connection Database Row View\nstruct ConnectionDatabaseRow: View {\n    let name: String\n    let host: String\n    let port: Int\n    let isLocal: Bool\n    let password: String  // Connection password\n\n    @State private var isHovered = false\n    @State private var isSelected = false\n    @State private var showPassword = false\n    @State private var dbUsername: String = ";
", text: $domains)\n                        .macInput()\n                }\n\n                // 有效期\n                FormField(" = ", text: $domains)\n                        .macInput()\n                }\n\n                // Validity period\n                FormField(";
")\n    ]\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 标题栏\n            headerBar\n\n            Divider()\n\n            // 表单内容\n            formContent\n\n            Divider()\n\n            // 底部按钮\n            bottomButtons\n        }\n        .frame(width: 500, height: 420)\n        .onAppear {\n            generateRandomPassword()\n        }\n    }\n\n    // 标题栏\n    private var headerBar: some View {\n        HStack {\n            Text(" = ")\n    ]\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Title bar\n            headerBar\n\n            Divider()\n\n            // Form content\n            formContent\n\n            Divider()\n\n            // Bottom buttons\n            bottomButtons\n        }\n        .frame(width: 500, height: 420)\n        .onAppear {\n            generateRandomPassword()\n        }\n    }\n\n    // Title bar\n    private var headerBar: some View {\n        HStack {\n            Text(";
")\n        let majorMinor = extractMajorMinorVersion(version)\n\n        return versions.contains { supportedVer in\n            majorMinor.hasPrefix(supportedVer) || supportedVer == majorMinor\n        }\n    }\n\n    /// 提取主次版本号\n    private func extractMajorMinorVersion(_ version: String) -> String {\n        let cleanVersion = version.split(separator: " = ")\n        let majorMinor = extractMajorMinorVersion(version)\n\n        return versions.contains { supportedVer in\n            majorMinor.hasPrefix(supportedVer) || supportedVer == majorMinor\n        }\n    }\n\n    /// 提取主次版本号\n    private func extractMajorMinorVersion(_ version: String) -> String {\n        let cleanVersion = version.split(separator: ";
")\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 120, alignment: .leading)\n\n            // 密码\n            Text(" = ")\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 120, alignment: .leading)\n\n            // 密码\n            Text(";
" }\n\n    /// Apache 主配置文件\n    var apacheConfigFile: String { " = " }\n\n    /// Apache 主配置文件\n    var apacheConfigFile: String { ";
", Color(red: 0.3, green: 0.56, blue: 0.31)) // MongoDB绿色\n        } else if name.contains(" = ", Color(red: 0.3, green: 0.56, blue: 0.31)) // MongoDB绿色\n        } else if name.contains(";
"\n                    self.isLoading = false\n                }\n            }\n        }\n    }\n\n    // 异步加载 hosts 文件（用于需要等待加载完成的场景）\n    func loadHostsAsync() async throws {\n        isLoading = true\n        errorMessage = nil\n\n        let content = try String(contentsOfFile: hostsPath, encoding: .utf8)\n        let entries = parseHostsContent(content)\n\n        self.entries = entries\n        self.isLoading = false\n    }\n\n    // 解析 hosts 文件内容\n    private nonisolated func parseHostsContent(_ content: String) -> [HostEntry] {\n        var entries: [HostEntry] = []\n        var inManagedSection = false\n        \n        for line in content.components(separatedBy: .newlines) {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            \n            // 检测管理区域标记\n            if trimmed == markerStart {\n                inManagedSection = true\n                continue\n            }\n            if trimmed == markerEnd {\n                inManagedSection = false\n                continue\n            }\n            \n            // 跳过空行和注释（非管理区域的注释）\n            if trimmed.isEmpty || (trimmed.hasPrefix(" = "\n                    self.isLoading = false\n                }\n            }\n        }\n    }\n\n    // 异步加载 hosts 文件（用于需要等待加载完成的场景）\n    func loadHostsAsync() async throws {\n        isLoading = true\n        errorMessage = nil\n\n        let content = try String(contentsOfFile: hostsPath, encoding: .utf8)\n        let entries = parseHostsContent(content)\n\n        self.entries = entries\n        self.isLoading = false\n    }\n\n    // 解析 hosts 文件内容\n    private nonisolated func parseHostsContent(_ content: String) -> [HostEntry] {\n        var entries: [HostEntry] = []\n        var inManagedSection = false\n        \n        for line in content.components(separatedBy: .newlines) {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            \n            // 检测管理区域标记\n            if trimmed == markerStart {\n                inManagedSection = true\n                continue\n            }\n            if trimmed == markerEnd {\n                inManagedSection = false\n                continue\n            }\n            \n            // 跳过空行和注释（非管理区域的注释）\n            if trimmed.isEmpty || (trimmed.hasPrefix(";
" }\n\n    /// 网站根目录\n    var wwwPath: String { " = " }\n\n    /// 网站根目录\n    var wwwPath: String { ";
")\n                        .foregroundColor(.orange)\n                        .font(.system(size: 13))\n                }\n                Text(LocalizedStringKey(item.description))\n                    .font(.system(size: 14))\n                    .foregroundColor(item.isUserDefined ? .orange : (isEnabled ? .primary : .secondary))\n                    .lineLimit(2)\n            }\n            .frame(width: 140, alignment: .leading)\n            .padding(.leading, 8)\n\n            // 当前值输入框\n            HStack(spacing: 4) {\n                TextField(" = ")\n                        .foregroundColor(.orange)\n                        .font(.system(size: 13))\n                }\n                Text(LocalizedStringKey(item.description))\n                    .font(.system(size: 14))\n                    .foregroundColor(item.isUserDefined ? .orange : (isEnabled ? .primary : .secondary))\n                    .lineLimit(2)\n            }\n            .frame(width: 140, alignment: .leading)\n            .padding(.leading, 8)\n\n            // 当前值输入框\n            HStack(spacing: 4) {\n                TextField(";
"📁 扫描到 \\(certFiles.count) 个证书文件" = "📁 扫描到 \\(certFiles.count) 个证书文件";
"\n\n            if !fm.fileExists(atPath: wsFile) && fm.fileExists(atPath: brewFile) {\n                try fm.copyItem(atPath: brewFile, toPath: wsFile)\n            }\n        }\n\n        // 3. 将 Homebrew 的原始配置文件重命名为 .bak，然后创建软链接\n        let brewNginxConfBak = " = "\n\n            if !fm.fileExists(atPath: wsFile) && fm.fileExists(atPath: brewFile) {\n                try fm.copyItem(atPath: brewFile, toPath: wsFile)\n            }\n        }\n\n        // 3. 将 Homebrew 的原始配置文件重命名为 .bak，然后创建软链接\n        let brewNginxConfBak = ";
") {\n                        // 格式: 127.0.0.1:9000\n                        if let port = value.split(separator: " = ") {\n                        // 格式: 127.0.0.1:9000\n                        if let port = value.split(separator: ";
"复制错误页面目录失败: \\(error.localizedDescription)" = "复制错误页面目录失败: \\(error.localizedDescription)";
"Let's Encrypt 证书已成功申请并配置。\\n\\n域名: \\(site.primaryDomain)\\n有效期: 90 天" = "Let's Encrypt 证书已成功申请并配置。\\n\\n域名: \\(site.primaryDomain)\\n有效期: 90 天";
"确定要删除证书 \\(cert.primaryDomain) 吗？\\n\\n此操作不可恢复。" = "确定要删除证书 \\(cert.primaryDomain) 吗？\\n\\n此操作不可恢复。";
" : appSettings.defaultSiteRoot\n        \n        // 检查目录是否存在\n        if !FileManager.default.fileExists(atPath: rootPath) {\n            // 如果目录不存在，尝试创建\n            do {\n                try FileManager.default.createDirectory(atPath: rootPath, withIntermediateDirectories: true, attributes: nil)\n            } catch {\n                // 创建失败，显示错误提示\n                let alert = NSAlert()\n                alert.messageText = " = " : appSettings.defaultSiteRoot\n        \n        // 检查目录是否存在\n        if !FileManager.default.fileExists(atPath: rootPath) {\n            // 如果目录不存在，尝试创建\n            do {\n                try FileManager.default.createDirectory(atPath: rootPath, withIntermediateDirectories: true, attributes: nil)\n            } catch {\n                // 创建失败，显示错误提示\n                let alert = NSAlert()\n                alert.messageText = ";
"]\n        }\n        panel.allowsOtherFileTypes = true\n        panel.allowsMultipleSelection = false\n        panel.canChooseDirectories = false\n        panel.canChooseFiles = true\n\n        // 设置默认目录为当前日志目录\n        let defaultPath = getLogDirectory()\n        if !defaultPath.isEmpty {\n            panel.directoryURL = URL(fileURLWithPath: defaultPath)\n        }\n\n        if panel.runModal() == .OK {\n            customLogPath = panel.url?.path\n        }\n    }\n\n    private func openLogDirectory() {\n        // 打开日志目录\n        let logPath = getLogDirectory()\n        NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: logPath)\n    }\n\n    private func getLogDirectory() -> String {\n        // 根据Web服务器类型返回日志目录\n        let workspaceManager = WorkspaceManager.shared\n        guard let config = workspaceManager.config else {\n            return " = "]\n        }\n        panel.allowsOtherFileTypes = true\n        panel.allowsMultipleSelection = false\n        panel.canChooseDirectories = false\n        panel.canChooseFiles = true\n\n        // 设置默认目录为当前日志目录\n        let defaultPath = getLogDirectory()\n        if !defaultPath.isEmpty {\n            panel.directoryURL = URL(fileURLWithPath: defaultPath)\n        }\n\n        if panel.runModal() == .OK {\n            customLogPath = panel.url?.path\n        }\n    }\n\n    private func openLogDirectory() {\n        // 打开日志目录\n        let logPath = getLogDirectory()\n        NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: logPath)\n    }\n\n    private func getLogDirectory() -> String {\n        // 根据Web服务器类型返回日志目录\n        let workspaceManager = WorkspaceManager.shared\n        guard let config = workspaceManager.config else {\n            return ";
"从配置文件导入: \\(parsed.fileName)" = "从配置文件导入: \\(parsed.fileName)";
"端口 \\(info.port)" = "端口 \\(info.port)";
"\n        }\n        return version\n    }\n}\n\n// MARK: - PHP 扩展类型\nenum PHPExtensionCategory: String, CaseIterable {\n    case decoder = " = "\n        }\n        return version\n    }\n}\n\n// MARK: - PHP 扩展类型\nenum PHPExtensionCategory: String, CaseIterable {\n    case decoder = ";
")\n                }\n\n                // 添加该节的配置项\n                for item in sectionItems {\n                    lines.append(generateConfigLine(item: item, commentPrefix: commentPrefix))\n                }\n\n                // 节之间添加空行\n                lines.append(" = ")\n                }\n\n                // 添加该节的配置项\n                for item in sectionItems {\n                    lines.append(generateConfigLine(item: item, commentPrefix: commentPrefix))\n                }\n\n                // 节之间添加空行\n                lines.append(";
"\n    }\n\n    /// 转换为符号字符串（如 " = "\n    }\n\n    /// 转换为符号字符串（如 ";
"版本: \\(service.version)" = "Version: \\(service.version)";
")\n                .font(.system(size: 16, weight: .bold))\n                .foregroundColor(.primary)\n                .padding(20)\n\n            ScrollView(showsIndicators: false) {\n                VStack(alignment: .leading, spacing: 6) {\n                    menuItems(isV2: true)\n                }\n                .padding(.horizontal, 16)\n            }\n        }\n        .background(Color.black.opacity(0.05))\n    }\n    \n    // 提取的菜单项\n    @ViewBuilder\n    private func menuItems(isV2: Bool) -> some View {\n        Group {\n            if isV2 {\n                MenuButtonV2(title: " = ")\n                .font(.system(size: 16, weight: .bold))\n                .foregroundColor(.primary)\n                .padding(20)\n\n            ScrollView(showsIndicators: false) {\n                VStack(alignment: .leading, spacing: 6) {\n                    menuItems(isV2: true)\n                }\n                .padding(.horizontal, 16)\n            }\n        }\n        .background(Color.black.opacity(0.05))\n    }\n    \n    // Extracted menu items\n    @ViewBuilder\n    private func menuItems(isV2: Bool) -> some View {\n        Group {\n            if isV2 {\n                MenuButtonV2(title: ";
")\n\n        // 获取证书路径（如果启用了SSL）\n        var certPath = " = ")\n\n        // Get certificate path (if SSL is enabled)\n        var certPath = ";
", command]\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // MARK: - 执行命令并跟踪进度\n\n    private func executeCommandWithProgress(_ command: String, extensionName: String, phpVersion: String) async -> Bool {\n        if command.hasPrefix(" = ", command]\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // MARK: - Execute command with progress tracking\n\n    private func executeCommandWithProgress(_ command: String, extensionName: String, phpVersion: String) async -> Bool {\n        if command.hasPrefix(";
"\n\n        let lines = apacheRules.components(separatedBy: .newlines)\n        var rewriteConditions: [String] = []\n        var inRewriteBlock = false\n\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n\n            // 跳过注释和空行\n            if trimmed.isEmpty || trimmed.hasPrefix(" = "\n\n        let lines = apacheRules.components(separatedBy: .newlines)\n        var rewriteConditions: [String] = []\n        var inRewriteBlock = false\n\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n\n            // Skip comments and blank lines\n            if trimmed.isEmpty || trimmed.hasPrefix(";
"创建父目录失败: \\(error.localizedDescription)\\n请检查路径权限或修改网站目录" = "Failed to create parent directory: \\(error.localizedDescription)\\nPlease check path permissions or modify the site directory.";
")\n            if fileManager.fileExists(atPath: bundleErrorPageDir) {\n                errorPageSourceDir = bundleErrorPageDir\n            }\n        }\n        \n        if errorPageSourceDir.isEmpty {\n            // 回退到基于文档目录的推断或默认工作空间\n            if let workspaceRoot = WorkspaceManager.shared.config?.rootPath {\n                errorPageSourceDir = (workspaceRoot as NSString).appendingPathComponent(" = ")\n            if fileManager.fileExists(atPath: bundleErrorPageDir) {\n                errorPageSourceDir = bundleErrorPageDir\n            }\n        }\n        \n        if errorPageSourceDir.isEmpty {\n            // Fallback to document-directory-based inference or default workspace\n            if let workspaceRoot = WorkspaceManager.shared.config?.rootPath {\n                errorPageSourceDir = (workspaceRoot as NSString).appendingPathComponent(";
" : appSettings.defaultSiteRoot\n        panel.directoryURL = URL(fileURLWithPath: defaultPath)\n\n        if panel.runModal() == .OK {\n            if let url = panel.url {\n                documentRoot = url.path\n            }\n        }\n    }\n\n    // 提交表单\n    private func submitForm() {\n        // 检查服务状态（Web服务器和PHP）\n        var stopped: [String] = []\n\n        // 检查Web服务器状态\n        if !isWebServerRunning(selectedWebServer) {\n            stopped.append(selectedWebServer.rawValue)\n        }\n\n        // 检查PHP版本状态\n        if selectedPHPVersion != " = " : appSettings.defaultSiteRoot\n        panel.directoryURL = URL(fileURLWithPath: defaultPath)\n\n        if panel.runModal() == .OK {\n            if let url = panel.url {\n                documentRoot = url.path\n            }\n        }\n    }\n\n    // Submit form\n    private func submitForm() {\n        // Check service status (web server and PHP)\n        var stopped: [String] = []\n\n        // Check web server status\n        if !isWebServerRunning(selectedWebServer) {\n            stopped.append(selectedWebServer.rawValue)\n        }\n\n        // Check PHP version status\n        if selectedPHPVersion != ";
"\n                ]\n\n                let outputPipe = Pipe()\n                let errorPipe = Pipe()\n                process.standardOutput = outputPipe\n                process.standardError = errorPipe\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    if process.terminationStatus == 0 {\n                        let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()\n                        if let output = String(data: outputData, encoding: .utf8) {\n                            let databases = output\n                                .components(separatedBy: .newlines)\n                                .map { $0.trimmingCharacters(in: .whitespaces) }\n                                .filter { !$0.isEmpty }\n                            continuation.resume(returning: databases)\n                            return\n                        }\n                    }\n                    continuation.resume(returning: [])\n                } catch {\n                    continuation.resume(returning: [])\n                }\n            }\n        }\n    }\n\n    // 在 MySQL 中创建数据库和用户\n    private func createMySQLDatabase(name: String, username: String, password: String) async -> Bool {\n        return await runMySQLCommand(" = "\n                ]\n\n                let outputPipe = Pipe()\n                let errorPipe = Pipe()\n                process.standardOutput = outputPipe\n                process.standardError = errorPipe\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    if process.terminationStatus == 0 {\n                        let outputData = outputPipe.fileHandleForReading.readDataToEndOfFile()\n                        if let output = String(data: outputData, encoding: .utf8) {\n                            let databases = output\n                                .components(separatedBy: .newlines)\n                                .map { $0.trimmingCharacters(in: .whitespaces) }\n                                .filter { !$0.isEmpty }\n                            continuation.resume(returning: databases)\n                            return\n                        }\n                    }\n                    continuation.resume(returning: [])\n                } catch {\n                    continuation.resume(returning: [])\n                }\n            }\n        }\n    }\n\n    // Create database and user in MySQL\n    private func createMySQLDatabase(name: String, username: String, password: String) async -> Bool {\n        return await runMySQLCommand(";
"目录不存在且无法创建: \\(rootPath)\\n\\n请先在设置中配置默认站点主目录。" = "Directory does not exist and cannot be created: \\(rootPath)\\n\\nPlease configure the default site root directory in Settings first.";
"]\n        brewPath = paths.first { FileManager.default.fileExists(atPath: $0) }\n    }\n\n    /// 启动监控\n    func startMonitoring() {\n        shouldStopMonitoring = false\n        monitoringTask?.cancel()\n\n        monitoringTask = Task {\n            // 立即执行一次\n            await refresh()\n\n            // 循环监控：完成后等待指定时间再执行下一次\n            while !shouldStopMonitoring && !Task.isCancelled {\n                try? await Task.sleep(nanoseconds: UInt64(pollingInterval * 1_000_000_000))\n                if !shouldStopMonitoring && !Task.isCancelled {\n                    await refresh()\n                }\n            }\n        }\n    }\n\n    /// 停止监控\n    func stopMonitoring() {\n        shouldStopMonitoring = true\n        monitoringTask?.cancel()\n        monitoringTask = nil\n    }\n\n    /// 手动刷新\n    func refresh() async {\n        guard !isRefreshing else { return }\n        isRefreshing = true\n\n        let (installed, running) = await fetchStatus()\n\n        installedPackages = installed\n        runningServices = running\n        isLoading = false\n        lastUpdated = Date()\n\n        // 同步更新 AppState 的静态缓存\n        AppState.cachedInstalledPackages = installed\n\n        isRefreshing = false\n    }\n\n    /// 检查指定服务是否正在运行\n    func isRunning(_ brewName: String) -> Bool {\n        runningServices.contains(brewName)\n    }\n\n    /// 检查指定包是否已安装\n    func isInstalled(_ brewName: String) -> Bool {\n        installedPackages[brewName] != nil\n    }\n\n    /// 获取指定包的版本\n    func version(of brewName: String) -> String? {\n        installedPackages[brewName]\n    }\n\n    // MARK: - Private Methods\n\n    /// 获取已安装包和运行中服务的状态\n    private nonisolated func fetchStatus() async -> ([String: String], Set<String>) {\n        guard let brewPath = brewPath else {\n            return ([:], [])\n        }\n\n        async let installedTask = fetchInstalledPackages(brewPath: brewPath)\n        async let runningTask = fetchRunningServices(brewPath: brewPath)\n\n        let installed = await installedTask\n        let running = await runningTask\n\n        return (installed, running)\n    }\n\n    /// 获取已安装的包列表\n    private nonisolated func fetchInstalledPackages(brewPath: String) async -> [String: String] {\n        await withCheckedContinuation { continuation in\n            // 在后台线程执行整个操作，避免阻塞\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: brewPath)\n                process.arguments = [" = "]\n        brewPath = paths.first { FileManager.default.fileExists(atPath: $0) }\n    }\n\n    /// Start monitoring\n    func startMonitoring() {\n        shouldStopMonitoring = false\n        monitoringTask?.cancel()\n\n        monitoringTask = Task {\n            // Execute once immediately\n            await refresh()\n\n            // Loop monitoring: wait specified time after completion before next execution\n            while !shouldStopMonitoring && !Task.isCancelled {\n                try? await Task.sleep(nanoseconds: UInt64(pollingInterval * 1_000_000_000))\n                if !shouldStopMonitoring && !Task.isCancelled {\n                    await refresh()\n                }\n            }\n        }\n    }\n\n    /// Stop monitoring\n    func stopMonitoring() {\n        shouldStopMonitoring = true\n        monitoringTask?.cancel()\n        monitoringTask = nil\n    }\n\n    /// Manual refresh\n    func refresh() async {\n        guard !isRefreshing else { return }\n        isRefreshing = true\n\n        let (installed, running) = await fetchStatus()\n\n        installedPackages = installed\n        runningServices = running\n        isLoading = false\n        lastUpdated = Date()\n\n        // Synchronize update of AppState's static cache\n        AppState.cachedInstalledPackages = installed\n\n        isRefreshing = false\n    }\n\n    /// Check whether a specified service is running\n    func isRunning(_ brewName: String) -> Bool {\n        runningServices.contains(brewName)\n    }\n\n    /// Check whether a specified package is installed\n    func isInstalled(_ brewName: String) -> Bool {\n        installedPackages[brewName] != nil\n    }\n\n    /// Get version of a specified package\n    func version(of brewName: String) -> String? {\n        installedPackages[brewName]\n    }\n\n    // MARK: - Private Methods\n\n    /// Fetch status of installed packages and running services\n    private nonisolated func fetchStatus() async -> ([String: String], Set<String>) {\n        guard let brewPath = brewPath else {\n            return ([:], [])\n        }\n\n        async let installedTask = fetchInstalledPackages(brewPath: brewPath)\n        async let runningTask = fetchRunningServices(brewPath: brewPath)\n\n        let installed = await installedTask\n        let running = await runningTask\n\n        return (installed, running)\n    }\n\n    /// Fetch list of installed packages\n    private nonisolated func fetchInstalledPackages(brewPath: String) async -> [String: String] {\n        await withCheckedContinuation { continuation in\n            // Perform entire operation on background thread to avoid blocking\n            DispatchQueue.global(qos: .userInitiated).async {\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: brewPath)\n                process.arguments = [";
")\n                }\n            }\n\n            // 默认站点配置\n            defaultSiteSection\n\n            Section(" = ")\n                }\n            }\n\n            // Default site configuration\n            defaultSiteSection\n\n            Section(";
"\n                }\n\n                // 使用较低的优先级（110+）确保排在预定义版本和PHP之后\n                additionalVersions.append((displayName, packageName, 3306, 110 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// 动态检测所有已安装的 MariaDB 版本（不在预定义列表中的）\n    private nonisolated func detectAdditionalMariaDBVersions(\n        predefinedBrewNames: Set<String>\n    ) -> [(name: String, brewName: String, port: Int, priority: Int)] {\n        guard let packages = AppState.cachedInstalledPackages else { return [] }\n\n        var additionalVersions: [(name: String, brewName: String, port: Int, priority: Int)] = []\n\n        for (packageName, _) in packages {\n            // 匹配 mariadb 或 mariadb@x.x 格式\n            if packageName == " = "\n                }\n\n                // Use lower priority (110+) to ensure ordering after predefined versions and PHP\n                additionalVersions.append((displayName, packageName, 3306, 110 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// Dynamically detect all installed MariaDB versions (not in predefined list)\n    private nonisolated func detectAdditionalMariaDBVersions(\n        predefinedBrewNames: Set<String>\n    ) -> [(name: String, brewName: String, port: Int, priority: Int)] {\n        guard let packages = AppState.cachedInstalledPackages else { return [] }\n\n        var additionalVersions: [(name: String, brewName: String, port: Int, priority: Int)] = []\n\n        for (packageName, _) in packages {\n            // Match 'mariadb' or 'mariadb@x.x' format\n            if packageName == ";
")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding(20)\n            .frame(height: 60)\n            \n            Divider()\n                .background(Color.gray.opacity(0.2))\n            \n            // 主体内容\n            HStack(spacing: 0) {\n                // 左侧导航栏\n                v2LeftMenu\n                    .frame(width: 200)\n                \n                // 右侧内容区域\n                ScrollView(showsIndicators: false) {\n                    configContent\n                        .padding(32)\n                }\n                .frame(maxWidth: .infinity, maxHeight: .infinity)\n                .background(Color.white.opacity(0.1))\n            }\n        }\n        .glass(.panel)\n        .overlay(\n            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                .stroke(Color.white.opacity(0.2), lineWidth: 1)\n        )\n    }\n    \n    private func saveConfig() {\n        Task {\n            do {\n                try await siteManager.updateSiteAdvancedConfig(site: site, config: advancedConfig)\n            } catch {\n                print(" = ")\n                        .font(.title3)\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding(20)\n            .frame(height: 60)\n            \n            Divider()\n                .background(Color.gray.opacity(0.2))\n            \n            // Main content area\n            HStack(spacing: 0) {\n                // Left navigation sidebar\n                v2LeftMenu\n                    .frame(width: 200)\n                \n                // Right content area\n                ScrollView(showsIndicators: false) {\n                    configContent\n                        .padding(32)\n                }\n                .frame(maxWidth: .infinity, maxHeight: .infinity)\n                .background(Color.white.opacity(0.1))\n            }\n        }\n        .glass(.panel)\n        .overlay(\n            RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                .stroke(Color.white.opacity(0.2), lineWidth: 1)\n        )\n    }\n    \n    private func saveConfig() {\n        Task {\n            do {\n                try await siteManager.updateSiteAdvancedConfig(site: site, config: advancedConfig)\n            } catch {\n                print(";
")!)\n                        }\n                        .buttonStyle(.bordered)\n                    }\n                }\n            }\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .padding(40)\n    }\n\n    // MARK: - 报告视图\n    private func reportView(report: GoAccessReport) -> some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // 顶部操作栏\n                HStack {\n                    if BuildInfo.uiVersion == " = ")!)\n                        }\n                        .buttonStyle(.bordered)\n                    }\n                }\n            }\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .padding(40)\n    }\n\n    // MARK: - Report view\n    private func reportView(report: GoAccessReport) -> some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // Top action bar\n                HStack {\n                    if BuildInfo.uiVersion == ";
"\n\n        // 确保目录存在\n        let fileManager = FileManager.default\n        try fileManager.createDirectory(atPath: nginxVhostsDir, withIntermediateDirectories: true)\n\n        let serverNames = domains.joined(separator: " = "\n\n        // Ensure directory exists\n        let fileManager = FileManager.default\n        try fileManager.createDirectory(atPath: nginxVhostsDir, withIntermediateDirectories: true)\n\n        let serverNames = domains.joined(separator: ";
" : site.note)\n            \n            // Web服务器\n            HStack(spacing: 2) {\n                Text(site.webServer.rawValue.capitalized)\n                    .font(.system(size: 12))\n                    .foregroundColor(.blue)\n                Image(systemName: showWebServerDropdown ? " = " : site.note)\n            \n            // Web server\n            HStack(spacing: 2) {\n                Text(site.webServer.rawValue.capitalized)\n                    .font(.system(size: 12))\n                    .foregroundColor(.blue)\n                Image(systemName: showWebServerDropdown ? ";
")\n    }\n\n    // 从 MySQL 中删除数据库和用户\n    private func deleteMySQLDatabase(name: String, username: String) async -> Bool {\n        return await runMySQLCommand(" = ")\n    }\n\n    // Delete database and user from MySQL\n    private func deleteMySQLDatabase(name: String, username: String) async -> Bool {\n        return await runMySQLCommand(";
")\n                }\n            }\n\n            // 保存更新后的证书列表\n            saveCertificates()\n\n        } catch {\n            scanStatus = String(format: NSLocalizedString(" = ")\n                }\n            }\n\n            // Save updated certificate list\n            saveCertificates()\n\n        } catch {\n            scanStatus = String(format: NSLocalizedString(";
"\\(serviceName) 日志" = "\\(serviceName) Logs";
"]),\n        // MySQL 9.x 认证策略\n        " = "]),\n        // MySQL 9.x authentication policy\n        ";
"))\n                                    }\n                                }\n                            }\n                        } label: {\n                            HStack(spacing: 6) {\n                                // 状态圆点（仅PHP版本显示）\n                                if selectedPHPVersion != " = "))\n                                    }\n                                }\n                            }\n                        } label: {\n                            HStack(spacing: 6) {\n                                // Status dot (shown only for PHP versions)\n                                if selectedPHPVersion != ";
", Color(red: 0.4, green: 0.4, blue: 0.4)) // Memcached灰色\n        } else if name.contains(" = ", Color(red: 0.4, green: 0.4, blue: 0.4)) // Memcached gray\n        } else if name.contains(";
"是" = "Yes";
"\n    @State private var enableSSL = false\n    @State private var updateHosts = false  // 添加本地解析选项\n    @State private var createFTP = false  // FTP选项\n    @State private var createDatabase = false  // 数据库选项\n    @State private var expiryDate = " = "\n    @State private var enableSSL = false\n    @State private var updateHosts = false  // Add local resolution option\n    @State private var createFTP = false  // FTP option\n    @State private var createDatabase = false  // Database option\n    @State private var expiryDate = ";
")\n        }\n\n        // 如果有服务已停止，显示警告\n        if !stopped.isEmpty {\n            stoppedServices = stopped\n            showServiceStoppedWarning = true\n            return\n        }\n\n        // 执行实际的提交逻辑\n        performSubmit()\n    }\n\n    private func performSubmit() {\n        // 准备路径\n        let siteDir = siteDirectory.hasSuffix(" = ")\n        }\n\n        // If any services are stopped, display a warning\n        if !stopped.isEmpty {\n            stoppedServices = stopped\n            showServiceStoppedWarning = true\n            return\n        }\n\n        // Execute the actual submission logic\n        performSubmit()\n    }\n\n    private func performSubmit() {\n        // Prepare path\n        let siteDir = siteDirectory.hasSuffix(";
"\n\n        // 确保日志目录存在\n        do {\n            try FileManager.default.createDirectory(atPath: logDir, withIntermediateDirectories: true, attributes: nil)\n        } catch {\n            NSLog(" = "\n\n        // Ensure log directory exists\n        do {\n            try FileManager.default.createDirectory(atPath: logDir, withIntermediateDirectories: true, attributes: nil)\n        } catch {\n            NSLog(";
"共 \\(totalLogCount) 条记录" = "Total \\(totalLogCount) records";
")\n    }\n\n    // 备份数据库\n    func backupDatabase(_ database: Database, to path: URL) async throws {\n        // 使用 mysqldump 命令备份\n        let success = await performMySQLDump(databaseName: database.name, to: path)\n        if !success {\n            throw DatabaseError.operationFailed(" = ")\n    }\n\n    // Backup database\n    func backupDatabase(_ database: Database, to path: URL) async throws {\n        // Use mysqldump command to back up\n        let success = await performMySQLDump(databaseName: database.name, to: path)\n        if !success {\n            throw DatabaseError.operationFailed(";
"安装 \\(softwareName) 版本" = "Installing \\(softwareName) version";
",\n                pid: pid,\n                processName: processName,\n                executablePath: details.executablePath,\n                commandLine: details.commandLine,\n                user: details.user,\n                parentPid: details.parentPid,\n                startTime: details.startTime,\n                workingDirectory: details.workingDirectory\n            )\n        } catch {\n            return nil\n        }\n    }\n\n    /// 显示端口详情\n    private func showPortDetails(_ port: PortItem) {\n        let alert = NSAlert()\n        alert.messageText = " = ",\n                pid: pid,\n                processName: processName,\n                executablePath: details.executablePath,\n                commandLine: details.commandLine,\n                user: details.user,\n                parentPid: details.parentPid,\n                startTime: details.startTime,\n                workingDirectory: details.workingDirectory\n            )\n        } catch {\n            return nil\n        }\n    }\n\n    /// Show port details\n    private func showPortDetails(_ port: PortItem) {\n        let alert = NSAlert()\n        alert.messageText = ";
", port: 21\n            ))\n        }\n\n        return versions\n    }\n\n    // MARK: - Tap 管理\n\n    /// 检查 tap 是否已添加\n    private func isTapInstalled(_ tap: String) async -> Bool {\n        guard let brewPath = brewPath else { return false }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [" = ", port: 21\n            ))\n        }\n\n        return versions\n    }\n\n    // MARK: - Tap Management\n\n    /// Check if tap is already installed\n    private func isTapInstalled(_ tap: String) async -> Bool {\n        guard let brewPath = brewPath else { return false }\n\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: brewPath)\n        process.arguments = [";
")\n                                    .foregroundColor(.secondary)\n                                    .font(.system(size: 14))\n                            }\n                            .buttonStyle(.plain)\n                            .padding(.leading, 8)\n                        }\n                        .padding(.horizontal, 10)\n                        .padding(.vertical, 6)\n                        .frame(minHeight: 32)\n                        .glass(.input)\n                    }\n                }\n\n                // 备注 & 测试连接\n                HStack(alignment: .bottom, spacing: 20) {\n                    FormField(" = ")\n                                    .foregroundColor(.secondary)\n                                    .font(.system(size: 14))\n                            }\n                            .buttonStyle(.plain)\n                            .padding(.leading, 8)\n                        }\n                        .padding(.horizontal, 10)\n                        .padding(.vertical, 6)\n                        .frame(minHeight: 32)\n                        .glass(.input)\n                    }\n                }\n\n                // Notes & Test Connection\n                HStack(alignment: .bottom, spacing: 20) {\n                    FormField(";
")\n        }\n    }\n\n    /// 处理认证方式切换\n    private func handleAuthSwitch(_ packet: Data) throws {\n        var offset = 1\n\n        // 新的认证插件名称\n        var newPluginName = " = ")\n        }\n    }\n\n    /// Handle authentication method switching\n    private func handleAuthSwitch(_ packet: Data) throws {\n        var offset = 1\n\n        // New authentication plugin name\n        var newPluginName = ";
")\n        }\n    }\n\n    // Apache 安装模式\n    @Published var apacheInstallMode: String {\n        didSet {\n            UserDefaults.standard.set(apacheInstallMode, forKey: " = ")\n        }\n    }\n\n    // Apache installation mode\n    @Published var apacheInstallMode: String {\n        didSet {\n            UserDefaults.standard.set(apacheInstallMode, forKey: ";
" }\n\n    /// Redis 日志目录\n    var redisLogsPath: String { " = " }\n\n    /// Redis log directory\n    var redisLogsPath: String { ";
"✅ Apache 配置文件已生成: \\(configFilePath)" = "✅ Apache configuration file generated: \\(configFilePath)";
", nil)\n    }\n\n    /// 加载 Redis 端口数据\n    private func loadRedisPortData() -> (port: String, error: String?) {\n        let lines = configContent.components(separatedBy: .newlines)\n        if let value = extractValue(for: " = ", nil)\n    }\n\n    /// Load Redis port data\n    private func loadRedisPortData() -> (port: String, error: String?) {\n        let lines = configContent.components(separatedBy: .newlines)\n        if let value = extractValue(for: ";
"\n            server {\n                listen 80;\n                server_name \\(serverNames);\n                root \\(publicRoot);\n                index index.php index.html index.htm;\n\n                # 访问日志\n                access_log \\(accessLog);\n                error_log \\(errorLog);\n\n                location / {\n                    try_files $uri $uri/ /index.php?$args;\n                }\n\n                location ~ \\\\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:\\(phpPort);\n                    fastcgi_index index.php;\n                    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                }\n\n                # 禁止访问隐藏文件\n                location ~ /\\\\. {\n                    deny all;\n                }\n            }\n            " = "\n            server {\n                listen 80;\n                server_name \\(serverNames);\n                root \\(publicRoot);\n                index index.php index.html index.htm;\n\n                # Access log\n                access_log \\(accessLog);\n                error_log \\(errorLog);\n\n                location / {\n                    try_files $uri $uri/ /index.php?$args;\n                }\n\n                location ~ \\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:\\(phpPort);\n                    fastcgi_index index.php;\n                    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;\n                }\n\n                # Block access to hidden files\n                location ~ /\\. {\n                    deny all;\n                }\n            }\n            ";
")\n                    }\n                }\n            }\n        }\n    }\n\n    // 执行导出\n    private func performExport(name: String, to url: URL) async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqldumpPath = MySQLClientPaths.findMysqldump() else {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqldumpPath)\n                process.arguments = [\n                    " = ")\n                    }\n                }\n            }\n        }\n    }\n\n    // Perform export\n    private func performExport(name: String, to url: URL) async -> Bool {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqldumpPath = MySQLClientPaths.findMysqldump() else {\n                    continuation.resume(returning: false)\n                    return\n                }\n\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: mysqldumpPath)\n                process.arguments = [\n                    ";
"\n    }\n\n    /// 重建软件列表\n    private func rebuildLists(installed: [String: String], running: Set<String>) {\n        let phpPorts = AppSettings.shared.phpPorts\n        let nginxPort = AppSettings.shared.nginxPort\n        let apachePort = AppSettings.shared.apachePort\n        phpVersions = buildPHPVersions(installed: installed, running: running, phpPorts: phpPorts)\n        webServers = buildWebServerVersions(installed: installed, running: running, nginxPort: nginxPort, apachePort: apachePort)\n        databases = buildDatabaseVersions(installed: installed, running: running)\n        caches = buildCacheVersions(installed: installed, running: running)\n        tools = buildToolVersions(installed: installed, running: running)\n    }\n\n    func refresh() {\n        loadSoftwareVersions()\n    }\n\n    // MARK: - 加载软件列表\n\n    func loadSoftwareVersions() {\n        // 1. 先立即显示固定的软件列表（使用缓存数据或空状态）\n        let cachedInstalled = AppState.cachedInstalledPackages ?? [:]\n        let cachedRunning = getCachedRunningServices()\n\n        // 立即构建并显示列表\n        let phpPorts = AppSettings.shared.phpPorts\n        let nginxPort = AppSettings.shared.nginxPort\n        let apachePort = AppSettings.shared.apachePort\n        phpVersions = buildPHPVersions(installed: cachedInstalled, running: cachedRunning, phpPorts: phpPorts)\n        webServers = buildWebServerVersions(installed: cachedInstalled, running: cachedRunning, nginxPort: nginxPort, apachePort: apachePort)\n        databases = buildDatabaseVersions(installed: cachedInstalled, running: cachedRunning)\n        caches = buildCacheVersions(installed: cachedInstalled, running: cachedRunning)\n        tools = buildToolVersions(installed: cachedInstalled, running: cachedRunning)\n\n        // 如果有缓存数据，不显示加载状态；否则显示加载中\n        let hasCache = !cachedInstalled.isEmpty\n        isLoading = !hasCache\n\n        // 2. 后台刷新实时数据\n        DispatchQueue.global(qos: .userInitiated).async { [weak self] in\n            guard let self = self else { return }\n\n            let installed = self.getInstalledPackages()\n            let running = self.getRunningServices()\n\n            let php = self.buildPHPVersions(installed: installed, running: running, phpPorts: phpPorts)\n            let web = self.buildWebServerVersions(installed: installed, running: running, nginxPort: nginxPort, apachePort: apachePort)\n            let db = self.buildDatabaseVersions(installed: installed, running: running)\n            let cache = self.buildCacheVersions(installed: installed, running: running)\n            let tool = self.buildToolVersions(installed: installed, running: running)\n\n            DispatchQueue.main.async {\n                self.phpVersions = php\n                self.webServers = web\n                self.databases = db\n                self.caches = cache\n                self.tools = tool\n                self.isLoading = false\n            }\n        }\n    }\n\n    /// 从 AppState 缓存获取运行中的服务\n    private nonisolated func getCachedRunningServices() -> Set<String> {\n        guard let output = AppState.cachedBrewServicesOutput else { return [] }\n\n        var running: Set<String> = []\n        for line in output.components(separatedBy: .newlines) {\n            if line.contains(" = "\n    }\n\n    /// 重建软件列表\n    private func rebuildLists(installed: [String: String], running: Set<String>) {\n        let phpPorts = AppSettings.shared.phpPorts\n        let nginxPort = AppSettings.shared.nginxPort\n        let apachePort = AppSettings.shared.apachePort\n        phpVersions = buildPHPVersions(installed: installed, running: running, phpPorts: phpPorts)\n        webServers = buildWebServerVersions(installed: installed, running: running, nginxPort: nginxPort, apachePort: apachePort)\n        databases = buildDatabaseVersions(installed: installed, running: running)\n        caches = buildCacheVersions(installed: installed, running: running)\n        tools = buildToolVersions(installed: installed, running: running)\n    }\n\n    func refresh() {\n        loadSoftwareVersions()\n    }\n\n    // MARK: - 加载软件列表\n\n    func loadSoftwareVersions() {\n        // 1. 先立即显示固定的软件列表（使用缓存数据或空状态）\n        let cachedInstalled = AppState.cachedInstalledPackages ?? [:]\n        let cachedRunning = getCachedRunningServices()\n\n        // 立即构建并显示列表\n        let phpPorts = AppSettings.shared.phpPorts\n        let nginxPort = AppSettings.shared.nginxPort\n        let apachePort = AppSettings.shared.apachePort\n        phpVersions = buildPHPVersions(installed: cachedInstalled, running: cachedRunning, phpPorts: phpPorts)\n        webServers = buildWebServerVersions(installed: cachedInstalled, running: cachedRunning, nginxPort: nginxPort, apachePort: apachePort)\n        databases = buildDatabaseVersions(installed: cachedInstalled, running: cachedRunning)\n        caches = buildCacheVersions(installed: cachedInstalled, running: cachedRunning)\n        tools = buildToolVersions(installed: cachedInstalled, running: cachedRunning)\n\n        // 如果有缓存数据，不显示加载状态；否则显示加载中\n        let hasCache = !cachedInstalled.isEmpty\n        isLoading = !hasCache\n\n        // 2. 后台刷新实时数据\n        DispatchQueue.global(qos: .userInitiated).async { [weak self] in\n            guard let self = self else { return }\n\n            let installed = self.getInstalledPackages()\n            let running = self.getRunningServices()\n\n            let php = self.buildPHPVersions(installed: installed, running: running, phpPorts: phpPorts)\n            let web = self.buildWebServerVersions(installed: installed, running: running, nginxPort: nginxPort, apachePort: apachePort)\n            let db = self.buildDatabaseVersions(installed: installed, running: running)\n            let cache = self.buildCacheVersions(installed: installed, running: running)\n            let tool = self.buildToolVersions(installed: installed, running: running)\n\n            DispatchQueue.main.async {\n                self.phpVersions = php\n                self.webServers = web\n                self.databases = db\n                self.caches = cache\n                self.tools = tool\n                self.isLoading = false\n            }\n        }\n    }\n\n    /// 从 AppState 缓存获取运行中的服务\n    private nonisolated func getCachedRunningServices() -> Set<String> {\n        guard let output = AppState.cachedBrewServicesOutput else { return [] }\n\n        var running: Set<String> = []\n        for line in output.components(separatedBy: .newlines) {\n            if line.contains(";
"\n        }\n    }\n\n    // 执行配置迁移\n    private func performMigration() {\n        guard let migration = pendingMigration else { return }\n\n        do {\n            try ConfigPathProvider.shared.migrateConfig(migration)\n            // 迁移成功，重新加载配置\n            pendingMigration = nil\n            addStatusMessage(" = "\n        }\n    }\n\n    // 执行配置迁移\n    private func performMigration() {\n        guard let migration = pendingMigration else { return }\n\n        do {\n            try ConfigPathProvider.shared.migrateConfig(migration)\n            // 迁移成功，重新加载配置\n            pendingMigration = nil\n            addStatusMessage(";
"\n        let result = await executeSQL(sql)\n\n        if result.success {\n            await fetchDatabases()\n        } else {\n            errorMessage = result.error\n        }\n\n        return result.success\n    }\n\n    // MARK: - 用户管理\n\n    /// 获取用户列表\n    func fetchUsers() async {\n        isLoading = true\n        let sql = " = "\n        let result = await executeSQL(sql)\n\n        if result.success {\n            await fetchDatabases()\n        } else {\n            errorMessage = result.error\n        }\n\n        return result.success\n    }\n\n    // MARK: - 用户管理\n\n    /// 获取用户列表\n    func fetchUsers() async {\n        isLoading = true\n        let sql = ";
"# \\(service.name) 配置文件" = "# \\(service.name) 配置文件";
"\n\n    // 每个软件的安装进度\n    @Published var installProgressMap: [String: InstallProgress] = [:]\n\n    // 正在安装的版本列表（按软件名称分组）\n    @Published var installingVersions: [String: InstallingVersion] = [:]\n\n    private let brewPath: String?\n    private var cancellables = Set<AnyCancellable>()\n\n    // Homebrew 包名映射\n    private let phpPackages = [" = "\n\n    // 每个软件的安装进度\n    @Published var installProgressMap: [String: InstallProgress] = [:]\n\n    // 正在安装的版本列表（按软件名称分组）\n    @Published var installingVersions: [String: InstallingVersion] = [:]\n\n    private let brewPath: String?\n    private var cancellables = Set<AnyCancellable>()\n\n    // Homebrew 包名映射\n    private let phpPackages = [";
") {\n                inLocationBlock = true\n                // 提取 location 路径\n                if let pathMatch = trimmed.range(of: #" = ") {\n                inLocationBlock = true\n                // 提取 location 路径\n                if let pathMatch = trimmed.range(of: #";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 表单内容\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // 域名\n                VStack(alignment: .leading, spacing: 8) {\n                    HStack(spacing: 4) {\n                        Text(LocalizedStringKey(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 表单内容\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // 域名\n                VStack(alignment: .leading, spacing: 8) {\n                    HStack(spacing: 4) {\n                        Text(LocalizedStringKey(";
" {\n                        domains.append(domain)\n                    }\n                }\n            }\n\n            // 解析 root\n            if trimmed.hasPrefix(" = " {\n                        domains.append(domain)\n                    }\n                }\n            }\n\n            // 解析 root\n            if trimmed.hasPrefix(";
"),\n                                   configuration: NSWorkspace.OpenConfiguration())\n        } else {\n            throw FileSystemError.notImplemented\n        }\n    }\n}\n\n// MARK: - 错误类型\n\nenum FileSystemError: LocalizedError {\n    case permissionDenied\n    case notImplemented\n    case operationFailed(String)\n\n    var errorDescription: String? {\n        switch self {\n        case .permissionDenied: return " = "),\n                                   configuration: NSWorkspace.OpenConfiguration())\n        } else {\n            throw FileSystemError.notImplemented\n        }\n    }\n}\n\n// MARK: - 错误类型\n\nenum FileSystemError: LocalizedError {\n    case permissionDenied\n    case notImplemented\n    case operationFailed(String)\n\n    var errorDescription: String? {\n        switch self {\n        case .permissionDenied: return ";
"\n            }\n        } else {\n            // 使用 rewrite 指令\n            // 转换 Apache 正则到 Nginx 正则\n            var nginxPattern = pattern\n            if !nginxPattern.hasPrefix(" = "\n            }\n        } else {\n            // 使用 rewrite 指令\n            // 转换 Apache 正则到 Nginx 正则\n            var nginxPattern = pattern\n            if !nginxPattern.hasPrefix(";
")\n            }\n        }\n\n        // 复制错误页面目录和默认首页\n        try await copyErrorPagesToSite(documentRoot: sitePublicRoot(documentRoot: documentRoot, publicDirectory: publicDirectory))\n\n        sites.append(newSite)\n        \n        // 立即保存（关键操作）\n        try await forceSaveSites()\n        \n        if let addedSite = sites.last, allRequiredServicesRunning(for: addedSite.webServer) {\n            try await startSite(addedSite)\n        }\n    }\n\n    // 删除站点到回收站\n    func deleteSite(_ site: Site) async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        guard let index = sites.firstIndex(where: { $0.id == site.id }) else {\n            throw SiteError.operationFailed(" = ")\n            }\n        }\n\n        // 复制错误页面目录和默认首页\n        try await copyErrorPagesToSite(documentRoot: sitePublicRoot(documentRoot: documentRoot, publicDirectory: publicDirectory))\n\n        sites.append(newSite)\n        \n        // 立即保存（关键操作）\n        try await forceSaveSites()\n        \n        if let addedSite = sites.last, allRequiredServicesRunning(for: addedSite.webServer) {\n            try await startSite(addedSite)\n        }\n    }\n\n    // 删除站点到回收站\n    func deleteSite(_ site: Site) async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        guard let index = sites.firstIndex(where: { $0.id == site.id }) else {\n            throw SiteError.operationFailed(";
"正在卸载 \\(software.name)..." = "正在卸载 \\(software.name)...";
"))\n\n        // 设置所有菜单项的 target\n        for item in menu.items {\n            item.target = self\n        }\n\n        statusItem?.menu = menu\n    }\n\n    @objc private func statusBarButtonClicked() {\n        showMainWindow()\n    }\n\n    @objc private func showMainWindow() {\n        // 激活应用并显示主窗口\n        NSApp.activate(ignoringOtherApps: true)\n\n        // 显示所有窗口\n        for window in NSApp.windows {\n            window.makeKeyAndOrderFront(nil)\n        }\n    }\n\n    @objc private func quitApp() {\n        NSApp.terminate(nil)\n    }\n\n    // 更新菜单中的服务状态\n    func updateServiceStatus() {\n        guard let menu = statusItem?.menu,\n              let appState = appState else { return }\n\n        // 找到服务状态部分（索引 3 之后）\n        // 先移除旧的服务状态项\n        while menu.items.count > 4 {\n            menu.removeItem(at: 3)\n        }\n\n        // 添加新的服务状态\n        for (index, service) in appState.services.enumerated() {\n            let statusIcon = service.state == .running ? " = "))\n\n        // 设置所有菜单项的 target\n        for item in menu.items {\n            item.target = self\n        }\n\n        statusItem?.menu = menu\n    }\n\n    @objc private func statusBarButtonClicked() {\n        showMainWindow()\n    }\n\n    @objc private func showMainWindow() {\n        // 激活应用并显示主窗口\n        NSApp.activate(ignoringOtherApps: true)\n\n        // 显示所有窗口\n        for window in NSApp.windows {\n            window.makeKeyAndOrderFront(nil)\n        }\n    }\n\n    @objc private func quitApp() {\n        NSApp.terminate(nil)\n    }\n\n    // 更新菜单中的服务状态\n    func updateServiceStatus() {\n        guard let menu = statusItem?.menu,\n              let appState = appState else { return }\n\n        // 找到服务状态部分（索引 3 之后）\n        // 先移除旧的服务状态项\n        while menu.items.count > 4 {\n            menu.removeItem(at: 3)\n        }\n\n        // 添加新的服务状态\n        for (index, service) in appState.services.enumerated() {\n            let statusIcon = service.state == .running ? ";
", selection: $sourceType) {\n                        ForEach(RewriteType.allCases, id: \\.self) { type in\n                            Text(LocalizedStringKey(type.rawValue)).tag(type)\n                        }\n                    }\n                    .pickerStyle(.segmented)\n                    .frame(width: 150)\n                }\n\n                TextEditor(text: $sourceRules)\n                    .font(.system(.body, design: .monospaced))\n                    .macTextEditor()\n\n                // 字符统计和清空按钮\n                HStack {\n                    Text(" = ", selection: $sourceType) {\n                        ForEach(RewriteType.allCases, id: \\.self) { type in\n                            Text(LocalizedStringKey(type.rawValue)).tag(type)\n                        }\n                    }\n                    .pickerStyle(.segmented)\n                    .frame(width: 150)\n                }\n\n                TextEditor(text: $sourceRules)\n                    .font(.system(.body, design: .monospaced))\n                    .macTextEditor()\n\n                // 字符统计和清空按钮\n                HStack {\n                    Text(";
"\n    }\n\n    /// 保存 Nginx 配置到文件\n    func saveNginxConfig() async -> Bool {\n        let config = generateNginxConfig()\n        let configPath = " = "\n    }\n\n    /// 保存 Nginx 配置到文件\n    func saveNginxConfig() async -> Bool {\n        let config = generateNginxConfig()\n        let configPath = ";
"当前端口: \\(String(currentPort))" = "当前端口: \\(String(currentPort))";
" 前缀\n                versions.append(version)\n            }\n        }\n        return versions.sorted()\n    }\n\n    // 获取已安装的Web服务器列表\n    private var installedWebServers: [WebServerType] {\n        var servers: [WebServerType] = []\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if name == " = " 前缀\n                versions.append(version)\n            }\n        }\n        return versions.sorted()\n    }\n\n    // 获取已安装的Web服务器列表\n    private var installedWebServers: [WebServerType] {\n        var servers: [WebServerType] = []\n        for service in appState.services {\n            let name = service.name.lowercased()\n            if name == ";
"适用于 \\(rawValue) 内存的服务器" = "适用于 \\(rawValue) 内存的服务器";
"\n            ]\n        }\n        return []\n    }\n\n    /// 诊断服务停止原因（nonisolated，后台线程调用）\n    private nonisolated static func diagnoseServiceStopReason(\n        service: ServiceStatus,\n        brewPath: String?,\n        homebrewPrefix: String,\n        configPaths: [String],\n        hasRecentError: Bool\n    ) -> StopReason {\n        let name = service.name.lowercased()\n        let brewName = service.brewName ?? " = "\n            ]\n        }\n        return []\n    }\n\n    /// 诊断服务停止原因（nonisolated，后台线程调用）\n    private nonisolated static func diagnoseServiceStopReason(\n        service: ServiceStatus,\n        brewPath: String?,\n        homebrewPrefix: String,\n        configPaths: [String],\n        hasRecentError: Bool\n    ) -> StopReason {\n        let name = service.name.lowercased()\n        let brewName = service.brewName ?? ";
"无效的域名：\\(domain)" = "无效的域名：\\(domain)";
")\n        }\n    }\n\n    /// 执行配置文件迁移\n    func migrateConfig(_ info: ConfigMigrationInfo) throws {\n        // 创建目标目录\n        try FileManager.default.createDirectory(\n            atPath: info.targetDirectory,\n            withIntermediateDirectories: true,\n            attributes: nil\n        )\n        // 复制配置文件到工作空间\n        try FileManager.default.copyItem(atPath: info.sourceFile, toPath: info.targetFile)\n\n        // 将原文件重命名为 .bak 备份\n        let backupPath = info.sourceFile + " = ")\n        }\n    }\n\n    /// Execute configuration file migration\n    func migrateConfig(_ info: ConfigMigrationInfo) throws {\n        // Create target directory\n        try FileManager.default.createDirectory(\n            atPath: info.targetDirectory,\n            withIntermediateDirectories: true,\n            attributes: nil\n        )\n        // Copy configuration file to workspace\n        try FileManager.default.copyItem(atPath: info.sourceFile, toPath: info.targetFile)\n\n        // Rename original file to .bak for backup\n        let backupPath = info.sourceFile + \", ";
":\n                    selectedAccessType = .localhost\n                default:\n                    selectedAccessType = .customIP\n                    customIP = host\n                }\n            }\n        }\n    }\n\n    // 获取当前主机设置\n    private func fetchCurrentHost() async -> String {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: " = ":\n                    selectedAccessType = .localhost\n                default:\n                    selectedAccessType = .customIP\n                    customIP = host\n                }\n            }\n        }\n    }\n\n    // Fetch current host setting\n    private func fetchCurrentHost() async -> String {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                guard let mysqlPath = MySQLClientPaths.findMysql() else {\n                    continuation.resume(returning: \"\", ";
"连接成功，MySQL版本: \\(shortVersion)" = "Connection successful, MySQL version: \\(shortVersion)";
"\n\n        // 确保配置目录存在\n        try FileManager.default.createDirectory(\n            atPath: configDir,\n            withIntermediateDirectories: true\n        )\n\n        // 生成配置内容\n        var config = " = "\n\n        // Ensure config directory exists\n        try FileManager.default.createDirectory(\n            atPath: configDir,\n            withIntermediateDirectories: true\n        )\n\n        // Generate config content\n        var config = \"\", ";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 表单内容\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // 域名\n                FormField(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // Form content\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // Domain name\n                FormField(\"\", ";
"火山" = "Volcano";
"\n    }\n\n    /// MySQL 数据目录 (按版本)\n    func mysqlDataPath(version: String) -> String {\n        " = "\n    }\n\n    /// MySQL data directory (by version)\n    func mysqlDataPath(version: String) -> String {\n        \"\", ";
", iconColor: .cyan, iconBgColor: .cyan.opacity(AppTokens.Opacity.light))\n                        }\n                    }\n                }\n            }\n            .padding()\n        }\n    }\n\n    // MARK: - 选择日志文件\n    private func selectLogFile() {\n        let panel = NSOpenPanel()\n        panel.title = " = ", iconColor: .cyan, iconBgColor: .cyan.opacity(AppTokens.Opacity.light))\n                        }\n                    }\n                }\n            }\n            .padding()\n        }\n    }\n\n    // MARK: - Select log file\n    private func selectLogFile() {\n        let panel = NSOpenPanel()\n        panel.title = \"\", ";
")\n        }\n\n        // 提取 Apache 配置的端口信息\n        let backendPorts = apacheBackendPorts()\n        let apacheHttpPort = config.parsed.ports.contains(80) ? backendPorts.http : (config.parsed.ports.first ?? backendPorts.http)\n        let apacheHttpsPort = backendPorts.https\n\n        // 创建临时站点对象用于生成配置\n        let siteName = config.parsed.fileName.replacingOccurrences(of: " = ")\n        }\n\n        // Extract Apache configuration port information\n        let backendPorts = apacheBackendPorts()\n        let apacheHttpPort = config.parsed.ports.contains(80) ? backendPorts.http : (config.parsed.ports.first ?? backendPorts.http)\n        let apacheHttpsPort = backendPorts.https\n\n        // Create temporary site object for config generation\n        let siteName = config.parsed.fileName.replacingOccurrences(of: \"\", ";
")\n    }\n\n    // 格式化配置行（根据服务类型）\n    private func formatConfigLine(key: String, value: String) -> String {\n        switch service.name {\n        case " = ")\n    }\n\n    // Format config line (by service type)\n    private func formatConfigLine(key: String, value: String) -> String {\n        switch service.name {\n        case \"\", ";
"已重启 \\(webServices.count) 个Web服务" = "Restarted \\(webServices.count) web services";
", brewName]\n\n        do {\n            try pgrepProcess.run()\n            pgrepProcess.waitUntilExit()\n            completion(pgrepProcess.terminationStatus == 0)\n        } catch {\n            completion(false)\n        }\n    }\n\n    // 添加状态消息\n    private func addStatusMessage(_ text: String, type: StatusMessage.StatusType) {\n        statusMessages.append(StatusMessage(text: text, type: type))\n    }\n\n    // 更新最后一条状态消息\n    private func updateLastStatusMessage(_ text: String, type: StatusMessage.StatusType) {\n        if !statusMessages.isEmpty {\n            statusMessages[statusMessages.count - 1] = StatusMessage(text: text, type: type)\n        }\n    }\n\n    // 更新指定索引的状态消息\n    private func updateStatusMessage(at index: Int, _ text: String, type: StatusMessage.StatusType) {\n        if index >= 0 && index < statusMessages.count {\n            statusMessages[index] = StatusMessage(text: text, type: type)\n        }\n    }\n\n    // 获取 brew 服务名\n    private func getBrewServiceName() -> String {\n        switch service.name {\n        case " = "], brewName]\n\n        do {\n            try pgrepProcess.run()\n            pgrepProcess.waitUntilExit()\n            completion(pgrepProcess.terminationStatus == 0)\n        } catch {\n            completion(false)\n        }\n    }\n\n    // Add status message\n    private func addStatusMessage(_ text: String, type: StatusMessage.StatusType) {\n        statusMessages.append(StatusMessage(text: text, type: type))\n    }\n\n    // Update last status message\n    private func updateLastStatusMessage(_ text: String, type: StatusMessage.StatusType) {\n        if !statusMessages.isEmpty {\n            statusMessages[statusMessages.count - 1] = StatusMessage(text: text, type: type)\n        }\n    }\n\n    // Update status message at specified index\n    private func updateStatusMessage(at index: Int, _ text: String, type: StatusMessage.StatusType) {\n        if index >= 0 && index < statusMessages.count {\n            statusMessages[index] = StatusMessage(text: text, type: type)\n        }\n    }\n\n    // Get brew service name\n    private func getBrewServiceName() -> String {\n        switch service.name {\n        case \"\", ";
"].contains(where: { name.contains($0) })\n    }\n\n    // 服务图标视图\n    @ViewBuilder\n    private var serviceIconView: some View {\n        ZStack {\n            RoundedRectangle(cornerRadius: 8)\n                .fill(Color.gray.opacity(AppTokens.Opacity.light))\n                .frame(width: 40, height: 40)\n\n            if let iconName = serviceIconName, let nsImage = loadServiceIcon(iconName) {\n                Image(nsImage: nsImage)\n                    .resizable()\n                    .aspectRatio(contentMode: .fit)\n                    .frame(width: 28, height: 28)\n            } else {\n                // 备用方案：显示字母图标\n                RoundedRectangle(cornerRadius: 8)\n                    .fill(serviceIconInfo.color)\n                    .frame(width: 40, height: 40)\n                    .overlay(\n                        Text(serviceIconInfo.letter)\n                            .font(.system(size: 18, weight: .bold))\n                            .foregroundColor(.white)\n                    )\n            }\n        }\n    }\n\n    // 从 bundle 加载服务图标\n    private func loadServiceIcon(_ name: String) -> NSImage? {\n        // 尝试从 Assets.xcassets 子目录加载\n        let bundlePath = Bundle.module.bundlePath\n        let imagePath = " = "].contains(where: { name.contains($0) })\n    }\n\n    // Service icon view\n    @ViewBuilder\n    private var serviceIconView: some View {\n        ZStack {\n            RoundedRectangle(cornerRadius: 8)\n                .fill(Color.gray.opacity(AppTokens.Opacity.light))\n                .frame(width: 40, height: 40)\n\n            if let iconName = serviceIconName, let nsImage = loadServiceIcon(iconName) {\n                Image(nsImage: nsImage)\n                    .resizable()\n                    .aspectRatio(contentMode: .fit)\n                    .frame(width: 28, height: 28)\n            } else {\n                // Fallback: display letter icon\n                RoundedRectangle(cornerRadius: 8)\n                    .fill(serviceIconInfo.color)\n                    .frame(width: 40, height: 40)\n                    .overlay(\n                        Text(serviceIconInfo.letter)\n                            .font(.system(size: 18, weight: .bold))\n                            .foregroundColor(.white)\n                    )\n            }\n        }\n    }\n\n    // Load service icon from bundle\n    private func loadServiceIcon(_ name: String) -> NSImage? {\n        // Try loading from Assets.xcassets subdirectory\n        let bundlePath = Bundle.module.bundlePath\n        let imagePath = \"\", ";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    private var contentView: some View {\n        VStack(spacing: 16) {\n            // 目标数据库\n            HStack {\n                Text(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    private var contentView: some View {\n        VStack(spacing: 16) {\n            // Target database\n            HStack {\n                Text(\"\", ";
")\n                                .font(.system(size: 10))\n                                .foregroundColor(.orange)\n                        }\n                    }\n\n                    // 第四行：设置选项\n                    HStack(spacing: 16) {\n                        Text(" = ")\n                                .font(.system(size: 10))\n                                .foregroundColor(.orange)\n                        }\n                    }\n\n                    // Fourth row: Settings options\n                    HStack(spacing: 16) {\n                        Text(\"\", ";
"\n        }\n    }\n\n    var color: Color {\n        switch self {\n        case .manual: return .secondary\n        case .portConflict: return .orange\n        case .configError, .configMissing: return .red\n        case .crashExit: return .red\n        case .unknown: return .secondary\n        }\n    }\n}\n\nstruct SoftwareVersion: Identifiable {\n    let id: String\n    let name: String\n    let version: String\n    var installed: Bool\n    var isDefault: Bool\n    var isRunning: Bool\n    var installPath: String?\n    var brewName: String?  // Homebrew 包名\n    var port: Int?\n    var hasPortConflict: Bool = false  // 是否存在端口冲突\n    var conflictingService: String?    // 冲突的服务名称\n    var configPath: String?\n    var installedAt: Date?\n    var badge: String?\n    var badgeColor: BadgeColor?\n    var stopReason: StopReason?  // 停止原因\n\n    var displayName: String {\n        " = "\n        }\n    }\n\n    var color: Color {\n        switch self {\n        case .manual: return .secondary\n        case .portConflict: return .orange\n        case .configError, .configMissing: return .red\n        case .crashExit: return .red\n        case .unknown: return .secondary\n        }\n    }\n}\n\nstruct SoftwareVersion: Identifiable {\n    let id: String\n    let name: String\n    let version: String\n    var installed: Bool\n    var isDefault: Bool\n    var isRunning: Bool\n    var installPath: String?\n    var brewName: String?  // Homebrew package name\n    var port: Int?\n    var hasPortConflict: Bool = false  // Whether port conflict exists\n    var conflictingService: String?    // Conflicting service name\n    var configPath: String?\n    var installedAt: Date?\n    var badge: String?\n    var badgeColor: BadgeColor?\n    var stopReason: StopReason?  // Reason for stopping\n\n    var displayName: String {\n        \"\", ";
") {\n                    onInstall()\n                }\n                .buttonStyle(.bordered)\n                .controlSize(.small)\n            }\n        }\n        .padding(.vertical, 8)\n        .padding(.horizontal, 12)\n    }\n\n    // 加载中视图（带进度）\n    private var loadingView: some View {\n        HStack(spacing: 8) {\n            if let progress = installProgress {\n                // 进度条\n                VStack(alignment: .trailing, spacing: 4) {\n                    HStack(spacing: 6) {\n                        Text(progress.phase.rawValue)\n                            .font(.caption)\n                            .foregroundColor(.secondary)\n\n                        ProgressView()\n                            .controlSize(.small)\n                    }\n\n                    // 进度条\n                    ProgressView(value: progress.progress)\n                        .progressViewStyle(.linear)\n                        .frame(width: 120)\n                }\n\n                // 展开/收起按钮\n                Button(action: onToggleProgress) {\n                    Image(systemName: progress.isExpanded ? " = ") {\n                    onInstall()\n                }\n                .buttonStyle(.bordered)\n                .controlSize(.small)\n            }\n        }\n        .padding(.vertical, 8)\n        .padding(.horizontal, 12)\n    }\n\n    // Loading view (with progress)\n    private var loadingView: some View {\n        HStack(spacing: 8) {\n            if let progress = installProgress {\n                // Progress bar\n                VStack(alignment: .trailing, spacing: 4) {\n                    HStack(spacing: 6) {\n                        Text(progress.phase.rawValue)\n                            .font(.caption)\n                            .foregroundColor(.secondary)\n\n                        ProgressView()\n                            .controlSize(.small)\n                    }\n\n                    // Progress bar\n                    ProgressView(value: progress.progress)\n                        .progressViewStyle(.linear)\n                        .frame(width: 120)\n                }\n\n                // Expand/collapse button\n                Button(action: onToggleProgress) {\n                    Image(systemName: progress.isExpanded ? \"\", ";
")\n\n        return paths\n    }\n\n    // MARK: - PHP 路径\n\n    /// 获取 PHP 配置文件路径（优先返回存在的文件）\n    func getPHPConfigPath(version: String) -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.phpConfigFile(version: version)\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        return " = ")\n\n        return paths\n    }\n\n    // MARK: - PHP Path\n\n    /// Get PHP configuration file path (return existing file first)\n    func getPHPConfigPath(version: String) -> String {\n        if let config = workspaceManager.config, config.isInitialized {\n            let workspacePath = config.phpConfigFile(version: version)\n            if FileManager.default.fileExists(atPath: workspacePath) {\n                return workspacePath\n            }\n        }\n        return \"\", ";
"配置检测:\\n\\(configErr)" = "Configuration check:\\n\\(configErr)";
")\n        guard parts.count == 4 else { return false }\n        for part in parts {\n            guard let num = Int(part), num >= 0, num <= 255 else { return false }\n        }\n        return true\n    }\n\n    // 验证域名\n    private func isValidHostname(_ hostname: String) -> Bool {\n        // 简单的域名验证\n        let pattern = " = ")\n        guard parts.count == 4 else { return false }\n        for part in parts {\n            guard let num = Int(part), num >= 0, num <= 255 else { return false }\n        }\n        return true\n    }\n\n    // Validate hostname\n    private func isValidHostname(_ hostname: String) -> Bool {\n        // Simple hostname validation\n        let pattern = \"";
"\n                    if let port = Int(portStr) {\n                        if !ports.contains(port) {\n                            ports.append(port)\n                        }\n                    }\n                }\n            }\n        }\n\n        // 验证必需字段\n        // 如果是代理配置，不需要 documentRoot；否则需要\n        guard !domains.isEmpty else {\n            return nil\n        }\n\n        if !isProxy && documentRoot.isEmpty {\n            return nil\n        }\n\n        let fileName = (filePath as NSString).lastPathComponent\n        return ParsedSiteConfig(\n            fileName: fileName,\n            domains: domains,\n            documentRoot: documentRoot,\n            webServer: .nginx,\n            hasSSL: hasSSL,\n            ports: ports,\n            isProxy: isProxy,\n            proxyBackendPort: proxyBackendPort\n        )\n    }\n\n    /// 解析 Apache 配置文件\n    static func parseApacheConfig(filePath: String) -> ParsedSiteConfig? {\n        guard let content = try? String(contentsOfFile: filePath, encoding: .utf8) else {\n            return nil\n        }\n\n        var domains: [String] = []\n        var documentRoot = " = "\n                    if let port = Int(portStr) {\n                        if !ports.contains(port) {\n                            ports.append(port)\n                        }\n                    }\n                }\n            }\n        }\n\n        // Validate required fields\n        // If it's a proxy configuration, documentRoot is not required; otherwise, it is required\n        guard !domains.isEmpty else {\n            return nil\n        }\n\n        if !isProxy && documentRoot.isEmpty {\n            return nil\n        }\n\n        let fileName = (filePath as NSString).lastPathComponent\n        return ParsedSiteConfig(\n            fileName: fileName,\n            domains: domains,\n            documentRoot: documentRoot,\n            webServer: .nginx,\n            hasSSL: hasSSL,\n            ports: ports,\n            isProxy: isProxy,\n            proxyBackendPort: proxyBackendPort\n        )\n    }\n\n    /// Parse Apache configuration file\n    static func parseApacheConfig(filePath: String) -> ParsedSiteConfig? {\n        guard let content = try? String(contentsOfFile: filePath, encoding: .utf8) else {\n            return nil\n        }\n\n        var domains: [String] = []\n        var documentRoot = ";
"\n\n        // 检查是否已打开\n        if let existingTab = databaseManager.openedDatabaseTabs.first(where: { $0.id == tabId }) {\n            databaseManager.activeTabId = existingTab.id\n            return\n        }\n\n        // 添加新标签\n        let newTab = OpenedDatabaseTab(id: tabId, name: name, isLocal: isLocal, host: host, port: port, databaseId: databaseId)\n        databaseManager.openedDatabaseTabs.append(newTab)\n        databaseManager.activeTabId = newTab.id\n    }\n\n    // 关闭数据库标签页\n    private func closeDatabaseTab(_ tab: OpenedDatabaseTab) {\n        databaseManager.openedDatabaseTabs.removeAll { $0.id == tab.id }\n        if databaseManager.activeTabId == tab.id {\n            databaseManager.activeTabId = nil\n        }\n    }\n\n    // 当前活动的连接信息\n    private var activeConnectionInfo: (host: String, port: Int, isLocal: Bool, name: String) {\n        // 如果有选中的标签页，返回该标签页的信息\n        if let tabId = databaseManager.activeTabId,\n           let activeTab = databaseManager.openedDatabaseTabs.first(where: { $0.id == tabId }) {\n            return (activeTab.host, activeTab.port, activeTab.isLocal, activeTab.name)\n        }\n        // 默认返回本地 MySQL 信息\n        return (" = "\n\n        // Check if already open\n        if let existingTab = databaseManager.openedDatabaseTabs.first(where: { $0.id == tabId }) {\n            databaseManager.activeTabId = existingTab.id\n            return\n        }\n\n        // Add new tab\n        let newTab = OpenedDatabaseTab(id: tabId, name: name, isLocal: isLocal, host: host, port: port, databaseId: databaseId)\n        databaseManager.openedDatabaseTabs.append(newTab)\n        databaseManager.activeTabId = newTab.id\n    }\n\n    // Close database tab\n    private func closeDatabaseTab(_ tab: OpenedDatabaseTab) {\n        databaseManager.openedDatabaseTabs.removeAll { $0.id == tab.id }\n        if databaseManager.activeTabId == tab.id {\n            databaseManager.activeTabId = nil\n        }\n    }\n\n    // Current active connection information\n    private var activeConnectionInfo: (host: String, port: Int, isLocal: Bool, name: String) {\n        // If a tab is selected, return its information\n        if let tabId = databaseManager.activeTabId,\n           let activeTab = databaseManager.openedDatabaseTabs.first(where: { $0.id == tabId }) {\n            return (activeTab.host, activeTab.port, activeTab.isLocal, activeTab.name)\n        }\n        // Default to local MySQL information\n        return (";
"导入选中项 (\\(selectedConfigs.count))" = "Import selected items (\\(selectedConfigs.count))";
"\n    }\n\n    private init() {\n        Task {\n            await refreshStatus()\n        }\n    }\n\n    // MARK: - 状态检测\n\n    /// 刷新 Web 服务器状态\n    func refreshStatus() async {\n        await detectNginxStatus()\n        await detectApacheStatus()\n    }\n\n    /// 检测 Nginx 运行状态和端口\n    private func detectNginxStatus() async {\n        let result = await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                // 使用 pgrep 检测 nginx 进程\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: " = "\n    }\n\n    private init() {\n        Task {\n            await refreshStatus()\n        }\n    }\n\n    // MARK: - Status Detection\n\n    /// Refresh web server status\n    func refreshStatus() async {\n        await detectNginxStatus()\n        await detectApacheStatus()\n    }\n\n    /// Detect Nginx running status and ports\n    private func detectNginxStatus() async {\n        let result = await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                // Use pgrep to detect nginx processes\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: ";
"]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            // lsof 输出中可能包含非 UTF-8 字节，这里使用容错解码避免全量扫描结果为空\n            let output = String(decoding: data, as: UTF8.self)\n            guard !output.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {\n                return []\n            }\n\n            let lines = output.components(separatedBy: .newlines).filter { !$0.isEmpty }\n            guard lines.count > 1 else {\n                return []\n            }\n\n            var result: [PortItem] = []\n            var seen = Set<String>()\n\n            for line in lines.dropFirst() {\n                let parts = line.split(whereSeparator: \\.isWhitespace)\n                guard parts.count >= 9 else { continue }\n\n                let processName = String(parts[0])\n                let pid = String(parts[1])\n                // lsof NAME 列常见格式是 " = "]\n\n        let pipe = Pipe()\n        process.standardOutput = pipe\n        process.standardError = pipe\n\n        do {\n            try process.run()\n            process.waitUntilExit()\n\n            let data = pipe.fileHandleForReading.readDataToEndOfFile()\n            // lsof output may contain non-UTF-8 bytes; use fault-tolerant decoding here to avoid empty results\n            let output = String(decoding: data, as: UTF8.self)\n            guard !output.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {\n                return []\n            }\n\n            let lines = output.components(separatedBy: .newlines).filter { !$0.isEmpty }\n            guard lines.count > 1 else {\n                return []\n            }\n\n            var result: [PortItem] = []\n            var seen = Set<String>()\n\n            for line in lines.dropFirst() {\n                let parts = line.split(whereSeparator: \\.isWhitespace)\n                guard parts.count >= 9 else { continue }\n\n                let processName = String(parts[0])\n                let pid = String(parts[1])\n                // Common format for lsof NAME column is ";
"]\n\n    var body: some View {\n        HStack(spacing: 8) {\n            // 占位（与 DatabaseTableRow 对齐）\n            Color.clear.frame(width: 30)\n\n            // 数据库名\n            HStack(spacing: 6) {\n                Image(systemName: isSystemDatabase ? " = "]\n\n    var body: some View {\n        HStack(spacing: 8) {\n            // Placeholder (aligned with DatabaseTableRow)\n            Color.clear.frame(width: 30)\n\n            // Database name\n            HStack(spacing: 6) {\n                Image(systemName: isSystemDatabase ? ";
"保存证书数据失败: \\(error)" = "Failed to save certificate data: \\(error)";
", maxSplits: 1)\n                if parts.count == 2 {\n                    let value = parts[1].trimmingCharacters(in: .whitespaces)\n                    // 提取端口号（可能是 " = ", maxSplits: 1)\n                if parts.count == 2 {\n                    let value = parts[1].trimmingCharacters(in: .whitespaces)\n                    // Extract port number (may be ";
"]\n\n    // 访问权限选项\n    private let accessOptions = [\n        (" = "]\n\n    // Access permission options\n    private let accessOptions = [\n        (";
")\n                            .foregroundColor(.secondary)\n                            .font(.system(size: 14))\n                    }\n                    .buttonStyle(.plain)\n                }\n            }\n            .padding(.horizontal, 12)\n            .padding(.vertical, 8)\n            .background(Color.white.opacity(0.1))\n            .cornerRadius(AppTokens.Radius.small)\n            .overlay(\n                RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                    .stroke(Color.white.opacity(0.2), lineWidth: 1)\n            )\n            .frame(width: 300)\n\n            Spacer()\n\n            // 显示IP归属地信息开关\n            Toggle(isOn: $showIPInfo) {\n                Text(LocalizedStringKey(" = ")\n                            .foregroundColor(.secondary)\n                            .font(.system(size: 14))\n                    }\n                    .buttonStyle(.plain)\n                }\n            }\n            .padding(.horizontal, 12)\n            .padding(.vertical, 8)\n            .background(Color.white.opacity(0.1))\n            .cornerRadius(AppTokens.Radius.small)\n            .overlay(\n                RoundedRectangle(cornerRadius: AppTokens.Radius.small)\n                    .stroke(Color.white.opacity(0.2), lineWidth: 1)\n            )\n            .frame(width: 300)\n\n            Spacer()\n\n            // Toggle for displaying IP location information\n            Toggle(isOn: $showIPInfo) {\n                Text(LocalizedStringKey(";
"macOS PHP 环境管理工具" = "macOS PHP Environment Management Tool";
") {\n                openLogDirectory()\n            }\n            .buttonStyle(.bordered)\n            .controlSize(.regular)\n\n            // 刷新按钮\n            Button(action: {\n                refreshLogs()\n            }) {\n                HStack(spacing: 4) {\n                    Image(systemName: " = ") {\n                openLogDirectory()\n            }\n            .buttonStyle(.bordered)\n            .controlSize(.regular)\n\n            // Refresh button\n            Button(action: {\n                refreshLogs()\n            }) {\n                HStack(spacing: 4) {\n                    Image(systemName: ";
"  // 支持 MySQL 8.0+ 认证\n                ]\n                if !dbPassword.isEmpty {\n                    arguments.append(" = "  // Support MySQL 8.0+ authentication\n                ]\n                if !dbPassword.isEmpty {\n                    arguments.append(";
") {\n                    // 提取颁发者\n                    if let cnRange = line.range(of: " = ") {\n                    // Extract issuer\n                    if let cnRange = line.range(of: ";
")\n        return formatter.date(from: dateString)\n    }\n\n    // MARK: - 证书生成\n\n    /// 生成SSL证书\n    /// - Parameters:\n    ///   - domains: 域名列表\n    ///   - outputDirectory: 输出目录\n    ///   - certificateName: 证书文件名（不含扩展名）\n    /// - Returns: 证书路径和私钥路径\n    func generateCertificate(domains: [String], outputDirectory: String, certificateName: String) async throws -> (certPath: String, keyPath: String) {\n        guard !domains.isEmpty else {\n            throw NSError(domain: " = ")\n        return formatter.date(from: dateString)\n    }\n\n    // MARK: - Certificate Generation\n\n    /// Generate SSL certificate\n    /// - Parameters:\n    ///   - domains: List of domains\n    ///   - outputDirectory: Output directory\n    ///   - certificateName: Certificate filename (without extension)\n    /// - Returns: Certificate path and private key path\n    func generateCertificate(domains: [String], outputDirectory: String, certificateName: String) async throws -> (certPath: String, keyPath: String) {\n        guard !domains.isEmpty else {\n            throw NSError(domain: ";
", configPath]\n\n                do {\n                    try process.run()\n                    return\n                } catch {\n                    // 如果 open 命令失败,尝试直接打开\n                    NSWorkspace.shared.open(URL(fileURLWithPath: configPath))\n                    return\n                }\n            }\n        }\n\n        // 如果没有找到配置文件,显示错误提示\n        let alert = NSAlert()\n        alert.messageText = " = ", configPath]\n\n                do {\n                    try process.run()\n                    return\n                } catch {\n                    // If 'open' command fails, attempt direct opening\n                    NSWorkspace.shared.open(URL(fileURLWithPath: configPath))\n                    return\n                }\n            }\n        }\n\n        // If no configuration file is found, display error message\n        let alert = NSAlert()\n        alert.messageText = ";
"\n            # phpMyAdmin 配置\n            location /phpmyadmin {\n                alias \\(pmaPath);\n                index index.php;\n\n                location ~ \\\\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_index index.php;\n                    fastcgi_param SCRIPT_FILENAME $request_filename;\n                }\n            }\n\n        " = "\n            # phpMyAdmin configuration\n            location /phpmyadmin {\n                alias \\(pmaPath);\n                index index.php;\n\n                location ~ \\\\.php$ {\n                    include fastcgi_params;\n                    fastcgi_pass 127.0.0.1:9000;\n                    fastcgi_index index.php;\n                    fastcgi_param SCRIPT_FILENAME $request_filename;\n                }\n            }\n\n        ";
")\n                    .font(.system(size: 13))\n                    .foregroundColor(.secondary)\n\n                // 下一页按钮\n                Button(action: { currentPage = min(totalPages, currentPage + 1) }) {\n                    Image(systemName: " = ")\n                    .font(.system(size: 13))\n                    .foregroundColor(.secondary)\n\n                // Next page button\n                Button(action: { currentPage = min(totalPages, currentPage + 1) }) {\n                    Image(systemName: ";
"\n        if fm.fileExists(atPath: brewNginxConf) {\n            // 检查是否是软链接\n            let isSymlink = (try? fm.destinationOfSymbolicLink(atPath: brewNginxConf)) != nil\n\n            if isSymlink {\n                // 如果是软链接，直接删除（不备份）\n                try fm.removeItem(atPath: brewNginxConf)\n            } else {\n                // 如果是普通文件，且 .bak 不存在，则重命名为 .bak\n                if !fm.fileExists(atPath: brewNginxConfBak) {\n                    try fm.moveItem(atPath: brewNginxConf, toPath: brewNginxConfBak)\n                } else {\n                    // 如果 .bak 已存在，直接删除原始文件\n                    try fm.removeItem(atPath: brewNginxConf)\n                }\n            }\n        }\n        try fm.createSymbolicLink(atPath: brewNginxConf, withDestinationPath: wsNginxConf)\n\n        // 4. 处理 servers/default.conf\n        let brewDefaultConfBak = " = "\n        if fm.fileExists(atPath: brewNginxConf) {\n            // Check if it's a symbolic link\n            let isSymlink = (try? fm.destinationOfSymbolicLink(atPath: brewNginxConf)) != nil\n\n            if isSymlink {\n                // If it's a symbolic link, delete directly (no backup)\n                try fm.removeItem(atPath: brewNginxConf)\n            } else {\n                // If it's a regular file and .bak doesn't exist, rename to .bak\n                if !fm.fileExists(atPath: brewNginxConfBak) {\n                    try fm.moveItem(atPath: brewNginxConf, toPath: brewNginxConfBak)\n                } else {\n                    // If .bak already exists, delete the original file directly\n                    try fm.removeItem(atPath: brewNginxConf)\n                }\n            }\n        }\n        try fm.createSymbolicLink(atPath: brewNginxConf, withDestinationPath: wsNginxConf)\n\n        // 4. Handle servers/default.conf\n        let brewDefaultConfBak = ";
"\n            } else {\n                // 同时支持 HTTP 和 HTTPS\n                configContent = " = "\n            } else {\n                // Support both HTTP and HTTPS simultaneously\n                configContent = ";
"\n        }\n    }\n\n    // 重启 MySQL\n    private func restartMySQL() {\n        isSaving = true\n        errorMessage = nil\n        successMessage = nil\n\n        let brewPaths = [" = "\n        }\n    }\n\n    // Restart MySQL\n    private func restartMySQL() {\n        isSaving = true\n        errorMessage = nil\n        successMessage = nil\n\n        let brewPaths = [";
"]\n                }\n\n                process.standardOutput = Pipe()\n                process.standardError = Pipe()\n\n                // 添加超时处理\n                var hasResumed = false\n                let timeoutWorkItem = DispatchWorkItem {\n                    if !hasResumed {\n                        hasResumed = true\n                        process.terminate()\n                        continuation.resume(returning: false)\n                    }\n                }\n\n                DispatchQueue.global().asyncAfter(deadline: .now() + 10, execute: timeoutWorkItem)\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    timeoutWorkItem.cancel()\n                    if !hasResumed {\n                        hasResumed = true\n                        continuation.resume(returning: process.terminationStatus == 0)\n                    }\n                } catch {\n                    timeoutWorkItem.cancel()\n                    if !hasResumed {\n                        hasResumed = true\n                        continuation.resume(returning: false)\n                    }\n                }\n            }\n        }\n    }\n\n    // MARK: - 端口监听检查\n\n    private func checkPorts(for site: Site) async -> HealthCheckResult {\n        var details: [String] = []\n        var missingPorts: [Int] = []\n\n        // 根据配置确定需要检查的端口\n        var portsToCheck: [Int] = []\n\n        switch site.webServer {\n        case .nginx:\n            // Nginx 直接模式：80, 443\n            portsToCheck = [80]\n            if site.sslStatus == .enabled {\n                portsToCheck.append(443)\n            }\n        case .apache:\n            let apacheHttpPort = AppSettings.shared.apachePort\n            let apacheHttpsPort = 8443\n\n            // Apache 后端模式\n            portsToCheck = [apacheHttpPort]\n            if site.sslStatus == .enabled && site.sslMode == .endToEnd {\n                portsToCheck.append(apacheHttpsPort)\n            }\n            // Nginx 前端\n            portsToCheck.append(80)\n            if site.sslStatus == .enabled {\n                portsToCheck.append(443)\n            }\n        }\n\n        // 检查每个端口\n        for port in portsToCheck {\n            let isListening = await checkPortListening(port)\n            if isListening {\n                details.append(" = "]\n                }\n\n                process.standardOutput = Pipe()\n                process.standardError = Pipe()\n\n                // Add timeout handling\n                var hasResumed = false\n                let timeoutWorkItem = DispatchWorkItem {\n                    if !hasResumed {\n                        hasResumed = true\n                        process.terminate()\n                        continuation.resume(returning: false)\n                    }\n                }\n\n                DispatchQueue.global().asyncAfter(deadline: .now() + 10, execute: timeoutWorkItem)\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n\n                    timeoutWorkItem.cancel()\n                    if !hasResumed {\n                        hasResumed = true\n                        continuation.resume(returning: process.terminationStatus == 0)\n                    }\n                } catch {\n                    timeoutWorkItem.cancel()\n                    if !hasResumed {\n                        hasResumed = true\n                        continuation.resume(returning: false)\n                    }\n                }\n            }\n        }\n    }\n\n    // MARK: - Port Listening Check\n\n    private func checkPorts(for site: Site) async -> HealthCheckResult {\n        var details: [String] = []\n        var missingPorts: [Int] = []\n\n        // Determine ports to check based on configuration\n        var portsToCheck: [Int] = []\n\n        switch site.webServer {\n        case .nginx:\n            // Nginx direct mode: 80, 443\n            portsToCheck = [80]\n            if site.sslStatus == .enabled {\n                portsToCheck.append(443)\n            }\n        case .apache:\n            let apacheHttpPort = AppSettings.shared.apachePort\n            let apacheHttpsPort = 8443\n\n            // Apache backend mode\n            portsToCheck = [apacheHttpPort]\n            if site.sslStatus == .enabled && site.sslMode == .endToEnd {\n                portsToCheck.append(apacheHttpsPort)\n            }\n            // Nginx frontend\n            portsToCheck.append(80)\n            if site.sslStatus == .enabled {\n                portsToCheck.append(443)\n            }\n        }\n\n        // Check each port\n        for port in portsToCheck {\n            let isListening = await checkPortListening(port)\n            if isListening {\n                details.append(";
"\n        }\n\n        return locationBlocks\n    }\n\n    // 复制站点\n    func duplicateSite(_ site: Site) async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        // 生成新站点名称（在原名称后加序号）\n        var newName = " = "\n        }\n\n        return locationBlocks\n    }\n\n    // Duplicate site\n    func duplicateSite(_ site: Site) async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        // Generate new site name (append sequence number to original name)\n        var newName = ";
"火山引擎" = "VolcEngine";
"正在准备安装 \\(ext.name)..." = "Preparing to install \\(ext.name)...";
"))\n                                        .font(.system(size: 12))\n                                        .foregroundColor(.secondary)\n                                        .frame(maxWidth: .infinity, alignment: .leading)\n                                }\n                                .padding(.horizontal, 16)\n                                .padding(.vertical, 8)\n\n                                if item.ip != results.topIPs.prefix(100).last?.ip {\n                                    Divider()\n                                }\n                            }\n                        }\n            .glass(.input)\n                        .overlay(\n                            RoundedRectangle(cornerRadius: 12)\n                                .stroke(Color.gray.opacity(AppTokens.Opacity.hover), lineWidth: 1)\n                        )\n                    }\n                }\n\n                // 总计\n                Text(" = "))\n                                        .font(.system(size: 12))\n                                        .foregroundColor(.secondary)\n                                        .frame(maxWidth: .infinity, alignment: .leading)\n                                }\n                                .padding(.horizontal, 16)\n                                .padding(.vertical, 8)\n\n                                if item.ip != results.topIPs.prefix(100).last?.ip {\n                                    Divider()\n                                }\n                            }\n                        }\n            .glass(.input)\n                        .overlay(\n                            RoundedRectangle(cornerRadius: 12)\n                                .stroke(Color.gray.opacity(AppTokens.Opacity.hover), lineWidth: 1)\n                        )\n                    }\n                }\n\n                // Total count\n                Text(";
") {\n            // 启用开关\n            Toggle(" = ") {\n            // Enable toggle\n            Toggle(";
")\n                }\n                args.append(name)\n                process.arguments = args\n\n                // 从文件读取 SQL\n                guard let inputHandle = FileHandle(forReadingAtPath: filePath.path) else {\n                    continuation.resume(returning: (false, " = ")\n                }\n                args.append(name)\n                process.arguments = args\n\n                // Read SQL from file\n                guard let inputHandle = FileHandle(forReadingAtPath: filePath.path) else {\n                    continuation.resume(returning: (false, ";
"\\(years) 年" = "\\(years) years";
", selection: $accessHost) {\n                ForEach(accessOptions, id: \\.0) { option in\n                    Text(option.1).tag(option.0)\n                }\n            }\n            .frame(maxWidth: .infinity)\n        }\n    }\n\n    // 目标服务器字段\n    private var targetServerField: some View {\n        HStack(alignment: .center, spacing: 12) {\n            Text(" = ", selection: $accessHost) {\n                ForEach(accessOptions, id: \\.0) { option in\n                    Text(option.1).tag(option.0)\n                }\n            }\n            .frame(maxWidth: .infinity)\n        }\n    }\n\n    // Target server field\n    private var targetServerField: some View {\n        HStack(alignment: .center, spacing: 12) {\n            Text(";
"[VhostScan] 写入日志失败: \\(error)" = "[VhostScan] Failed to write log: \\(error)";
":\n            return getMemcachedSyncRules()\n        default:\n            return []\n        }\n    }\n\n    // MARK: - Nginx 端口同步规则\n\n    private func getNginxSyncRules() -> [PortSyncRule] {\n        // Nginx 作为前端服务器，修改其端口通常不需要同步其他配置\n        // 但需要更新所有站点配置中的 listen 指令\n        return [\n            PortSyncRule(\n                targetSoftware: " = ":\n            return getMemcachedSyncRules()\n        default:\n            return []\n        }\n    }\n\n    // MARK: - Nginx Port Synchronization Rules\n\n    private func getNginxSyncRules() -> [PortSyncRule] {\n        // When Nginx acts as the frontend server, modifying its port typically does not require synchronizing other configurations,\n        // but all site configurations' \"listen\" directives need updating.\n        return [\n            PortSyncRule(\n                targetSoftware: ";
"认证失败: \\(msg)" = "Authentication failed: \\(msg)";
" {\n                    if isActive {\n                        RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                            .stroke(Color.blue.opacity(0.3), lineWidth: 1)\n                    }\n                } else {\n                    TabBorderShape(radius: 10)\n                        .stroke(isActive ? AppColors.border : Color.clear, lineWidth: 1)\n                }\n            }\n        )\n        // 遮挡底部边框，实现与下方内容融合\n        .overlay(\n            Group {\n                if isActive && BuildInfo.uiVersion != " = " {\n                    if isActive {\n                        RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                            .stroke(Color.blue.opacity(0.3), lineWidth: 1)\n                    }\n                } else {\n                    TabBorderShape(radius: 10)\n                        .stroke(isActive ? AppColors.border : Color.clear, lineWidth: 1)\n                }\n            }\n        )\n        // Overlay bottom border to blend with content below\n        .overlay(\n            Group {\n                if isActive && BuildInfo.uiVersion != ";
"成功导入 \\(successCount) 个站点，失败 \\(failCount) 个" = "Successfully imported \\(successCount) sites, failed \\(failCount)";
" : database.userPassword\n        NSPasteboard.general.clearContents()\n        NSPasteboard.general.setString(pwd, forType: .string)\n    }\n}\n\n// MARK: - 远程数据库权限设置 Sheet\nstruct RemoteDatabasePermissionsSheet: View {\n    let databaseName: String\n    let username: String\n    @ObservedObject var connector: MySQLConnector\n    @Environment(\\.dismiss) private var dismiss\n\n    @State private var selectedAccessType: AccessType = .everyone\n    @State private var customIP = " = " : database.userPassword\n        NSPasteboard.general.clearContents()\n        NSPasteboard.general.setString(pwd, forType: .string)\n    }\n}\n\n// MARK: - Remote Database Permissions Sheet\nstruct RemoteDatabasePermissionsSheet: View {\n    let databaseName: String\n    let username: String\n    @ObservedObject var connector: MySQLConnector\n    @Environment(\\.dismiss) private var dismiss\n\n    @State private var selectedAccessType: AccessType = .everyone\n    @State private var customIP = ";
"\n\n                // 跳过目录\n                var isDirectory: ObjCBool = false\n                guard fileManager.fileExists(atPath: filePath, isDirectory: &isDirectory),\n                      !isDirectory.boolValue else {\n                    continue\n                }\n\n                // 同步文件\n                let result = await syncFile(\n                    path: filePath,\n                    patterns: patterns,\n                    newPort: newPort\n                )\n\n                if result.success {\n                    updatedFiles.append(filePath)\n                } else {\n                    failedFiles.append(filePath)\n                    if let error = result.error {\n                        errors.append(error)\n                    }\n                }\n            }\n        } catch {\n            errors.append(" = "\n\n                // Skip directories\n                var isDirectory: ObjCBool = false\n                guard fileManager.fileExists(atPath: filePath, isDirectory: &isDirectory),\n                      !isDirectory.boolValue else {\n                    continue\n                }\n\n                // Synchronize file\n                let result = await syncFile(\n                    path: filePath,\n                    patterns: patterns,\n                    newPort: newPort\n                )\n\n                if result.success {\n                    updatedFiles.append(filePath)\n                } else {\n                    failedFiles.append(filePath)\n                    if let error = result.error {\n                        errors.append(error)\n                    }\n                }\n            }\n        } catch {\n            errors.append(";
")\n            }\n        } else {\n            // 不分节，直接生成\n            for item in items {\n                lines.append(generateConfigLine(item: item, commentPrefix: commentPrefix))\n            }\n        }\n\n        return lines.joined(separator: " = ")\n            }\n        } else {\n            // No sectioning, generate directly\n            for item in items {\n                lines.append(generateConfigLine(item: item, commentPrefix: commentPrefix))\n            }\n        }\n\n        return lines.joined(separator: ";
"更新" = "Update";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(20)\n        .frame(height: 60)\n    }\n\n    // 标题栏\n    private var headerBar: some View {\n        HStack {\n            Text(LocalizedStringKey(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding(20)\n        .frame(height: 60)\n    }\n\n    // Header bar\n    private var headerBar: some View {\n        HStack {\n            Text(LocalizedStringKey(";
" + nginxPattern\n            }\n\n            // 转换替换字符串中的变量\n            var nginxReplacement = replacement\n                .replacingOccurrences(of: " = " + nginxPattern\n            }\n\n            // Convert variables in the replacement string\n            var nginxReplacement = replacement\n                .replacingOccurrences(of: ";
"安装 \\(software.name) 失败" = "Failed to install \\(software.name)";
"\n    var isExpanded: Bool = false\n    var errorMessage: String?  // 错误信息\n    var errorDetail: String?   // 错误详情（brew 输出）\n\n    enum InstallPhase: String {\n        case preparing = " = "\n    var isExpanded: Bool = false\n    var errorMessage: String?  // Error message\n    var errorDetail: String?   // Error details (brew output)\n\n    enum InstallPhase: String {\n        case preparing = ";
", text: $commonName)\n                        .macInput()\n                }\n\n                // 组织名称\n                FormField(" = ", text: $commonName)\n                        .macInput()\n                }\n\n                // Organization name\n                FormField(";
")\n                        .font(.system(size: 11))\n                        .foregroundColor(.secondary)\n                    Text(config.parsed.documentRoot)\n                        .font(.system(size: 12))\n                        .foregroundColor(.secondary)\n                        .lineLimit(1)\n                }\n\n                // 第三行：冲突警告（如果有）\n                if config.hasConflict, let conflictServer = config.conflictWith {\n                    HStack(spacing: 6) {\n                        Image(systemName: " = ")\n                        .font(.system(size: 11))\n                        .foregroundColor(.secondary)\n                    Text(config.parsed.documentRoot)\n                        .font(.system(size: 12))\n                        .foregroundColor(.secondary)\n                        .lineLimit(1)\n                }\n\n                // Third row: Conflict warning (if any)\n                if config.hasConflict, let conflictServer = config.conflictWith {\n                    HStack(spacing: 6) {\n                        Image(systemName: ";
"),\n        ]\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 头部工具栏\n            HStack {\n                Text(" = "),\n        ]\n    }\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Header toolbar\n            HStack {\n                Text(";
"\n        }\n\n        // 使用 Homebrew 路径\n        if let homebrewPath = homebrewPath {\n            return " = "\n        }\n\n        // Use Homebrew path\n        if let homebrewPath = homebrewPath {\n            return ";
")\n                        .font(.system(size: 12))\n                        .foregroundColor(.white.opacity(0.8))\n                }\n            }\n            .padding(.vertical, 10)\n            .padding(.horizontal, 12)\n            .contentShape(Rectangle())\n            .background(\n                Group {\n                    if isSelected {\n                        RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                            .fill(\n                                LinearGradient(\n                                    colors: [item.color.opacity(0.8), item.color],\n                                    startPoint: .topLeading,\n                                    endPoint: .bottomTrailing\n                                )\n                            )\n                            .shadow(color: item.color.opacity(0.3), radius: 4, x: 0, y: 2)\n                    } else if isHovered {\n                        RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                            .fill(Color.gray.opacity(0.1))\n                    } else {\n                        Color.clear\n                    }\n                }\n            )\n        }\n        .buttonStyle(.plain)\n        .onHover { hovering in\n            isHovered = hovering\n        }\n    }\n}\n\nstruct ToolSubItemViewV2: View {\n    let tool: ToolShortcut\n    let isSelected: Bool\n    let action: () -> Void\n    @State private var isHovered = false\n    \n    var body: some View {\n        Button(action: action) {\n            HStack(spacing: 8) {\n                Image(systemName: tool.icon)\n                    .font(.system(size: 12))\n                Text(LocalizedStringKey(tool.title))\n                    .font(.system(size: 13))\n                Spacer()\n            }\n            .foregroundColor(isSelected ? .blue : .secondary)\n            .padding(.vertical, 8)\n            .padding(.horizontal, 14)\n            .contentShape(Rectangle())\n            .background(\n                Group {\n                    if isSelected {\n                        RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                            .fill(Color.blue.opacity(0.15))\n                    } else if isHovered {\n                        RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                            .fill(Color.gray.opacity(0.1))\n                    } else {\n                        Color.clear\n                    }\n                }\n            )\n        }\n        .buttonStyle(.plain)\n        .onHover { hovering in\n            isHovered = hovering\n        }\n    }\n}\n\n// 占位的 V2 主内容区\nstruct MainContentViewV2: View {\n    let selectedTab: SidebarItem\n    @Binding var selectedTool: ToolShortcut\n    \n    var body: some View {\n        VStack(spacing: 0) {\n            if selectedTab == .sites {\n                SitesListViewV2()\n            } else {\n                DetailView(selectedTab: selectedTab, selectedTool: $selectedTool)\n            }\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .glass(.panel)\n    }\n}\n\nstruct DouyinPopoverView: View {\n    @State private var isHovering = false\n    var remoteImage: NSImage?\n\n    var body: some View {\n        Group {\n            if let img = remoteImage {\n                Image(nsImage: img)\n                    .resizable()\n            } else {\n                Image(" = ")\n                        .font(.system(size: 12))\n                        .foregroundColor(.white.opacity(0.8))\n                }\n            }\n            .padding(.vertical, 10)\n            .padding(.horizontal, 12)\n            .contentShape(Rectangle())\n            .background(\n                Group {\n                    if isSelected {\n                        RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                            .fill(\n                                LinearGradient(\n                                    colors: [item.color.opacity(0.8), item.color],\n                                    startPoint: .topLeading,\n                                    endPoint: .bottomTrailing\n                                )\n                            )\n                            .shadow(color: item.color.opacity(0.3), radius: 4, x: 0, y: 2)\n                    } else if isHovered {\n                        RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                            .fill(Color.gray.opacity(0.1))\n                    } else {\n                        Color.clear\n                    }\n                }\n            )\n        }\n        .buttonStyle(.plain)\n        .onHover { hovering in\n            isHovered = hovering\n        }\n    }\n}\n\nstruct ToolSubItemViewV2: View {\n    let tool: ToolShortcut\n    let isSelected: Bool\n    let action: () -> Void\n    @State private var isHovered = false\n    \n    var body: some View {\n        Button(action: action) {\n            HStack(spacing: 8) {\n                Image(systemName: tool.icon)\n                    .font(.system(size: 12))\n                Text(LocalizedStringKey(tool.title))\n                    .font(.system(size: 13))\n                Spacer()\n            }\n            .foregroundColor(isSelected ? .blue : .secondary)\n            .padding(.vertical, 8)\n            .padding(.horizontal, 14)\n            .contentShape(Rectangle())\n            .background(\n                Group {\n                    if isSelected {\n                        RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                            .fill(Color.blue.opacity(0.15))\n                    } else if isHovered {\n                        RoundedRectangle(cornerRadius: AppTokens.Radius.medium)\n                            .fill(Color.gray.opacity(0.1))\n                    } else {\n                        Color.clear\n                    }\n                }\n            )\n        }\n        .buttonStyle(.plain)\n        .onHover { hovering in\n            isHovered = hovering\n        }\n    }\n}\n\n// Placeholder V2 main content area\nstruct MainContentViewV2: View {\n    let selectedTab: SidebarItem\n    @Binding var selectedTool: ToolShortcut\n    \n    var body: some View {\n        VStack(spacing: 0) {\n            if selectedTab == .sites {\n                SitesListViewV2()\n            } else {\n                DetailView(selectedTab: selectedTab, selectedTool: $selectedTool)\n            }\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .glass(.panel)\n    }\n}\n\nstruct DouyinPopoverView: View {\n    @State private var isHovering = false\n    var remoteImage: NSImage?\n\n    var body: some View {\n        Group {\n            if let img = remoteImage {\n                Image(nsImage: img)\n                    .resizable()\n            } else {\n                Image(";
"未找到匹配的日志文件: \\(pattern)" = "No matching log file found: \\(pattern)";
")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // 表单内容\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // 连接名称\n                FormField(" = ")\n                    .font(.title3)\n                    .foregroundColor(.secondary)\n            }\n            .buttonStyle(.plain)\n        }\n        .padding()\n    }\n\n    // Form content\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // Connection name\n                FormField(";
"\n        ),\n    ]\n\n    /// 获取配置字典\n    static func getConfigDict(for serviceName: String) -> [String: ConfigMeta] {\n        switch serviceName {\n        case " = "\n        ),\n    ]\n\n    /// Get configuration dictionary\n    static func getConfigDict(for serviceName: String) -> [String: ConfigMeta] {\n        switch serviceName {\n        case ";
", selection: $statusFilter) {\n                    ForEach(CertificateStatusFilter.allCases, id: \\.self) { filter in\n                        Text(LocalizedStringKey(filter.rawValue)).tag(filter)\n                    }\n                }\n                .pickerStyle(.segmented)\n                .labelsHidden()\n                .frame(width: 280)\n\n                Spacer()\n            }\n\n            // 证书列表 - 使用 Table 组件\n            if filteredCertificates.isEmpty {\n                // 空状态\n                VStack(spacing: 12) {\n                    Image(systemName: " = ", selection: $statusFilter) {\n                    ForEach(CertificateStatusFilter.allCases, id: \\.self) { filter in\n                        Text(LocalizedStringKey(filter.rawValue)).tag(filter)\n                    }\n                }\n                .pickerStyle(.segmented)\n                .labelsHidden()\n                .frame(width: 280)\n\n                Spacer()\n            }\n\n            // Certificate list - Using Table component\n            if filteredCertificates.isEmpty {\n                // Empty state\n                VStack(spacing: 12) {\n                    Image(systemName: ";
"备份旧版本失败: \\(error.localizedDescription)" = "Failed to back up old version: \\(error.localizedDescription)";
")\n                        .font(.system(size: 14, weight: .medium))\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            // 内容区域\n            if isLoading {\n                Spacer()\n                ProgressView(" = ")\n                        .font(.system(size: 14, weight: .medium))\n                        .foregroundColor(.secondary)\n                }\n                .buttonStyle(.plain)\n            }\n            .padding()\n\n            Divider()\n\n            // Content area\n            if isLoading {\n                Spacer()\n                ProgressView(";
"\n\n        if let url = URL(string: urlString) {\n            NSWorkspace.shared.open(url)\n        }\n    }\n\n    /// 获取默认的 phpMyAdmin URL（127.0.0.1）\n    func getDefaultPhpMyAdminURL() -> URL? {\n        let token = generateToken(forIP: " = "\n\n        if let url = URL(string: urlString) {\n            NSWorkspace.shared.open(url)\n        }\n    }\n\n    /// Get default phpMyAdmin URL (127.0.0.1)\n    func getDefaultPhpMyAdminURL() -> URL? {\n        let token = generateToken(forIP: ";
")\n        \n        // 如果目标目录已存在，先删除\n        if fileManager.fileExists(atPath: errorPageTargetDir) {\n            try fileManager.removeItem(atPath: errorPageTargetDir)\n        }\n        \n        // 复制目录\n        do {\n            try fileManager.copyItem(atPath: errorPageSourceDir, toPath: errorPageTargetDir)\n            print(" = ")\n        \n        // If target directory already exists, remove it first\n        if fileManager.fileExists(atPath: errorPageTargetDir) {\n            try fileManager.removeItem(atPath: errorPageTargetDir)\n        }\n        \n        // Copy directory\n        do {\n            try fileManager.copyItem(atPath: errorPageSourceDir, toPath: errorPageTargetDir)\n            print(";
"]\n\n    // 第三方 tap 配置\n    private let phpTap = " = "]\n\n    // Third-party tap configuration\n    private let phpTap = ";
"标签视为本地\n        }\n        return databaseManager.openedDatabaseTabs.first { $0.id == databaseManager.activeTabId }?.isLocal ?? true\n    }\n\n    // 可用的数据库版本\n    private let availableVersions = [\n        DatabaseVersion(version: " = "Treat tags as local\n        }\n        return databaseManager.openedDatabaseTabs.first { $0.id == databaseManager.activeTabId }?.isLocal ?? true\n    }\n\n    // Available database versions\n    private let availableVersions = [\n        DatabaseVersion(version: ";
")\n                                .font(.system(size: 11))\n                                .foregroundColor(.red)\n                        }\n                        .buttonStyle(.plain)\n                    }\n                } else {\n                    Circle()\n                        .fill(Color.orange)\n                        .frame(width: 8, height: 8)\n                }\n            }\n        }\n        .onHover { hovering in\n            isHovered = hovering\n        }\n    }\n}\n\n// MARK: - 本地数据库权限设置 Sheet\nstruct LocalDatabasePermissionsSheet: View {\n    let databaseName: String\n    let username: String\n    let host: String\n    let port: Int\n    let password: String\n    @Environment(\\.dismiss) private var dismiss\n\n    @State private var selectedAccessType: AccessType = .localhost\n    @State private var customIP = " = ")\n                                .font(.system(size: 11))\n                                .foregroundColor(.red)\n                        }\n                        .buttonStyle(.plain)\n                    }\n                } else {\n                    Circle()\n                        .fill(Color.orange)\n                        .frame(width: 8, height: 8)\n                }\n            }\n        }\n        .onHover { hovering in\n            isHovered = hovering\n        }\n    }\n}\n\n// MARK: - Local database permissions settings sheet\nstruct LocalDatabasePermissionsSheet: View {\n    let databaseName: String\n    let username: String\n    let host: String\n    let port: Int\n    let password: String\n    @Environment(\\.dismiss) private var dismiss\n\n    @State private var selectedAccessType: AccessType = .localhost\n    @State private var customIP = ";
"当前版本暂不支持自动创建数据库，请先创建站点后手动配置。" = "Automatic database creation is not currently supported in this version. Please create a site first and then configure the database manually.";
" ? Color.white.opacity(0.2) : AppColors.border, lineWidth: 1)\n                                )\n                        }\n                        .buttonStyle(.plain)\n                    }\n\n                    // 权限状态提示（固定高度避免布局跳动）\n                    HStack(spacing: 4) {\n                        if !siteDirectory.isEmpty {\n                            if directoryPermissionStatus.exists && directoryPermissionStatus.writable {\n                                Image(systemName: " = " ? Color.white.opacity(0.2) : AppColors.border, lineWidth: 1)\n                                )\n                        }\n                        .buttonStyle(.plain)\n                    }\n\n                    // Permission status hint (fixed height to prevent layout jumping)\n                    HStack(spacing: 4) {\n                        if !siteDirectory.isEmpty {\n                            if directoryPermissionStatus.exists && directoryPermissionStatus.writable {\n                                Image(systemName: ";
"保存备注失败: \\(error)" = "Failed to save note: \\(error)";
")\n        }\n        let oldEntry = entries[targetIndex]\n\n        // 排除自身后的冲突检查\n        let otherEntries = entries.filter { $0.id != entryId }\n        if otherEntries.contains(where: { $0.hostname == hostname && $0.ip != ip }) {\n            throw HostsError.conflict(" = ")\n        }\n        let oldEntry = entries[targetIndex]\n\n        // Conflict check excluding self\n        let otherEntries = entries.filter { $0.id != entryId }\n        if otherEntries.contains(where: { $0.hostname == hostname && $0.ip != ip }) {\n            throw HostsError.conflict(";
"  // 数据库位置\n    var note: String = " = "  // Database location\n    var note: String = ";
"豆包" = "Doubao";
"\n    @State private var errorMessage: String?\n    @State private var showError = false\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // 头部工具栏\n            HStack {\n                Text(" = "\n    @State private var errorMessage: String?\n    @State private var showError = false\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Header toolbar\n            HStack {\n                Text(";
"\n\n        if fileManager.fileExists(atPath: fullPath) {\n            // 检查是否有 .php 文件\n            if let contents = try? fileManager.contentsOfDirectory(atPath: fullPath),\n               contents.contains(where: { $0.hasSuffix(" = "\n\n        if fileManager.fileExists(atPath: fullPath) {\n            // Check for .php files\n            if let contents = try? fileManager.contentsOfDirectory(atPath: fullPath),\n               contents.contains(where: { $0.hasSuffix(";
",\n                    name\n                ]\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // 删除数据库\n    private func deleteDatabase(_ name: String) {\n        let alert = NSAlert()\n        alert.messageText = " = ",\n                    name\n                ]\n\n                do {\n                    try process.run()\n                    process.waitUntilExit()\n                    continuation.resume(returning: process.terminationStatus == 0)\n                } catch {\n                    continuation.resume(returning: false)\n                }\n            }\n        }\n    }\n\n    // Delete database\n    private func deleteDatabase(_ name: String) {\n        let alert = NSAlert()\n        alert.messageText = ";
") && inMysqldSection {\n                mysqldSectionEndIndex = i - 1\n                inMysqldSection = false\n            }\n\n            // 查找并更新配置项（同时检查注释行和非注释行）\n            var lineToCheck = trimmed\n            var isCommented = false\n\n            // 检查是否是注释行\n            if trimmed.hasPrefix(commentPrefix) {\n                isCommented = true\n                lineToCheck = String(trimmed.dropFirst(commentPrefix.count)).trimmingCharacters(in: .whitespaces)\n            }\n\n            if lineToCheck.contains(" = ") && inMysqldSection {\n                mysqldSectionEndIndex = i - 1\n                inMysqldSection = false\n            }\n\n            // Locate and update configuration item (checking both commented and uncommented lines)\n            var lineToCheck = trimmed\n            var isCommented = false\n\n            // Check if it's a comment line\n            if trimmed.hasPrefix(commentPrefix) {\n                isCommented = true\n                lineToCheck = String(trimmed.dropFirst(commentPrefix.count)).trimmingCharacters(in: .whitespaces)\n            }\n\n            if lineToCheck.contains(";
"Anthropic Messages (原生)" = "Anthropic Messages (原生)";
"OpenAI Chat Completions 兼容大多数中转与兼容服务。" = "OpenAI Chat Completions 兼容大多数中转与兼容服务。";
"Anthropic 原生接口使用 x-api-key + anthropic-version。" = "Anthropic 原生接口使用 x-api-key + anthropic-version。";
") {\n            return .anthropicMessages\n        }\n\n        return .openAIChatCompletions\n    }\n}\n\nstruct AIModelProvider: Identifiable, Codable, Equatable {\n    var id: UUID\n    var name: String\n    var apiUrl: String\n    var apiKey: String\n    var modelName: String\n    var isDefault: Bool\n    var apiFormat: AIAPIFormat\n\n    init(\n        id: UUID = UUID(),\n        name: String,\n        apiUrl: String,\n        apiKey: String,\n        modelName: String,\n        isDefault: Bool,\n        apiFormat: AIAPIFormat? = nil\n    ) {\n        self.id = id\n        self.name = name\n        self.apiUrl = apiUrl\n        self.apiKey = apiKey\n        self.modelName = modelName\n        self.isDefault = isDefault\n        self.apiFormat = apiFormat ?? AIAPIFormat.infer(from: apiUrl)\n    }\n\n    private enum CodingKeys: String, CodingKey {\n        case id\n        case name\n        case apiUrl\n        case apiKey\n        case modelName\n        case isDefault\n        case apiFormat\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()\n        name = try container.decode(String.self, forKey: .name)\n        apiUrl = try container.decode(String.self, forKey: .apiUrl)\n        apiKey = try container.decode(String.self, forKey: .apiKey)\n        modelName = try container.decode(String.self, forKey: .modelName)\n        isDefault = try container.decode(Bool.self, forKey: .isDefault)\n        apiFormat = try container.decodeIfPresent(AIAPIFormat.self, forKey: .apiFormat)\n            ?? AIAPIFormat.infer(from: apiUrl)\n    }\n    \n    static let predefinedProviders: [AIModelProvider] = [\n        // 国内大模型\n        AIModelProvider(name: " = ") {\n            return .anthropicMessages\n        }\n\n        return .openAIChatCompletions\n    }\n}\n\nstruct AIModelProvider: Identifiable, Codable, Equatable {\n    var id: UUID\n    var name: String\n    var apiUrl: String\n    var apiKey: String\n    var modelName: String\n    var isDefault: Bool\n    var apiFormat: AIAPIFormat\n\n    init(\n        id: UUID = UUID(),\n        name: String,\n        apiUrl: String,\n        apiKey: String,\n        modelName: String,\n        isDefault: Bool,\n        apiFormat: AIAPIFormat? = nil\n    ) {\n        self.id = id\n        self.name = name\n        self.apiUrl = apiUrl\n        self.apiKey = apiKey\n        self.modelName = modelName\n        self.isDefault = isDefault\n        self.apiFormat = apiFormat ?? AIAPIFormat.infer(from: apiUrl)\n    }\n\n    private enum CodingKeys: String, CodingKey {\n        case id\n        case name\n        case apiUrl\n        case apiKey\n        case modelName\n        case isDefault\n        case apiFormat\n    }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        id = try container.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()\n        name = try container.decode(String.self, forKey: .name)\n        apiUrl = try container.decode(String.self, forKey: .apiUrl)\n        apiKey = try container.decode(String.self, forKey: .apiKey)\n        modelName = try container.decode(String.self, forKey: .modelName)\n        isDefault = try container.decode(Bool.self, forKey: .isDefault)\n        apiFormat = try container.decodeIfPresent(AIAPIFormat.self, forKey: .apiFormat)\n            ?? AIAPIFormat.infer(from: apiUrl)\n    }\n    \n    static let predefinedProviders: [AIModelProvider] = [\n        // 国内大模型\n        AIModelProvider(name: ";
"示例: \\(provider.apiFormat.defaultAPIURL)" = "示例: \\(provider.apiFormat.defaultAPIURL)";
"API 格式:" = "API 格式:";
"格式:" = "格式:";
"默认地址" = "默认地址";
"Responses API 适合新模型接口，地址通常为 /v1/responses。" = "Responses API 适合新模型接口，地址通常为 /v1/responses。";
", isDefault: false, apiFormat: .openAIChatCompletions),\n        // 国外大模型\n        AIModelProvider(name: " = ", isDefault: false, apiFormat: .openAIChatCompletions),\n        // 国外大模型\n        AIModelProvider(name: ";