"目录不存在: %@" = "Diretório não existe: %@";
"目录不存在: %lld" = "Diretório não existe: %lld";
"新建站点" = "Criar um novo site";
"忽略错误继续导入" = "Ignorar erros e continuar importando";
"保存并重启" = "Salvar e reiniciar";
"%@ 不支持重新加载配置，将使用重启代替" = "%@ não suporta recarregar a configuração; será usado o reinício em vez disso";
"%lld 不支持重新加载配置，将使用重启代替" = "%lld não suporta recarregar a configuração; será usado o reinício em vez disso";
"字符串缓冲" = "Buffer de string";
"有效期" = "Período de validade";
"\n                    </FilesMatch>\n\n                    # 访问日志\n                    ErrorLog " = "</FilesMatch>\n\n                    #Log de acesso\n                    ErrorLog";
"删除选中" = "Remover selecionados";
"网络读取超时" = "Tempo limite de leitura de rede";
"纯静态" = "Totalmente estático";
"本地 MySQL" = "MySQL local";
"✓ 私钥文件存在" = "✓ Arquivo de chave privada existe";
"❌ 更新失败 %@ 个配置文件:" = "❌ Falha na atualização dos arquivos de configuração: %@";
"❌ 更新失败 %lld 个配置文件:" = "❌ Falha na atualização de %lld arquivos de configuração:";
"添加备注信息 (可选)..." = "Adicionar observações (opcional)...";
"请输入至少一个域名" = "Por favor, insira pelo menos um nome de domínio";
"端口冲突：与 \\(software.conflictingService ?? " = "Conflito de porta: com \\(software.conflictingService ??";
"密码保护" = "Proteção por senha";
"详情" = "Detalhes";
"保护模式" = "modo protegido";
"恢复失败: %@" = "Recuperação falhou: %@";
"恢复失败: %lld" = "Recuperação falhou: %lld";
"Socket 文件路径" = "Caminho do arquivo de soquete";
"# %@ 配置文件" = "# Arquivo de configuração de %@";
"# %lld 配置文件" = "# Arquivo de configuração de %lld";
"• 建议设置自动续期" = "• Recomenda-se configurar a renovação automática";
"无法启动站点：%@ Web 服务器未安装。请先安装 %@ 后再启动站点。" = "Não é possível iniciar o site: %@ O servidor web não está instalado. Instale %@ antes de iniciar o site.";
"无法启动站点：%lld Web 服务器未安装。请先安装 %lld 后再启动站点。" = "Não é possível iniciar o site: %lld O servidor web não está instalado. Instale %lld antes de iniciar o site.";
"用户数据库" = "banco de dados de usuários";
"用于签发本地开发证书" = "Usado para emitir certificados de desenvolvimento local";
"Nginx/Apache 规则互转" = "Conversão de regras do Nginx/Apache";
"两次输入的密码不一致" = "As senhas inseridas duas vezes são inconsistentes";
"确定要卸载 %@ 吗？此操作无法撤销。" = "Tem certeza de que deseja desinstalar %@? Esta ação não pode ser desfeita.";
"确定要卸载 %lld 吗？此操作无法撤销。" = "Tem certeza de que deseja desinstalar %lld? Esta ação não pode ser desfeita.";
"无法转换文本" = "Não é possível converter o texto";
"比memcache支持更多高级功能" = "Oferece recursos mais avançados do que o memcache";
"重加载" = "Recarregar";
"工作空间: 已初始化 (%@)" = "Área de trabalho: Inicializada (%@)";
"工作空间: 已初始化 (%lld)" = "Área de trabalho: Inicializada (%lld)";
"关于" = "Sobre";
"端口配置失败: %@" = "Falha na configuração da porta: %@";
"端口配置失败: %lld" = "Falha na configuração da porta: %lld";
"取消" = "Cancelar";
"扫描信息" = "Informações da verificação";
"Root密码已复制到剪贴板" = "Senha raiz copiada para a área de transferência";
"GD图形库" = "Biblioteca gráfica GD";
"Mac集成开发环境" = "Ambiente de desenvolvimento integrado para Mac";
"接管系统中已安装的 %@，使用 80/443 端口" = "Assumir o controle do %@ instalado no sistema, usando as portas 80/443";
"接管系统中已安装的 %lld，使用 80/443 端口" = "Assumir o controle do %lld instalado no sistema, usando as portas 80/443";
"设置默认站点功能正在开发中,敬请期待。\\n\\n此功能将允许您设置当访问 localhost 时显示的默认站点。" = "A função de definir o site padrão está em desenvolvimento; aguarde novidades. \\n\\nEssa funcionalidade permitirá que você defina o site padrão exibido ao acessar localhost.";
"允许URL包含" = "Permitir que as URLs contenham";
"会话最大生命周期" = "Tempo máximo de vida da sessão";
"生成证书失败" = "Falha ao gerar o certificado";
"访问量趋势" = "Tendências de tráfego";
"保存 Nginx 配置失败: %@" = "Falha ao salvar a configuração do Nginx: %@";
"保存 Nginx 配置失败: %lld" = "Falha ao salvar a configuração do Nginx: %lld";
"正在检查系统依赖..." = "Verificando dependências do sistema...";
"安装方法：" = "Método de instalação:";
"正在配置 Nginx..." = "Configurando o Nginx...";
"切割大小" = "tamanho do corte";
"错误日志" = "log de erros";
"证书管理" = "Gerenciamento de certificados";
"修改 hosts 文件需要管理员权限，保存时系统会提示输入密码" = "Modificar o arquivo hosts requer privilégios de administrador, e o sistema solicitará que você insira uma senha ao salvar.";
"端口号与当前端口相同" = "O número da porta é igual ao da porta atual";
"剩余" = "Restante";
"内存限制" = "limite de memória";
"高性能 Web 服务器" = "Servidor web de alto desempenho";
"数据库" = "banco de dados";
"状态说明: 未配置SSL证书" = "Descrição do status: certificado SSL não configurado";
"SQL注入攻击检测" = "Detecção de ataques de injeção SQL";
"Nginx 端口" = "Porta do Nginx";
"查询缓存大小" = "Tamanho do cache de consultas";
"自定义" = "Personalizar";
"工作空间路径" = "caminho do workspace";
"显示密码" = "mostrar senha";
"哈希计算工具" = "Ferramenta de cálculo de hash";
"访客 IP 地址和地理位置" = "Endereço IP do visitante e localização geográfica";
"错误率" = "taxa de erro";
"默认MIME类型" = "Tipo MIME padrão";
"内存淘汰策略" = "Estratégia de eliminação de memória";
"• 安装到用户目录，无需 sudo 权限" = "• Instalar no diretório do usuário sem permissões sudo";
"未检测到已安装的 Web 服务器（Nginx 或 Apache）。请先安装至少一个 Web 服务器后再创建站点。" = "O servidor web instalado (Nginx ou Apache) não foi detectado. Por favor, instale pelo menos um servidor web antes de criar um site.";
"允许URL文件打开" = "Permitir que arquivos de URL sejam abertos";
"共存模式" = "modo de coexistência";
"自签名证书" = "certificado autoassinado";
"域名不能为空" = "O nome de domínio não pode estar vazio";
"避免权限问题" = "Evitar problemas de permissão";
"文档型数据库" = "banco de dados de documentos";
"连接队列大小" = "tamanho da fila de conexões";
"⚠️ 错误信息:" = "⚠️ Mensagem de erro:";
"原始模式" = "modo original";
"提示：本地数据库仅支持修改密码和备注。如需修改端口等配置，请在“高级设置”中调整配置文件。" = "Dica: O banco de dados local só suporta alteração de senhas e comentários. Se você precisar modificar configurações como portas, ajuste o arquivo de configuração nas \"Configurações Avançadas\".";
"推荐值" = "Valor recomendado";
"日期时间" = "data e hora";
"执行失败: %@" = "Falha na execução: %@";
"执行失败: %lld" = "Falha na execução: %lld";
"%@ 日志" = "log de %@";
"%lld 日志" = "%lld log";
"重新扫描" = "Escanear novamente";
"✅ Nginx 反向代理配置文件已生成: %@" = "✅ O arquivo de configuração do proxy reverso Nginx foi gerado: %@";
"✅ Nginx 反向代理配置文件已生成: %lld" = "✅ O arquivo de configuração do proxy reverso Nginx foi gerado: %lld";
"✅ 使用 mkcert 生成证书" = "✅ Usar mkcert para gerar certificados";
"证书有效但连接失败" = "O certificado é válido, mas a conexão falha";
"Socket通信支持" = "Suporte à comunicação via socket";
"1. 请使用浏览器下载安装包" = "1. Baixe o pacote de instalação usando seu navegador";
"同步二进制日志" = "Sincronizar logs binários";
"正在重启 Nginx..." = "Reiniciando o Nginx...";
"IO 容量" = "Capacidade de E/S";
"50x Server Error (服务器错误)" = "Erro do servidor 50x";
"版本" = "Versão";
"未找到 Homebrew，无法重启服务" = "Homebrew não encontrado; o serviço não pode ser reiniciado";
"过期时间: %@)" = "Data de expiração: %@)";
"过期时间: %lld)" = "Data de expiração: %lld)";
"更新 Nginx 反向代理配置中的 Apache 后端端口" = "Atualizar a porta de backend do Apache na configuração do proxy reverso Nginx";
"MySQL版本" = "Versão do MySQL";
"✅ Apache 配置文件已生成: %@" = "✅ O arquivo de configuração do Apache foi gerado: %@";
"✅ Apache 配置文件已生成: %lld" = "✅ O arquivo de configuração do Apache foi gerado: %lld";
"验证方式" = "Método de verificação";
"准备安装 %@..." = "Preparando para instalar %@...";
"准备安装 %lld..." = "Preparando para instalar %lld...";
"✅ 解析成功: \\(parsed.domains.joined(separator: " = "✅ Análise bem-sucedida: \\(parsed.domains.joined(separator:";
"恢复失败" = "Recuperação falhou";
"TCP优化" = "Otimização TCP";
"备份成功" = "Backup realizado com sucesso";
"无法打开根目录" = "Não é possível abrir o diretório raiz";
"复制成功" = "Copiado com sucesso";
"修改配置后，需重启数据库生效" = "Após modificar a configuração, você precisa reiniciar o banco de dados para que as alterações entrem em vigor.";
"验证时间戳" = "Verificar carimbo de data/hora";
"已导出到: %@" = "Exportado para: %@";
"已导出到: %lld" = "Exportado para: %lld";
"暂无日志内容" = "Ainda não há conteúdo de log";
"安装后需要手动创建数据库和用户表" = "Após a instalação, você precisa criar manualmente o banco de dados e as tabelas de usuários";
"IP地址" = "Endereço IP";
"剩余天数: %@)" = "Número de dias restantes: %@)";
"剩余天数: %lld)" = "Número de dias restantes: %lld)";
"域名 %@ 已指向不同的 IP 地址" = "O nome de domínio %@ já aponta para um endereço IP diferente";
"域名 %lld 已指向不同的 IP 地址" = "O nome de domínio %lld já aponta para um endereço IP diferente";
"端口 %@" = "Porta %@";
"端口 %lld" = "Porta %lld";
"提示:" = "Dicas:";
"正在备份配置文件..." = "Fazendo backup dos arquivos de configuração...";
"收起详情" = "Ocultar detalhes";
"默认站点" = "site padrão";
"过期日期" = "Data de expiração";
"查看日志" = "Visualizar log";
"使用 Homebrew 安装：" = "Instalar usando o Homebrew:";
"CA证书路径" = "Caminho do certificado CA";
"文件路径" = "caminho do arquivo";
"打开日志按钮" = "Botão para abrir o log";
"复制密码" = "Copiar senha";
"自定义..." = "Personalizar...";
"✓ 已复制错误页面目录到: %@" = "✓ O diretório das páginas de erro foi copiado para: %@";
"✓ 已复制错误页面目录到: %lld" = "✓ O diretório das páginas de erro foi copiado para: %lld";
"复制详情" = "Copiar detalhes";
"目录权限正常" = "As permissões do diretório estão normais";
"站点名称不能为空" = "O nome do site não pode estar vazio";
"专用服务器模式" = "Modo de servidor dedicado";
"加载令牌失败: %@" = "Falha ao carregar o token: %@";
"加载令牌失败: %lld" = "Falha ao carregar o token: %lld";
"排序缓冲区" = "buffer de classificação";
"证书链" = "cadeia de certificados";
"查看详情" = "Ver detalhes";
"恢复" = "restaurar";
"保存原始内容" = "Salvar conteúdo original";
"建议：" = "Sugestões:";
"加密解密工具" = "Ferramentas de criptografia e descriptografia";
"强制重置密码失败，请检查 MySQL 服务状态" = "Redefinição forçada de senha falhou. Verifique o status do serviço MySQL.";
"配置已保存，但重载 Apache 失败" = "Configuração salva, mas a recarga do Apache falhou";
"重试" = "Tentar novamente";
"安全扫描失败: %@" = "Verificação de segurança falhou: %@";
"安全扫描失败: %lld" = "Verificação de segurança falhou: %lld";
"详细统计" = "Estatísticas detalhadas";
"推荐" = "Recomendado";
"网络写入超时" = "Tempo limite de gravação na rede";
"单连接最大请求数" = "Número máximo de solicitações para uma única conexão";
"清除所有日志" = "Limpar todos os logs";
"Keychain 状态" = "Status da chaveiro";
"✗ 私钥文件不存在: %@" = "✗ O arquivo da chave privada não existe: %@";
"✗ 私钥文件不存在: %lld" = "✗ O arquivo da chave privada não existe: %lld";
"正在验证安装..." = "Verificando a instalação...";
"概览统计" = "Estatísticas gerais";
"刷新权限" = "Atualizar permissões";
"确定要删除数据库「%@」吗？此操作不可恢复！" = "Tem certeza de que deseja excluir o banco de dados \"%@\"? Esta operação é irreversível!";
"确定要删除数据库「%lld」吗？此操作不可恢复！" = "Tem certeza de que deseja excluir o banco de dados \"%lld\"? Esta operação é irreversível!";
"平均响应时间" = "tempo médio de resposta";
"日志缓冲大小" = "Tamanho do buffer de log";
"正在安装..." = "Instalando...";
"保存令牌失败: %@" = "Falha ao salvar o token: %@";
"保存令牌失败: %lld" = "Falha ao salvar o token: %lld";
"自动修复生成" = "Geração automática de reparo";
"建议使用 9000-9999 范围内的端口" = "Recomenda-se usar portas na faixa de 9000 a 9999";
"端口 %@ 已被占用" = "A porta %@ já está ocupada";
"端口 %lld 已被占用" = "A porta %lld já está ocupada";
"端口冲突" = "Conflito de porta";
"SMTP服务器" = "Servidor SMTP";
"修改 Root 密码" = "Alterar senha do usuário root";
"稍后设置" = "Configurar posteriormente";
"更新文件失败 %@: %@" = "Falha ao atualizar o arquivo %@: %@";
"更新文件失败 %lld: %lld" = "Falha ao atualizar o arquivo %lld: %lld";
"InnoDB缓冲区大小" = "Tamanho do buffer do InnoDB";
"域名无法访问" = "O nome de domínio não pode ser acessado";
"启动 %@ 失败" = "Falha ao iniciar %@";
"启动 %lld 失败" = "Falha ao iniciar %lld";
"远程" = "remoto";
"禁用Nagle算法" = "Desativar o algoritmo de Nagle";
"白名单 (仅允许)" = "Lista de permissões (permitir apenas)";
"默认端口" = "Porta padrão";
"编辑" = "Editar";
"扫描子目录失败: %@" = "Falha ao analisar o subdiretório: %@";
"扫描子目录失败: %lld" = "Falha ao analisar o subdiretório: %lld";
"自定义端口" = "Porta personalizada";
"开放目录限制" = "Restrições de abertura de diretório";
"已过期" = "Expirado";
"攻击来源 IP (Top 100)" = "IP de origem do ataque (Top 100)";
"错误日志最大长度" = "Comprimento máximo do log de erros";
"访客来源" = "origem do visitante";
"手动安装" = "Instalação manual";
"✓ 创建临时测试文件" = "✓ Criar arquivos de teste temporários";
"监听端口" = "porta de escuta";
"续期证书" = "Renovar certificado";
"删除" = "Excluir";
"• 密码已更改但未记录" = "• Senha alterada, mas não foi feito login";
"⚠️ 端口 %@ 已被占用" = "⚠️ A porta %@ já está ocupada";
"⚠️ 端口 %lld 已被占用" = "⚠️ A porta %lld já está ocupada";
"未找到匹配的端口" = "Nenhuma porta correspondente encontrada";
"最大连接错误数" = "Número máximo de erros de conexão";
"可用版本" = "Versões disponíveis";
"✓ 证书文件存在" = "✓ Arquivo de certificado existe";
"输出控制" = "Controle de saída";
"日志文件" = "arquivo de log";
"已自动填充" = "Preenchido automaticamente";
"SQL注入" = "Injeção SQL";
"跨域配置" = "Configuração de domínio cruzado";
"单文件最大大小" = "Tamanho máximo de um único arquivo";
"常见端口" = "Portas comuns";
"未找到 Redis 配置文件" = "Arquivo de configuração do Redis não encontrado";
"请再次输入密码" = "Por favor, insira a senha novamente";
"未找到 MySQL 客户端，请先安装 MySQL" = "Cliente MySQL não encontrado, instale o MySQL primeiro";
"记录服务器错误和警告信息" = "Registrar mensagens de erro e aviso do servidor de log";
"生成加密密码失败" = "Falha ao gerar a senha criptografada";
"• Root 密码不是常见密码" = "• A senha de root não é uma senha comum";
"批量导入" = "Importação em lote";
"日志过期秒数" = "Tempo de expiração dos logs (em segundos)";
"✗ %@ 未配置" = "✗ %@ não configurado";
"✗ %lld 未配置" = "✗ %lld não configurado";
"Hosts解析" = "Análise de hosts";
"输入源规则后点击转换按钮" = "Após inserir as regras de origem, clique no botão Converter";
"返回" = "Voltar";
"站点统计 - %@" = "Estatísticas do site - %@";
"站点统计 - %lld" = "Estatísticas do site - %lld";
"线程缓存大小" = "Tamanho do cache de threads";
"✓ 证书有效" = "✓ O certificado é válido";
"正在恢复配置..." = "Restaurando configuração...";
"最受欢迎页面" = "Páginas mais populares";
"\n        主机: %@\n        端口: %@\n        用户名: root\n        密码: %@\n        " = "Host: %@\n        Porta: %@\n        Nome de usuário: root\n        Senha: %@";
"\n        主机: %lld\n        端口: %lld\n        用户名: root\n        密码: %lld\n        " = "Host: %lld\n        Porta: %lld\n        Nome de usuário: root\n        Senha: %lld";
"扫描目录:" = "Diretório de verificação:";
"证书文件缺失" = "Arquivo de certificado ausente";
"正在安装 %@..." = "Instalando %@...";
"正在安装 %lld..." = "Instalando %lld...";
"高级配置" = "Configuração avançada";
"分布式内存缓存" = "Cache de memória distribuída";
"每天切割一次" = "Cortar uma vez por dia";
"用户名" = "Nome de usuário";
"未找到匹配的数据库" = "Nenhum banco de dados correspondente encontrado";
"用户" = "Usuário";
"PHP-FPM 端口" = "Porta do PHP-FPM";
"无Web服务" = "Nenhum serviço web";
"指定路径不是目录" = "O caminho especificado não é um diretório";
"错误报告级别" = "Nível de relatório de erros";
"创建网站" = "Criar site";
"添加Vary头" = "Adicionar cabeçalho Vary";
"主配置: %@ (普通文件)" = "Configuração principal: %@ (arquivo normal)";
"主配置: %lld (普通文件)" = "Configuração principal: %lld (arquivo normal)";
"请选择站点" = "Selecione um site";
"新端口配置" = "Nova configuração de porta";
"正在检测 IP 地址..." = "Verificando o endereço IP...";
"修复成功" = "Reparo bem-sucedido";
"配置文件不存在: %@" = "Arquivo de configuração não existe: %@";
"配置文件不存在: %lld" = "Arquivo de configuração não existe: %lld";
"无配置" = "Nenhuma configuração";
"需人工输入" = "Requer entrada manual";
"重启失败: %@" = "Falha ao reiniciar: %@";
"重启失败: %lld" = "Falha ao reiniciar: %lld";
"配置文件语法错误" = "Erro de sintaxe no arquivo de configuração";
"内存键值存储" = "Armazenamento em memória de pares chave-valor";
"修改远程数据库连接信息" = "Modificar as informações de conexão com o banco de dados remoto";
"⚠ 证书即将过期" = "⚠ O certificado está prestes a expirar";
"强制 HTTPS" = "Forçar HTTPS";
"重启失败" = "Falha ao reiniciar";
"安装 %@ 版本" = "Instalar versão %@";
"安装 %lld 版本" = "Instalar versão %lld";
"启动中" = "Iniciando";
"慢查询日志" = "Log de consultas lentas";
"修改密码" = "Alterar senha";
"• 强制重置将停止 MySQL 服务并以安全模式重启" = "• A reinicialização forçada interromperá o serviço MySQL e o reiniciará no modo seguro";
"站点配置修复" = "Correções na configuração do site";
"输入文本" = "Digite o texto";
"保存注释" = "Salvar anotação";
"证书状态:" = "Status do certificado:";
"数据处理" = "Processamento de dados";
"导出" = "Exportar";
"暂不支持该软件的配置" = "A configuração deste software atualmente não é suportada";
"InnoDB 配置" = "Configuração do InnoDB";
"警告" = "aviso";
"保存证书数据失败: %@" = "Falha ao salvar os dados do certificado: %@";
"保存证书数据失败: %lld" = "Falha ao salvar os dados do certificado: %lld";
"无法创建网络连接" = "Não foi possível criar uma conexão de rede";
"JSON/PHP/JS/CSS/HTML 格式化" = "Formatação JSON/PHP/JS/CSS/HTML";
"操作: %@" = "Operação: %@";
"操作: %lld" = "Operação: %lld";
"状态说明" = "Descrição do status";
"正在加载配置..." = "Carregando configuração...";
"秒" = "segundos";
"失败请求" = "requisição falhou";
"PHP版本" = "Versão do PHP";
"❌ 扫描配置目录失败: %@" = "❌ Falha ao escanear o diretório de configuração: %@";
"❌ 扫描配置目录失败: %lld" = "❌ Falha ao escanear o diretório de configuração: %lld";
"确定要从备份 %@ 恢复数据库吗？当前数据将被覆盖！" = "Tem certeza de que deseja restaurar o banco de dados do backup %@? Os dados atuais serão sobrescritos!";
"确定要从备份 %lld 恢复数据库吗？当前数据将被覆盖！" = "Tem certeza de que deseja restaurar o banco de dados do backup %lld? Os dados atuais serão sobrescritos!";
"修改后点击保存，原配置文件将自动备份" = "Após a modificação, clique em Salvar e o arquivo de configuração original será automaticamente copiado como backup.";
"站点信息" = "Informações do site";
"PHP 版本不匹配" = "Incompatibilidade de versão do PHP";
"PHP 版本" = "Versão do PHP";
"运行状态" = "Status de execução";
"配置已保存，正在重启..." = "Configuração salva, reiniciando...";
"检测中..." = "Em detecção...";
"默认认证插件" = "Plugin de autenticação padrão";
"✓ 第三方源添加成功\\n" = "✓ Fonte de terceiros adicionada com sucesso\\n";
"数据库名" = "Nome do banco de dados";
"权限" = "Permissões";
"最近7天" = "Últimos 7 dias";
"日志切割方式" = "Método de divisão de logs";
"访问被拒绝，请检查用户名和密码" = "Acesso negado. Verifique o nome de usuário e a senha.";
"表单模式" = "Modo de formulário";
"在 Finder 中显示" = "Mostrar no Finder";
"管理员名称" = "Nome do administrador";
"\n\n        // 5. 执行证书申请\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: " = "// 5. Executar a solicitação de certificado\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath:";
"记录错误日志" = "Registrar log de erro";
"有效" = "válido";
"回滚前自动备份" = "Backup automático antes do rollback";
"找到 %@ 个候选站点" = "Encontrados %@ sites candidatos";
"找到 %lld 个候选站点" = "Encontrados %lld sites candidatos";
"使用 MySQL 数据库存储用户信息，适合多用户管理和大型站点" = "Usar o banco de dados MySQL para armazenar informações de usuário, adequado para gerenciamento multiusuário e sites grandes";
"端口 %@ 已被运行中的 %@ 占用" = "A porta %@ já está ocupada pelo processo em execução %@";
"端口 %lld 已被运行中的 %lld 占用" = "A porta %lld já está ocupada pelo processo em execução %lld";
"修改 my.cnf 配置文件，将端口改为 3307" = "Modifique o arquivo de configuração my.cnf e altere a porta para 3307";
"网站实际访问的目录，限制为网站目录下的一级子目录" = "O diretório realmente acessado pelo site é limitado ao subdiretório de primeiro nível dentro do diretório do site.";
"网站运行正常，没有错误记录" = "O site está funcionando normalmente, sem erros registrados";
"SQL 模式" = "Modo SQL";
"修改端口" = "Modificar porta";
"\n            throw LetsEncryptError.certificateIssueFailed(message: output)\n        }\n\n        // 6. 安装证书到指定目录\n        let certPath = " = "throw LetsEncryptError.certificateIssueFailed(message: output)\n        }\n\n        // 6. Instalar o certificado no diretório especificado\n        let certPath =";
"现代加密库" = "Biblioteca de criptografia moderna";
"添加数据库" = "Adicionar banco de dados";
"导入前清空数据库" = "Limpar o banco de dados antes da importação";
"正在测试 PHP 运行..." = "Testando a execução do PHP...";
"创建数据库失败: \\(dbResult.error ?? " = "Falha ao criar o banco de dados: \\(dbResult.error ??";
"停止 Nginx 失败" = "Falha ao interromper o Nginx";
"确认删除" = "Confirmar exclusão";
"编辑后保持原管理状态" = "Manter o status original de gerenciamento após a edição";
"连接配置" = "Configuração de conexão";
"为 PHP %@ 配置新端口，不影响现有服务" = "Configure uma nova porta para o PHP %@ sem afetar os serviços existentes";
"为 PHP %lld 配置新端口，不影响现有服务" = "Configure uma nova porta para o PHP %lld sem afetar os serviços existentes";
"SOAP协议支持" = "Suporte ao protocolo SOAP";
"等待超时" = "Tempo limite de espera";
"请输入域名或备注" = "Por favor, insira um nome de domínio ou observações";
"数据目录" = "diretório de dados";
"设置数据库权限" = "Definir permissões do banco de dados";
"绑定域名" = "Associar nome de domínio";
"未知的认证响应: %@" = "Resposta de autenticação desconhecida: %@";
"未知的认证响应: %lld" = "Resposta de autenticação desconhecida: %lld";
"保存配置失败: %@" = "Falha ao salvar a configuração: %@";
"保存配置失败: %lld" = "Falha ao salvar a configuração: %lld";
"当前端口" = "porta atual";
"暴露PHP版本" = "Expor versão do PHP";
"TRACE 方法" = "Método TRACE";
"错误信息:" = "Mensagem de erro:";
"当前端口: %@)" = "Porta atual: %@)";
"当前端口: %lld)" = "Porta atual: %lld)";
"到期时间" = "Tempo de expiração";
"禁用类" = "Classe desabilitada";
"请先设置服务工作空间" = "Por favor, configure primeiro o espaço de trabalho do serviço";
"全部软件" = "Todos os softwares";
"\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        " = "#Configuração do Nginx para phpMyAdmin\n        # Gerado automaticamente pelo JihePHPEnv\n        #Hora da geração: %@.string(from: Date()))\n\n        location /phpmyadmin {\n            alias %@;\n            index index.php;\n\n            # Restrições de acesso 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            # Negar acesso a arquivos sensíveis\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        " = "#Configuração do Nginx para phpMyAdmin\n        # Gerado automaticamente pelo JihePHPEnv\n        #Hora da geração: %lld.string(from: Date()))\n\n        location /phpmyadmin {\n            alias %lld;\n            index index.php;\n\n            # Restrições de acesso 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            # Negar acesso a arquivos sensíveis\n            location ~ /\\. {\n                deny all;\n            }\n        }";
"MongoDB驱动" = "Driver do MongoDB";
"请先安装 FTP 服务" = "Por favor, instale primeiro o serviço FTP";
"统一查看服务日志" = "Visualizar logs dos serviços de forma unificada";
"一键安装" = "Instalação em um clique";
"若可用内存小于1G，可能会安装不上" = "Se a memória disponível for inferior a 1 GB, talvez não seja possível instalá-lo.";
"✅ .htpasswd 文件已生成: %@" = "✅ Arquivo .htpasswd gerado com sucesso: %@";
"✅ .htpasswd 文件已生成: %lld" = "✅ Arquivo .htpasswd gerado com sucesso: %lld";
"❌ 加载证书数据失败: %@" = "❌ Falha ao carregar os dados do certificado: %@";
"❌ 加载证书数据失败: %lld" = "❌ Falha ao carregar os dados do certificado: %lld";
"网站不存在页" = "Página do site não existe";
"[DependencyManager] 找到内置 mkcert: %@" = "[DependencyManager] Encontrado mkcert embutido: %@";
"[DependencyManager] 找到内置 mkcert: %lld" = "[DependencyManager] Encontrado mkcert embutido: %lld";
"最大数据包" = "Pacote de dados máximo";
"新建网站默认页面" = "Criar uma nova página inicial do site";
"正在获取状态..." = "Obtendo status...";
"欢迎使用" = "Bem-vindo";
"需要安装 GoAccess 才能使用日志分析功能" = "O GoAccess precisa estar instalado para usar a função de análise de logs";
"删除选中项 (%@)" = "Remover itens selecionados (%@)";
"删除选中项 (%lld)" = "Remover itens selecionados (%lld)";
"请输入要格式化的代码" = "Por favor, insira o código a ser formatado";
")) 匹配的站点目录。请手动创建 Apache 配置或删除孤立的 Nginx 代理配置。" = ")) Diretórios de sites correspondentes. Crie manualmente a configuração do Apache ou exclua a configuração de proxy do Nginx órfã.";
"无法转换解密后的数据" = "Não foi possível converter os dados descriptografados";
"随机生成" = "gerado aleatoriamente";
"无法连接到服务器，请检查域名是否解析到正确的IP" = "Não foi possível conectar-se ao servidor; verifique se o nome de domínio está resolvendo para o IP correto";
"Hosts文件" = "Arquivo hosts";
"原始模式支持直接编辑 /etc/hosts 全部内容。" = "O modo bruto permite a edição direta de todo o conteúdo do arquivo /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        " = "# Arquivo de configuração do Pure-FTPd\n        # Gerado automaticamente por um ambiente PHP\n\n        # Configuração 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        # Configuração TLS\n        TLS 1\n\n        # Faixa de portas para modo passivo\n        PassivePortRange 30000 50000";
"保存设置" = "Salvar configurações";
"无法创建测试文件" = "Não foi possível criar o arquivo de teste";
"手动调整各项配置" = "Ajustar manualmente várias configurações";
"重新加载" = "recarregar";
"其他配置" = "Outras configurações";
"重启所有" = "Reiniciar todos";
"生成报告失败" = "Falha ao gerar o relatório";
"• 生成后浏览器将自动信任由该 CA 签发的证书" = "• Assim que gerado, o navegador confiará automaticamente no certificado emitido por esta AC";
"访客趋势" = "Tendências de visitantes";
"锁等待超时" = "Tempo limite de espera para bloqueio";
"严格模式" = "modo estrito";
"请选择要查看的日志文件" = "Selecione um arquivo de log para visualizar";
"未知进程" = "processo desconhecido";
"批量设置" = "Configurações em lote";
"开始之前，请先设置服务工作空间" = "Antes de começar, configure seu espaço de trabalho de serviço";
"文件类型检测扩展" = "Extensão de detecção de tipo de arquivo";
"待安装" = "A ser instalado";
"所有版本已安装" = "Todas as versões instaladas";
"扫描路径:" = "Caminho da verificação:";
"正在检查 acme.sh 安装状态..." = "Verificando o status da instalação do acme.sh...";
"管理" = "gerenciamento";
"\n                # HTTP VirtualHost\n                # Apache 后端端口：%@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ?? " = "#HTTPVirtualHost\n                # Porta de backend do Apache: %@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ??";
"\n                # HTTP VirtualHost\n                # Apache 后端端口：%lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ?? " = "#HTTPVirtualHost\n                # Porta de backend do Apache: %lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ??";
"PHP 版本:" = "Versão do PHP:";
"会话名称" = "nome da sessão";
"上次修改: %@)" = "Última modificação: %@)";
"上次修改: %lld)" = "Última modificação: %lld)";
"修复配置" = "Corrigir configuração";
"列表模式" = "modo de lista";
"全部端口" = "Todas as portas";
">\n                        Options Indexes FollowSymLinks\n                        AllowOverride All\n                        Require all granted\n                    </Directory>\n\n                    # PHP-FPM 配置\n                    <FilesMatch \\\\.php$>\n                        SetHandler " = ">\n                        Opções Indexes FollowSymLinks\n                        AllowOverride All\n                        Require all granted\n                    </Directory>\n\n                    #Configuração do PHP-FPM\n                    <FilesMatch \\\\.php$>\n                        SetHandler";
"显示正常站点" = "Exibir site normal";
"acme.sh 是申请 Let's Encrypt 证书的必需工具" = "O acme.sh é uma ferramenta necessária para solicitar um certificado Let's Encrypt";
"欢迎使用 及和PHP环境" = "Bem-vindo ao ambiente e PHP";
"数据库名称" = "Nome do banco de dados";
"已搜索以下路径:" = "Os seguintes caminhos foram pesquisados:";
"⚠ 版本不匹配，期望: %@" = "⚠ Incompatibilidade de versão, versão esperada: %@";
"⚠ 版本不匹配，期望: %lld" = "⚠ Incompatibilidade de versão, versão esperada: %lld";
"同步配置" = "Sincronizar configuração";
"✅ 已恢复配置: %@" = "✅ Configuração restaurada: %@";
"✅ 已恢复配置: %lld" = "✅ Configuração restaurada: %lld";
"自定义日志分析" = "Análise personalizada de logs";
"已安装版本" = "Versão instalada";
"IP 地址" = "Endereço IP";
"JSON 格式错误: %@" = "Erro de formato JSON: %@";
"JSON 格式错误: %lld" = "Erro de formato JSON: %lld";
"扫描时间: %@)" = "Tempo de verificação: %@)";
"扫描时间: %lld)" = "Tempo de verificação: %lld)";
"• 大文件导入可能需要较长时间，请耐心等待" = "• A importação de arquivos grandes pode levar muito tempo; por favor, tenha paciência.";
"请输入数据库名" = "Por favor, insira o nome do banco de dados";
"禁用函数" = "Desativar função";
"配置 PHP-FPM 端口失败: %@" = "Falha ao configurar a porta do PHP-FPM: %@";
"配置 PHP-FPM 端口失败: %lld" = "Falha ao configurar a porta do PHP-FPM: %lld";
"运行 'lsof -i :%@' 查看端口占用详情" = "Execute 'lsof -i :%@' para visualizar os detalhes de ocupação da porta";
"运行 'lsof -i :%lld' 查看端口占用详情" = "Execute 'lsof -i :%lld' para visualizar os detalhes de ocupação da porta";
"缓存器" = "buffer";
"5 分钟" = "5 minutos";
"%@ 重启失败 (退出码: %@)" = "%@ Reinicialização falhou (Código de saída: %@)";
"%lld 重启失败 (退出码: %lld)" = "%lld Reinicialização falhou (Código de saída: %lld)";
"独立文件" = "arquivo independente";
"证书类型" = "Tipo de certificado";
"✅ Nginx 配置文件已生成: %@" = "✅ Arquivo de configuração do Nginx foi gerado: %@";
"✅ Nginx 配置文件已生成: %lld" = "✅ Arquivo de configuração do Nginx foi gerado: %lld";
"最活跃时段" = "período mais ativo";
"伪静态" = "estático pseudo";
"配置 Pure-FTPd..." = "Configurando o Pure-FTPd...";
"测试中..." = "Testando...";
"更改日志路径" = "Alterar caminho do log";
"❌ 解析证书失败: %@" = "❌ Falha ao analisar o certificado: %@";
"❌ 解析证书失败: %lld" = "❌ Falha ao analisar o certificado: %lld";
"正在分析日志..." = "Analisando logs...";
"目录扫描和探测行为" = "Comportamento de varredura e sondagem de diretórios";
"点击\\\"签发证书\\\"按钮创建新的站点证书" = "Clique no botão \"Emitir Certificado\" para criar um novo certificado de site";
"未找到要删除的远程数据库连接" = "A conexão remota com o banco de dados a ser excluída não foi encontrada";
"生效日期" = "Data de vigência";
"无效的 URL" = "URL inválida";
"打开可执行文件位置" = "Abrir localização do arquivo executável";
"搜索软件名称..." = "Pesquisar nome do software...";
"请求体缓冲区" = "Buffer do corpo da solicitação";
"写入 Hosts 文件" = "Gravar no arquivo Hosts";
"检测到括号不匹配，已尽力格式化。请检查左侧波浪红线标注位置。" = "Foi detectado um desajuste de colchetes; foi aplicada uma formatação com o melhor esforço possível. Verifique a posição marcada com a linha vermelha ondulada à esquerda.";
"最大空闲进程" = "Processo ocioso máximo";
"显示 %@ 个结果" = "Exibindo %@ resultados";
"显示 %lld 个结果" = "Exibindo %lld resultados";
"导出配置" = "Exportar configuração";
"当前步骤:" = "Etapas atuais:";
"复制到 Navicat" = "Copiar para o Navicat";
"有备份" = "Tem backup";
"软件管理" = "Gerenciamento de software";
"异常退出" = "Saída anormal";
"自签名" = "assinado por si mesmo";
"启动" = "iniciar";
"请输入密码" = "Por favor, insira a senha";
"设置后可避免系统目录权限问题，便于统一管理和备份。" = "Após a configuração, é possível evitar problemas de permissão no diretório do sistema e facilitar o gerenciamento unificado e o backup.";
"• 开源的 ACME 协议客户端" = "• Cliente de protocolo ACME de código aberto";
"配置 Apache 失败" = "Falha ao configurar o Apache";
"• 支持 .log 和 .txt 文件" = "• Suporta arquivos .log e .txt";
"数据库端口" = "Porta do banco de dados";
"✅ 已备份旧配置: %@" = "✅ A configuração antiga foi salva em backup: %@";
"✅ 已备份旧配置: %lld" = "✅ A configuração antiga foi salva em backup: %lld";
"Nginx FastCGI 连接端口（localhost）" = "Porta de conexão FastCGI do Nginx (localhost)";
"错误日志路径" = "Caminho do arquivo de log de erros";
"\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            # 🔄 Configuração de proxy reverso do Nginx (backend Apache)\n            # Esta configuração encaminha solicitações para o servidor backend Apache\n            # Porta frontal: 80 (Nginx)\n            # Porta 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            # 🔄 Configuração de proxy reverso do Nginx (backend Apache)\n            # Esta configuração encaminha solicitações para o servidor backend Apache\n            # Porta frontal: 80 (Nginx)\n            # Porta 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            }";
"有(%@)" = "Sim (%@)";
"有(%lld)" = "Sim (%lld)";
"脚本缓存器" = "cache de script";
"使用 %@" = "Usar %@";
"使用 %lld" = "Usar %lld";
"错误 %@: %@" = "Erro %@: %@";
"错误 %lld: %lld" = "Erro %lld: %lld";
"自動" = "automático";
"路径缓存大小" = "Tamanho do cache de caminho";
"请输入要计算哈希值的文本" = "Insira o texto para o qual o valor hash deve ser calculado";
"系统监控" = "Monitoramento do sistema";
"单进程最大连接数" = "Número máximo de conexões por processo";
"HTTP 配置" = "Configuração HTTP";
"证书已过期" = "O certificado expirou";
"选择导出目录" = "Selecionar diretório de exportação";
"历史备份" = "Backup histórico";
"Cookie生命周期" = "Ciclo de vida do cookie";
"端口详情已复制到剪贴板" = "Detalhes da porta copiados para a área de transferência";
"Gzip 压缩" = "Compactação Gzip";
"fastcgi_pass 需要在 Apache 中配置 mod_proxy_fcgi" = "fastcgi_pass requer o módulo mod_proxy_fcgi configurado no Apache";
"确定要删除备份 %@ 吗？" = "Você tem certeza de que deseja excluir o backup %@?";
"确定要删除备份 %lld 吗？" = "Você tem certeza de que deseja excluir o backup %lld?";
"⚠️ 证书目录不存在: %@" = "⚠️ O diretório de certificados não existe: %@";
"⚠️ 证书目录不存在: %lld" = "⚠️ O diretório de certificados não existe: %lld";
"事件模型" = "modelo de evento";
"端口已更新为 %@，相关配置已同步" = "A porta foi atualizada para %@ e a configuração relacionada foi sincronizada";
"端口已更新为 %lld，相关配置已同步" = "A porta foi atualizada para %lld e a configuração relacionada foi sincronizada";
"• 修复所有站点：批量强力修复所有站点的配置文件" = "• Reparar todos os sites: Repara poderosamente os arquivos de configuração de todos os sites em lote";
"证书尚未生效" = "O certificado ainda não é válido";
"恢复配置失败" = "Falha ao restaurar a configuração";
"错误信息" = "mensagem de erro";
"创建运行目录失败: %@\\n请检查路径权限或修改运行目录" = "Falha ao criar o diretório de execução: %@\\nVerifique as permissões do caminho ou modifique o diretório de execução";
"创建运行目录失败: %lld\\n请检查路径权限或修改运行目录" = "Falha ao criar o diretório de execução: %lld\\nVerifique as permissões do caminho ou modifique o diretório de execução";
"读取目录失败: %@" = "Falha ao ler o diretório: %@";
"读取目录失败: %lld" = "Falha ao ler o diretório: %lld";
"mysql/{版本}/" = "mysql/{version}/";
"配置文件未找到" = "Arquivo de configuração não encontrado";
"站点配置已导出到: %@" = "Configuração do site exportada para: %@";
"站点配置已导出到: %lld" = "Configuração do site exportada para: %lld";
"IP限制" = "Restrições de IP";
"信息" = "informação";
"字节" = "bytes";
"读取日志文件失败: %@" = "Falha ao ler o arquivo de log: %@";
"读取日志文件失败: %lld" = "Falha ao ler o arquivo de log: %lld";
"安装 %@" = "Instalar %@";
"安装 %lld" = "Instalar %lld";
"点击备份" = "Clique no backup";
"选择站点" = "Selecionar site";
"3. 确认网站服务正在运行" = "3. Confirme que o serviço do site está em execução";
"YAML解析支持" = "Suporte à análise YAML";
"默认 PHP:" = "PHP padrão:";
"已检测到" = "Detectado";
"天" = "dia";
"正在扫描目录..." = "Analisando diretório...";
"TLS设置" = "Configurações TLS";
"需确认" = "Necessário confirmar";
"确认批量删除" = "Confirmar exclusão em lote";
"表数量" = "Número de tabelas";
"根目录不能为空" = "O diretório raiz não pode estar vazio";
"数据库位置" = "Localização do banco de dados";
"这可能需要几秒钟，请耐心等待" = "Isso pode levar alguns segundos, por favor, tenha paciência";
"统一管理" = "Gerenciamento unificado";
"设置完成" = "Configuração concluída";
"IP 列表" = "Lista de IPs";
"进程名称" = "nome do processo";
"设为默认" = "Definir como padrão";
"开始导入" = "Iniciar importação";
"证书即将过期（%@天）" = "O certificado está prestes a expirar (%@ dias)";
"证书即将过期（%lld天）" = "O certificado está prestes a expirar (%lld dias)";
"正在加载 PHP %@ 扩展..." = "Carregando extensão PHP %@...";
"正在加载 PHP %lld 扩展..." = "Carregando extensão PHP %lld...";
"数据库已成功导入" = "O banco de dados foi importado com sucesso";
"服务启动失败，请检查配置文件" = "Falha ao iniciar o serviço; verifique o arquivo de configuração";
"# 由及和PHP环境自动生成" = "# Gerado automaticamente pelo ambiente PHP";
"连接信息" = "informações de conexão";
"数据库 %@ 已删除" = "Banco de dados %@ excluído";
"数据库 %lld 已删除" = "Banco de dados %lld excluído";
"SMTP端口" = "Porta SMTP";
"已禁用" = "Desativado";
"服务器备注" = "Observações do servidor";
"配置已保存" = "Configuração salva";
"刷新方法" = "Método de atualização";
"• 证书到期前 30 天将自动提醒续期" = "• Lembrete automático para renovação 30 dias antes do vencimento do certificado";
"以下参数不在系统配置表中，可能存在风险" = "Os seguintes parâmetros não estão na tabela de configuração do sistema e podem ser arriscados";
"修改远程数据库密码" = "Alterar senha do banco de dados remoto";
"• 支持MySQL5.5、MariaDB10.1及以上版本" = "• Suporta MySQL 5.5, MariaDB 10.1 e versões superiores";
"停止中" = "Parando";
"分析内容包括：" = "A análise inclui:";
"✓ 已复制默认首页到: %@" = "✓ Página inicial padrão copiada para: %@";
"✓ 已复制默认首页到: %lld" = "✓ Página inicial padrão copiada para: %lld";
"管理员密码" = "Senha do administrador";
"批量停止" = "Parada em lote";
"Homebrew 未安装。请访问 https://brew.sh 安装 Homebrew" = "O Homebrew não está instalado. Visite https://brew.sh para instalar o 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            # Configuração básica\n            porta=3306\n            bind-address = 127.0.0.1\n            character-set-server = utf8mb4\n            collation-server = utf8mb4_general_ci\n\n            # Configuração de conexão\n            max_connections = 151\n            wait_timeout = 28800\n\n            # Configuração do 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";
"所有必要端口正常监听" = "Todas as portas necessárias estão sendo monitoradas normalmente";
"应用到选中项" = "Aplicar aos itens selecionados";
"配置文件信息:\\n%@" = "Informações do arquivo de configuração:\n%@";
"配置文件信息:\\n%lld" = "Informações do arquivo de configuração:\n%lld";
"未找到 mysql 客户端" = "cliente mysql não encontrado";
"自动滚动" = "rolagem automática";
"端口状态" = "status da porta";
"未检测到密码" = "Senha não detectada";
"HTTPS 访问" = "Acesso HTTPS";
"导入失败" = "Falha na importação";
"MySQL 重启成功" = "MySQL reiniciado com sucesso";
"MD5/SHA 哈希计算" = "Cálculo de hash MD5/SHA";
"浅色" = "cor da luz";
"应用内未找到 phpMyAdmin" = "phpMyAdmin não encontrado no aplicativo";
"功能未实现" = "Função não implementada";
"加密算法" = "algoritmo de criptografia";
"接管模式" = "Modo de tomada de controle";
"更新 Web 服务器配置中的 PHP-FPM 连接端口" = "Atualizar a porta de conexão do PHP-FPM na configuração do servidor web";
"检测 Root 密码" = "Detectar senha de administrador (root)";
"确定要删除数据库 %@ 吗？此操作不可恢复。" = "Tem certeza de que deseja excluir o banco de dados %@? Esta operação é irreversível.";
"确定要删除数据库 %lld 吗？此操作不可恢复。" = "Tem certeza de que deseja excluir o banco de dados %lld? Esta operação é irreversível.";
"请输入要加密的文本" = "Insira o texto a ser criptografado";
"SSL 架构模式:" = "Padrão de arquitetura SSL:";
"连接失败: %@" = "Falha na conexão: %@";
"连接失败: %lld" = "Falha na conexão: %lld";
"密码不能为空" = "A senha não pode estar vazia";
"更换PHP版本失败" = "Falha ao alterar a versão do PHP";
"显示主窗口" = "Exibir janela principal";
"域名无法解析，请检查DNS设置" = "O nome de domínio não pôde ser resolvido; verifique as configurações de DNS";
"路径不是目录" = "o caminho não é um diretório";
"✓ 端口 %@ 正在监听" = "✓ A porta %@ está escutando";
"✓ 端口 %lld 正在监听" = "✓ A porta %lld está escutando";
"导入数据库失败" = "Falha ao importar o banco de dados";
"加载中..." = "Carregando...";
"加密失败: %@" = "Falha na criptografia: %@";
"加密失败: %lld" = "Falha na criptografia: %lld";
"管理员邮箱" = "E-mail do administrador";
"允许本地导入" = "Permitir importação local";
"自定义文件..." = "Arquivos personalizados...";
"工作进程数" = "Número de processos de trabalho";
"附加功能" = "Recursos adicionais";
"欢迎使用及和PHP环境" = "Bem-vindo ao ambiente PHP";
"使用原路径" = "Usar caminho original";
"Base64/URL/HTML 编码" = "Codificação Base64/URL/HTML";
"清空扫描日志" = "Limpar log de verificação";
"独立表空间" = "tablespace independente";
"备注" = "Observações";
"压缩" = "Compactação";
"最大输入时间" = "Tempo máximo de entrada";
"主机 (% 表示任意主机)" = "Host (\"%\" significa qualquer host)";
"显示常用配置项" = "Exibir itens de configuração comumente utilizados";
"导出站点配置" = "Exportar configuração do site";
"内存配置" = "Configuração de memória";
"生效时间: %@)" = "Tempo de validade: %@";
"生效时间: %lld)" = "Tempo de validade: %lld)";
"文本哈希计算" = "Cálculo de hash de texto";
"日志文件不存在: %@\\n\\n提示：请确认服务已安装并运行过。" = "O arquivo de log não existe: %@\\n\\nAviso: Confirme se o serviço foi instalado e está em execução.";
"日志文件不存在: %lld\\n\\n提示：请确认服务已安装并运行过。" = "O arquivo de log não existe: %lld\\n\\nAviso: Confirme se o serviço foi instalado e está em execução.";
"失败" = "falhou";
"\n        【基本信息】\n        服务名称: %@\n        端口号: %@\n        状态: %@\n\n        【进程信息】\n        进程 PID: %@\n        进程名称: %@\n        父进程 PID: %@\n        运行用户: %@\n        启动时间: %@\n\n        【路径信息】\n        可执行文件: %@\n        工作目录: %@\n\n        【命令行】\n        %@\n        " = "【Informações básicas】\n        Nome do serviço: %@\n        Número da porta: %@\n        Status: %@\n\n        [Informações do processo]\n        PID do processo: %@\n        Nome do processo: %@\n        PID do processo pai: %@\n        Usuário em execução: %@\n        Hora de início: %@\n\n        【Informações de caminho】\n        Arquivo executável: %@\n        Diretório de trabalho: %@\n\n        【Linha de comando】\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        " = "【Informações básicas】\n        Nome do serviço: %lld\n        Número da porta: %lld\n        Status: %lld\n\n        [Informações do processo]\n        PID do processo: %lld\n        Nome do processo: %lld\n        PID do processo pai: %lld\n        Usuário em execução: %lld\n        Hora de início: %lld\n\n        【Informações de caminho】\n        Arquivo executável: %lld\n        Diretório de trabalho: %lld\n\n        【Linha de comando】\n        %lld";
"MySQL默认Socket" = "Socket padrão do MySQL";
"系统临时目录" = "Diretório temporário do sistema";
"版本 %@" = "Versão %@";
"版本 %lld" = "Versão %lld";
"正在扫描端口..." = "Verificando portas...";
"一次接受多个连接" = "Aceitar múltiplas conexões simultaneamente";
"无法连接到远程数据库" = "Não foi possível conectar ao banco de dados remoto";
"强制GTID一致性" = "Impor consistência de GTID";
"输入用户名" = "Digite o nome de usuário";
"404 Not Found (未找到)" = "404 Não Encontrado";
"脚本解密" = "Descriptografia de script";
"申请SSL证书" = "Solicitar um certificado SSL";
"准备安装 Apache..." = "Preparando para instalar o Apache...";
"正在检测端口占用情况..." = "Verificando a ocupação da porta...";
"Nginx和Apache都使用SSL（用于特殊需求）" = "Tanto o Nginx quanto o Apache usam SSL (para necessidades especiais)";
"已修复 %@ 个站点的配置文件" = "Arquivos de configuração corrigidos para os sites %@";
"已修复 %lld 个站点的配置文件" = "Arquivos de configuração corrigidos para %lld sites";
"本地 MySQL 数据库" = "Banco de dados MySQL local";
") 使用相同端口" = ") usam a mesma porta";
"站点不存在" = "O site não existe";
"超过保留天数的日志文件将被自动删除" = "Arquivos de log que excederem o número de dias de retenção serão excluídos automaticamente";
"扫描状态:" = "Status da verificação:";
"2025-01-01 至 2035-01-01" = "01/01/2025 a 01/01/2035";
"配置检测:\\n%@" = "Verificação de configuração:\\n%@";
"配置检测:\\n%lld" = "Verificação de configuração:\\n%lld";
"MySQL改进版扩展" = "Extensão aprimorada do MySQL";
"用户名不能为空" = "O nome de usuário não pode estar vazio";
"添加至" = "adicionar a";
"\n        # 转换自 Nginx 规则\n        # 注意：部分规则可能需要手动调整\n\n        <IfModule mod_rewrite.c>\n            RewriteEngine On\n\n        " = "#Convertido das regras do Nginx\n        # Observação: algumas regras podem precisar ser ajustadas manualmente\n\n        <IfModule mod_rewrite.c>\n            RewriteEngineOn";
"转换警告" = "aviso de conversão";
"启用" = "habilitar";
"Redo日志容量" = "Capacidade do log de redos";
"停止 %@ 失败" = "Falha ao interromper %@";
"停止 %lld 失败" = "Falha ao interromper %lld";
"导出成功" = "Exportação bem-sucedida";
"日志查看" = "Visualização de logs";
"请求路径" = "Caminho da solicitação";
"签发成功" = "Emitido com sucesso";
"未找到要更新的数据库连接" = "Conexão com o banco de dados para atualização não encontrada";
"排序缓冲大小" = "Tamanho do buffer de classificação";
"连接成功，MySQL版本: %@" = "Conexão bem-sucedida, versão do MySQL: %@";
"连接成功，MySQL版本: %lld" = "Conexão bem-sucedida, versão do MySQL: %lld";
"即将过期" = "Expira em breve";
"主配置: %@ -> %@ (软链接)" = "Configuração principal: %@ -> %@ (link simbólico)";
"主配置: %lld -> %lld (软链接)" = "Configuração principal: %lld -> %lld (link simbólico)";
"颁发者" = "emissor";
"加载日志失败: %@" = "Falha ao carregar o log: %@";
"加载日志失败: %lld" = "Falha ao carregar o log: %lld";
"日志设置" = "Configurações de log";
"源规则" = "regras de origem";
"手动停止" = "parada manual";
"加载数据库数据失败，且无法恢复备份" = "Falha ao carregar os dados do banco de dados e não é possível restaurar o backup";
"PHP 脚本语言" = "Linguagem de script PHP";
"⚠️ 工作空间未初始化,跳过证书扫描" = "⚠️ O workspace não está inicializado; a verificação de certificados foi ignorada";
"申请 Let's Encrypt 证书" = "Solicitar um certificado Let's Encrypt";
"无效响应" = "Resposta inválida";
"修改密码失败，请检查 MySQL 连接" = "Falha ao alterar a senha; verifique a conexão com o MySQL";
"访客数量和请求趋势分析" = "Análise da tendência de número de visitantes e solicitações";
"安装中" = "Instalando";
"   - 文档根目录: %@" = "- Diretório raiz do documento: %@";
"   - 文档根目录: %lld" = "- Diretório raiz do documento: %lld";
"更新于: %@)" = "Atualizado em: %@)";
"更新于: %lld)" = "Atualizado em: %lld)";
"正在验证..." = "Verificando...";
"访问权限" = "Direitos de acesso";
"备份跳过" = "Backup ignorado";
"选择连接" = "Selecionar conexão";
"检查更新" = "Verificar atualizações";
"配置" = "Configuração";
"哈希计算" = "Cálculo de hash";
"检测到 %@ 配置文件位于原始路径：\\n%@\\n\\n是否将其迁移到工作空间？\\n目标路径：%@" = "Arquivo de configuração %@ detectado no caminho original:\\n%@\\n\\nDeseja migrá-lo para o espaço de trabalho?\\nCaminho de destino: %@";
"检测到 %lld 配置文件位于原始路径：\\n%lld\\n\\n是否将其迁移到工作空间？\\n目标路径：%lld" = "Arquivo de configuração %lld detectado no caminho original:\n%lld\n\nDeseja migrá-lo para o espaço de trabalho?\nCaminho de destino: %lld";
"最近一年" = "ano passado";
"新密码" = "nova senha";
"修复说明" = "Instruções de reparo";
"✗ 复制默认首页失败: %@" = "✗ Falha ao copiar a página inicial padrão: %@";
"✗ 复制默认首页失败: %lld" = "✗ Falha ao copiar a página inicial padrão: %lld";
"站点管理设置" = "Configurações de gerenciamento de sites";
"端口管理" = "Gerenciamento de portas";
"创建 FTP 账号" = "Criar conta FTP";
"Zlib输出压缩" = "Compactação de saída Zlib";
"警告: _errorpage_ 目录不存在: %@，跳过复制错误页面" = "Aviso: o diretório _errorpage_ não existe: %@, cópia da página de erro ignorada";
"警告: _errorpage_ 目录不存在: %lld，跳过复制错误页面" = "Aviso: o diretório _errorpage_ não existe: %lld, cópia da página de erro ignorada";
"• Apache Combined 格式" = "• Formato combinado do Apache";
"证书信息" = "Informações do certificado";
"默认站点主目录" = "Diretório inicial padrão do site";
"保存失败" = "Falha ao salvar";
"未找到 mysqldump 工具" = "Ferramenta mysqldump não encontrada";
"自定义路径" = "caminho personalizado";
"读取加密密码失败" = "Falha ao ler a senha criptografada";
"复制端口" = "Copiar porta";
"批量禁用" = "Desabilitar em lote";
"路径" = "caminho";
"✗ PHP 测试失败" = "✗ Falha no teste do PHP";
"brew 输出:\\n%@" = "Saída do brew:\n%@";
"brew 输出:\\n%lld" = "Saída do brew:\n%lld";
"未知错误" = "erro desconhecido";
"配置文件已迁移到工作空间" = "Os arquivos de configuração foram migrados para o espaço de trabalho";
"搜索数据库名或备注" = "Pesquisar nome do banco de dados ou anotações";
"网站管理" = "Gerenciamento de sites";
"配置文件格式无效: %@" = "Formato inválido do arquivo de configuração: %@";
"配置文件格式无效: %lld" = "Formato inválido do arquivo de configuração: %lld";
"打开目录" = "Abrir diretório";
"检测到冲突：该域名在 %@ 中也有配置" = "Conflito detectado: o nome de domínio também está configurado em %@";
"检测到冲突：该域名在 %lld 中也有配置" = "Conflito detectado: o nome de domínio também está configurado em %lld";
"数据库权限已更新" = "Permissões do banco de dados atualizadas";
"同时上传文件数" = "Número de arquivos enviados simultaneamente";
"工作空间: 未初始化（使用 Homebrew 默认配置）" = "Área de trabalho: Não inicializada (usando a configuração padrão do Homebrew)";
"强制保存" = "Forçar salvamento";
"Let's Encrypt 证书" = "Certificado Let's Encrypt";
"AES 加密解密工具" = "Ferramenta de criptografia e descriptografia AES";
"规则差异对比" = "Comparação das diferenças nas regras";
"# 请手动卸载" = "# Desinstale manualmente, por favor";
"未选择" = "Não selecionado";
"确认删除数据库" = "Confirmar exclusão do banco de dados";
"打开终端" = "Abrir terminal";
"MySQL 版本: %@" = "Versão do MySQL: %@";
"MySQL 版本: %lld" = "Versão do MySQL: %lld";
"启用默认站点" = "Habilitar site padrão";
"有效期（年）" = "Período de validade (ano)";
"转换后规则" = "Regras pós-conversão";
"安装 Nginx 失败" = "Falha ao instalar o Nginx";
"强力修复" = "Reparo avançado";
"父目录没有写权限，请修改路径" = "O diretório pai não possui permissão de gravação; modifique o caminho, por favor.";
"站点证书" = "Certificado do site";
"删除成功" = "Excluído com sucesso";
"服务器地址不能为空" = "O endereço do servidor não pode estar vazio";
"生成" = "gerar";
"二进制日志缓存大小" = "Tamanho do cache de logs binários";
"确定要彻底删除站点 %@ 吗？此操作不可恢复。" = "Tem certeza de que deseja excluir completamente o site %@? Esta operação é irreversível.";
"确定要彻底删除站点 %lld 吗？此操作不可恢复。" = "Tem certeza de que deseja excluir completamente o site %lld? Esta operação é irreversível.";
"带宽使用" = "Uso de largura de banda";
"最大缓存文件数" = "Número máximo de arquivos em cache";
"容量未计算" = "Capacidade não calculada";
"总体统计" = "Estatísticas gerais";
"• 注意1：请确保本服务器有访问数据库的权限" = "• Observação 1: Certifique-se de que este servidor tenha permissão para acessar o banco de dados";
"记录无索引查询" = "Consulta de log sem índice";
"错误日志级别" = "Nível de erro do log";
"• 强力修复：根据站点信息重新生成配置文件（不依赖历史记录）" = "• Reparação poderosa: gere novamente os arquivos de configuração com base nas informações do site (não depende do histórico)";
"卸载 %@ 失败" = "Falha ao desinstalar %@";
"卸载 %lld 失败" = "Falha ao desinstalar %lld";
"卸载中" = "Desinstalando";
"错误" = "Erro";
"点击应用建议值" = "Clique em Aplicar Valores Sugeridos";
"域名 %@ 无法访问：%@" = "O nome de domínio %@ não pode ser acessado: %@";
"域名 %lld 无法访问：%lld" = "O nome de domínio %lld não pode ser acessado: %lld";
"高性能Excel读写" = "Leitura e gravação de Excel de alto desempenho";
"正在切换 PHP 版本..." = "Alterando a versão do PHP...";
"发送数据失败" = "Falha ao enviar os dados";
"SSL 证书签发与管理" = "Emissão e gerenciamento de certificados SSL";
"最常访问的页面和资源" = "Páginas e recursos mais visitados";
"修改数据库密码" = "Alterar senha do banco de dados";
"请求头 (Headers)" = "Cabeçalhos da solicitação";
"自启动设置" = "Configurações de inicialização automática";
"您选择的PHP %@ 当前已停止运行。\\n\\n使用已停止的PHP版本可能导致网站无法正常运行PHP程序。\\n\\n建议先启动该PHP版本，或选择其他运行中的版本。" = "A versão do PHP selecionada (%@) está atualmente parada. \\n\\nUsar uma versão descontinuada do PHP pode impedir que o site execute programas PHP corretamente. \\n\\nRecomenda-se iniciar primeiro essa versão do PHP ou selecionar outra versão em execução.";
"您选择的PHP %lld 当前已停止运行。\\n\\n使用已停止的PHP版本可能导致网站无法正常运行PHP程序。\\n\\n建议先启动该PHP版本，或选择其他运行中的版本。" = "A versão do PHP selecionada (%lld) está atualmente parada. \\n\\nUsar uma versão descontinuada do PHP pode impedir que o site execute programas PHP corretamente. \\n\\nRecomenda-se iniciar primeiro essa versão do PHP ou selecionar outra versão em execução.";
"软件未安装" = "Software não instalado";
"请稍候，正在创建站点配置..." = "Aguarde, a configuração do site está sendo criada...";
"启动失败" = "Falha na inicialização";
"已安装" = "Instalado";
"• 重置过程中数据库将暂时不可用" = "• O banco de dados ficará temporariamente indisponível durante o processo de redefinição";
"设置成功" = "Configuração concluída com sucesso";
"选择 \\(serverType == .nginx ? " = "Selecionar \\(serverType == .nginx ?";
"关于及和PHP环境" = "Sobre o ambiente PHP Jihe";
"⚠ 证书尚未生效" = "⚠ O certificado ainda não é válido";
"删除数据库失败" = "Falha ao excluir o banco de dados";
"重要配置" = "Configuração importante";
"资源限制" = "Limites de recursos";
"检查所有表" = "Verificar todas as tabelas";
"安装完成" = "Instalação concluída";
"转换完成，建议测试后使用" = "Conversão concluída; recomenda-se utilizá-la após os testes";
"分析报告" = "relatório de análise";
"已信任" = "Confiável";
"✅ 扫描到 %@ 个证书文件" = "✅ Verificou %@ arquivos de certificado";
"✅ 扫描到 %lld 个证书文件" = "✅ Verificou %lld arquivos de certificado";
"配置端口失败" = "Falha ao configurar a porta";
"共 %@ 条记录" = "Total de %@ registros";
"共 %lld 条记录" = "Total de %lld registros";
"• 密码长度建议至少8位，包含字母和数字" = "• Recomenda-se que a senha tenha pelo menos 8 caracteres, incluindo letras e números";
"Nginx处理SSL，转发HTTP到Apache（推荐）" = "O Nginx gerencia o SSL e encaminha o HTTP para o Apache (recomendado)";
"按年" = "por ano";
"生成反向代理配置前自动备份" = "Backup automático antes da geração da configuração de proxy reverso";
"暂无可用站点，请先创建站点" = "Nenhum site disponível ainda; crie um site primeiro";
"[DependencyManager] mkcert CA 安装成功" = "[DependencyManager] CA do mkcert instalada com sucesso";
"无效的URL：%@" = "URL inválida: %@";
"无效的URL：%lld" = "URL inválido: %lld";
"所有主机" = "Todos os hosts";
"暂无错误日志" = "Ainda não há nenhum log de erro";
"选择本地安装包" = "Selecionar pacote de instalação local";
"正在扫描配置文件..." = "Analisando arquivos de configuração...";
"未找到 Apache 配置文件" = "Arquivo de configuração do Apache não encontrado";
"不切割日志文件" = "Não dividir os arquivos de log";
"加密" = "Criptografia";
"没有运行的 Web 服务器，且默认目录不存在: %@" = "Não há nenhum servidor web em execução e o diretório padrão não existe: %@";
"没有运行的 Web 服务器，且默认目录不存在: %lld" = "Não há nenhum servidor web em execução e o diretório padrão não existe: %lld";
"PHP 扩展" = "Extensão PHP";
"认证失败: %@" = "Falha na autenticação: %@";
"认证失败: %lld" = "Falha na autenticação: %lld";
"显示回收站" = "Exibir lixeira";
"表缓存" = "cache de tabelas";
"修改默认页面功能正在开发中,敬请期待。\\n\\n此功能将允许您自定义 Nginx/Apache 的默认欢迎页面。" = "A função para modificar a página padrão está em desenvolvimento; fique atento. \\n\\nEssa funcionalidade permitirá personalizar a página inicial padrão do Nginx/Apache.";
"端口号" = "número da porta";
"认证失败" = "Falha na autenticação";
"本地 CA 已初始化" = "CA local inicializada";
"总请求数" = "Total de solicitações";
"无法在根目录下创建目录，请使用用户目录" = "Não é possível criar diretório no diretório raiz; use o diretório do usuário";
"MacEnv 主题" = "Tema MacEnv";
"无法读取 hosts 文件" = "Não é possível ler o arquivo hosts";
"监听中" = "Monitoramento";
"修改 redis.conf 配置文件，将端口改为 6380" = "Modifique o arquivo de configuração redis.conf e altere a porta para 6380";
"指定ip" = "Especificar IP";
"无法添加 %@，请检查网络连接" = "Não é possível adicionar %@; verifique sua conexão de rede";
"无法添加 %lld，请检查网络连接" = "Não é possível adicionar %lld; verifique sua conexão de rede";
"缺少 Apache 后端配置" = "Configuração de backend do Apache ausente";
"复制错误页面目录失败: %@" = "Falha ao copiar o diretório de páginas de erro: %@";
"复制错误页面目录失败: %lld" = "Falha ao copiar o diretório de páginas de erro: %lld";
"解密" = "Descriptografar";
"计算哈希值" = "Calcular valor hash";
"站点状态" = "status do site";
"SSL 证书正常" = "Certificado SSL válido";
"已管理" = "Gerenciado";
"1. 确认域名已正确解析到服务器 IP" = "1. Confirme que o nome de domínio foi resolvido corretamente para o IP do servidor";
"今天" = "hoje";
"内存" = "memória";
"⚠️ 备份旧 Nginx 配置失败: %@" = "⚠️ Falha ao fazer backup da configuração antiga do Nginx: %@";
"⚠️ 备份旧 Nginx 配置失败: %lld" = "⚠️ Falha ao fazer backup da antiga configuração do Nginx: %lld";
"重置路径" = "redefinir caminho";
"停止占用端口的服务，PHP %@ 将使用默认端口 %@" = "Interrompa o serviço que está ocupando a porta. O PHP %@ usará a porta padrão %@";
"停止占用端口的服务，PHP %lld 将使用默认端口 %lld" = "Interrompa o serviço que está ocupando a porta. O PHP %lld usará a porta padrão %lld";
"经典 Web 服务器" = "Servidor web clássico";
"提示：Ctrl+F 搜索关键字，Ctrl+S 保存，Ctrl+H 查找替换" = "Dica: Ctrl+F para pesquisar palavras-chave, Ctrl+S para salvar, Ctrl+H para localizar e substituir";
"导出数据库失败" = "Falha ao exportar o banco de dados";
"无法打开目录：路径未知" = "Não é possível abrir o diretório: caminho desconhecido";
"正在安装 GoAccess..." = "Instalando o GoAccess...";
"保持连接" = "manter conectado";
"正在加载软件列表..." = "Carregando lista de softwares...";
"有 %@ 个端口未监听" = "Há %@ portas não escutando";
"有 %lld 个端口未监听" = "Há %lld portas não escutando";
"提示: 使用 MySQL 模式需要先启动 MySQL 服务" = "Dica importante: Para usar o modo MySQL, você precisa iniciar primeiro o serviço MySQL";
"峰值访问量" = "visitas de pico";
"重要提示" = "Dicas importantes";
"组织名称" = "Nome da organização";
"安装 Pure-FTPd 失败" = "Falha na instalação do Pure-FTPd";
"默认文档" = "documento padrão";
"搜索域名或IP" = "Pesquisar nome de domínio ou IP";
"共 %@ 条  前往" = "Total de %@ itens Go";
"共 %lld 条  前往" = "Total de %lld itens Go";
"建议端口: %@" = "Porta sugerida: %@";
"建议端口: %lld" = "Porta sugerida: %lld";
"下载完成，准备安装..." = "Download concluído, pronto para instalar...";
"无效的域名：%@" = "Nome de domínio inválido: %@";
"无效的域名：%lld" = "Nome de domínio inválido: %lld";
"修改成功" = "Modificação bem-sucedida";
"Redis 端口" = "Porta Redis";
"热门请求 (Top 10)" = "Solicitações populares (Top 10)";
"系统默认" = "Padrão do sistema";
"仅使用Cookie" = "Usar apenas cookies";
"请选择解决方案" = "Selecione uma solução";
"• 注意3：通过宝塔安装的数据库root默认不支持远程权限" = "• Observação 3: A raiz do banco de dados instalada por meio do Pagoda não suporta permissões remotas por padrão.";
"每行一个IP地址" = "Um endereço IP por linha";
"请先测试连接" = "Teste primeiro a conexão";
"开始使用" = "Começar";
"Sendmail路径" = "Caminho do Sendmail";
"创建时间" = "data de criação";
"GTID 模式" = "Modo GTID";
"查询失败: %@" = "Falha na consulta: %@";
"查询失败: %lld" = "Falha na consulta: %lld";
"多版本共存管理" = "Gerenciamento de coexistência de múltiplas versões";
"下载中" = "Baixando";
"请选择要分析的访问日志文件" = "Selecione um arquivo de log de acesso para analisar";
"已从备份恢复数据" = "Dados restaurados do backup";
"配置文件已保存" = "Arquivo de configuração salvo";
"暂无备份记录" = "Ainda não há registro de backup";
"加载数据库数据失败: %@" = "Falha ao carregar os dados do banco de dados: %@";
"加载数据库数据失败: %lld" = "Falha ao carregar os dados do banco de dados: %lld";
"未找到配置文件" = "Arquivo de configuração não encontrado";
"添加第三方源 %@..." = "Adicionando fonte de terceiros %@...";
"添加第三方源 %lld..." = "Adicionando fonte de terceiros %lld...";
"正在验证 SSL 证书..." = "Verificando o certificado SSL...";
"最大浪费百分比" = "Percentual máximo de desperdício";
"备份列表 - %@" = "Lista de backups - %@";
"备份列表 - %lld" = "Lista de backups - %lld";
"最大使用内存" = "Uso máximo de memória";
"对象关系型数据库" = "banco de dados orientado a objetos-relacionais";
"单用户最大连接数" = "Número máximo de conexões por usuário";
"• Redis扩展仅支持一个PHP版本安装使用，若在其它PHP版本已安装redis扩展，请勿再装" = "• A extensão Redis suporta apenas a instalação e o uso de uma versão do PHP. Se a extensão redis já estiver instalada em outras versões do PHP, não a instale novamente.";
"批量重启完成" = "Reinicialização em lote concluída";
"HTTP 端口:" = "Porta HTTP:";
"重新选择" = "Selecionar novamente";
"在 Finder 中打开" = "Abrir no Finder";
"攻击类型" = "Tipo de ataque";
"剩余天数: %@ 天" = "Número de dias restantes: %@ dias";
"剩余天数: %lld 天" = "Número de dias restantes: %lld dias";
"及和PHP环境" = "e ambiente PHP";
"加载中" = "Carregando";
"性能配置" = "Configuração de desempenho";
"该软件运行正常" = "O software está funcionando normalmente";
"证书申请失败：%@" = "Falha na solicitação do certificado: %@";
"证书申请失败：%lld" = "Falha na solicitação do certificado: %lld";
"配置中" = "Configurando";
"网站还没有访问记录，或日志文件不存在" = "O site não possui registro de acesso ou o arquivo de log não existe";
"JIT 编译模式" = "Modo de compilação JIT";
"保存时将条目纳入已管理区域。" = "Ao salvar, a entrada será incluída na área gerenciada.";
"JIT 缓冲区大小" = "Tamanho do buffer JIT";
"• 修改后需要重启 Nginx/Apache 才能生效" = "• O Nginx/Apache precisa ser reiniciado após a modificação para que ela entre em vigor";
"应用配置失败" = "Falha na configuração do aplicativo";
"高效文件传输" = "Transferência eficiente de arquivos";
"终端" = "terminal";
"端口占用与冲突管理" = "Gerenciamento de ocupação e conflito de portas";
") 配置文件目录" = ") diretório do arquivo de configuração";
"签发证书" = "Emitir certificado";
"选择目录" = "Selecionar diretório";
"预估内存使用超过系统内存的 80%" = "O uso estimado de memória excede 80% da memória do sistema";
"读取数据失败" = "Falha ao ler os dados";
"编辑站点" = "Editar site";
"压缩级别(-1到9)" = "Nível de compactação (-1 a 9)";
"规则模板" = "Modelo de regra";
"内存数据结构存储" = "Armazenamento em estrutura de dados na memória";
"服务器标识" = "ID do servidor";
"创建配置文件" = "Criar arquivo de configuração";
"安装列表" = "Lista de instalação";
"方法" = "método";
"确定要删除 %@ %@ 吗？此操作不可恢复。" = "Tem certeza de que deseja excluir %@ %@? Esta operação é irreversível.";
"确定要删除 %lld %lld 吗？此操作不可恢复。" = "Tem certeza de que deseja excluir %lld %lld? Esta operação é irreversível.";
"生成新配置前自动备份" = "Backup automático antes de gerar nova configuração";
"Zlib压缩支持" = "Suporte à compactação Zlib";
"Web服务器" = "Servidor web";
"运行目录子路径:" = "Executar subcaminho do diretório:";
"MySQL 系统数据库" = "Banco de dados do sistema MySQL";
"最大执行时间" = "Tempo máximo de execução";
"创建数据库失败，请检查 MySQL 连接" = "Falha ao criar o banco de dados; verifique a conexão com o MySQL";
"新建备份" = "Criar um novo backup";
"MySQL 状态" = "Status do MySQL";
"可用" = "Disponível";
"备份" = "backup";
"\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 " = "Ouvir na porta 8080\n\n            # Carregar 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 reverso Nginx para a porta HTTPS do Apache";
"仅 1024+" = "Apenas 1024+";
"父目录不存在，将自动创建" = "O diretório pai não existe e será criado automaticamente.";
"[SiteManager] 恢复 hosts 条目失败: %@ - %@" = "[SiteManager] Falha ao restaurar entrada no arquivo hosts: %@ - %@";
"[SiteManager] 恢复 hosts 条目失败: %lld - %lld" = "[SiteManager] Falha ao restaurar entrada no arquivo hosts: %lld - %lld";
"停止 %@ 释放端口 %@" = "Parar %@ e liberar a porta %@";
"停止 %lld 释放端口 %lld" = "Parar %lld e liberar a porta %lld";
"✅ mkcert 生成证书成功: %@" = "✅ Certificado gerado com sucesso pelo mkcert: %@";
"✅ mkcert 生成证书成功: %lld" = "✅ Certificado gerado com sucesso pelo mkcert: %lld";
"按大小" = "por tamanho";
"暂无数据库" = "Nenhum banco de dados ainda";
"检查 Homebrew..." = "Confira o Homebrew...";
"PDO PostgreSQL驱动" = "Driver PDO PostgreSQL";
"添加用户" = "Adicionar usuário";
"添加站点" = "Adicionar site";
"选择SQL文件" = "Selecionar arquivo SQL";
"有效期至" = "Válido até";
"每年切割一次" = "Cortar uma vez por ano";
"批量彻底删除" = "Exclusão em lote completa";
"我们将帮助您完成初始设置" = "Nós o ajudaremos na configuração inicial";
"操作失败: %@" = "Falha na operação: %@";
"操作失败: %lld" = "Falha na operação: %lld";
"%@ 重启失败,请检查日志" = "%@ Falha ao reiniciar, verifique o log";
"%lld 重启失败,请检查日志" = "%lld Falha ao reiniciar, verifique o log";
"• 支持多个域名，用空格分隔" = "• Suporta vários nomes de domínio, separados por espaços";
"无法读取响应" = "Não foi possível ler a resposta";
"按月" = "por mês";
"请先创建网站后再查看日志" = "Crie primeiro o site e, em seguida, visualize os logs";
"端口号已复制到剪贴板" = "Número da porta copiado para a área de transferência";
"表名大小写敏感" = "Os nomes das tabelas diferenciam maiúsculas de minúsculas";
"剩余63天" = "63 dias restantes";
"连接超时" = "Tempo limite de conexão";
"未找到 %@ 的配置文件。\\n\\n可能的原因:\\n1. 服务未安装\\n2. 配置文件位置不在预期路径\\n\\n请确认服务已正确安装。" = "Arquivo de configuração não encontrado para %@. \\n\\nPossíveis causas:\\n1. O serviço não está instalado\\n2. O local do arquivo de configuração não está no caminho esperado\\n\\nVerifique se o serviço foi instalado corretamente.";
"未找到 %lld 的配置文件。\\n\\n可能的原因:\\n1. 服务未安装\\n2. 配置文件位置不在预期路径\\n\\n请确认服务已正确安装。" = "Arquivo de configuração não encontrado para %lld. \\n\\nPossíveis causas:\\n1. O serviço não está instalado\\n2. O local do arquivo de configuração não está no caminho esperado\\n\\nVerifique se o serviço foi instalado corretamente.";
"最大内存(0不限制)" = "Memória máxima (0 sem limite)";
"本地连接" = "Conexão de área local";
"待检测" = "A ser testado";
"IO 最大容量" = "Capacidade máxima de E/S";
"配置冲突：同时存在完整的 Nginx 和 Apache 配置。请手动选择要使用的配置，并删除另一个。" = "Conflito de configuração: existem ambas as configurações completas do Nginx e do Apache. Selecione manualmente a configuração a ser usada e exclua a outra.";
"检测" = "Detecção";
"正在下载 GoAccess..." = "Baixando GoAccess...";
"default.conf listen 指令:" = "Diretiva 'listen' do default.conf:";
"选择父目录并开始扫描" = "Selecione o diretório pai e inicie a varredura";
"跨站脚本攻击检测" = "Detecção de ataques de script entre sites (XSS)";
"缓存时间 (Seconds)" = "Tempo de cache (segundos)";
"复制默认首页失败: %@" = "Falha ao copiar a página inicial padrão: %@";
"复制默认首页失败: %lld" = "Falha ao copiar a página inicial padrão: %lld";
"创建数据库" = "Criar banco de dados";
"关于 acme.sh：" = "Sobre o acme.sh:";
"MySQL 数据库" = "Banco de dados MySQL";
"通用扩展" = "Extensão universal";
"即将打开 %@ 的配置文件" = "O arquivo de configuração de %@ está prestes a ser aberto.";
"即将打开 %lld 的配置文件" = "O arquivo de configuração de %lld está prestes a ser aberto.";
"未检测到已安装的服务" = "Serviço instalado não detectado";
"没有正在运行的Web服务" = "Nenhum serviço web em execução";
"剩余天数" = "Dias restantes";
"生成私钥失败" = "Falha ao gerar a chave privada";
"# 请手动安装" = "# Instale manualmente";
"请先加密文本或输入要解密的密文" = "Por favor, criptografe primeiro o texto ou insira o texto cifrado a ser decifrado";
"线程池大小" = "Tamanho do pool de threads";
"系统和浏览器" = "Sistema e navegador";
"线程并发数" = "Número de threads simultâneos";
"有効 %@ / %@" = "Válido %@ / %@";
"有効 %lld / %lld" = "Válido %lld / %lld";
"清除搜索" = "Limpar pesquisa";
"• 系统将自动创建数据库和用户，并授予相应权限" = "• O sistema criará automaticamente bancos de dados e usuários e concederá as permissões correspondentes";
"生成 CA 证书" = "Gerar certificado CA";
"MPM 配置" = "Configuração do MPM";
"无法创建验证文件：%@" = "Não foi possível criar o arquivo de verificação: %@";
"无法创建验证文件：%lld" = "Não foi possível criar o arquivo de verificação: %lld";
"📁 扫描到 %@ 个证书文件" = "📁 Verificado até %@ arquivos de certificado";
"📁 扫描到 %lld 个证书文件" = "📁 Verificado até %lld arquivos de certificado";
"复制完整连接信息到剪贴板，方便在 Navicat 中使用" = "Copie as informações completas de conexão para a área de transferência para uso fácil no Navicat";
"准备安装 Nginx..." = "Preparando para instalar o Nginx...";
"搜索日志..." = "Pesquisando logs...";
"繁" = "Tradicional";
"系统" = "sistema";
"• 申请前会自动检测域名是否可达" = "• Verificará automaticamente se o nome de domínio é acessível antes da aplicação";
"强烈建议迁移 %@ 配置文件" = "Recomenda-se fortemente migrar o arquivo de configuração %@";
"强烈建议迁移 %lld 配置文件" = "Recomenda-se fortemente migrar o arquivo de configuração %lld";
"最大工作进程" = "Número máximo de processos de trabalho";
"记录所有HTTP请求（包括状态码、IP、请求路径等）" = "Registra todas as solicitações HTTP (incluindo código de status, IP, caminho da solicitação, etc.)";
"使用其他端口" = "Usar outra porta";
"攻击次数" = "Número de ataques";
"导入配置失败: %@" = "Falha ao importar a configuração: %@";
"导入配置失败: %lld" = "Falha ao importar a configuração: %lld";
"\n            # Apache 配置文件\n            # 由及和PHP环境自动生成\n\n            ServerRoot " = "#Arquivo de configuração do Apache\n            # Gerado automaticamente pelo ambiente PHP\n\n            ServerRoot";
"基于内存亦可持久化的Key-Value数据库" = "Banco de dados Key-Value baseado em memória e persistente";
"作者" = "Autor";
"显示最近 %@ 条日志" = "Exibir os últimos %@ logs";
"显示最近 %lld 条日志" = "Exibir os últimos %lld logs";
"便于备份" = "Fácil de fazer backup";
"修复配置失败: %@" = "Falha ao reparar a configuração: %@";
"修复配置失败: %lld" = "Falha ao reparar a configuração: %lld";
"⚠️ 工作空间未初始化" = "⚠️ Workspace não inicializado";
"PDO MySQL驱动" = "Driver PDO MySQL";
"连接名称不能为空" = "O nome da conexão não pode estar vazio";
"本地域名（如 localhost、.local）不支持 Let's Encrypt" = "Nomes de domínio locais (como localhost, .local) não suportam o 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>Parabéns, o site foi criado com sucesso! </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=";
"暂无备份" = "Ainda não há backup";
"表定义缓存" = "cache de definição de tabela";
"格式化" = "Formato";
"复制连接信息" = "Copiar informações da conexão";
"✗ 配置文件不存在: %@" = "✗ O arquivo de configuração não existe: %@";
"✗ 配置文件不存在: %lld" = "✗ O arquivo de configuração não existe: %lld";
"连接缓冲区" = "buffer de conexão";
"读取目录失败 %@: %@" = "Falha ao ler o diretório %@: %@";
"读取目录失败 %lld: %lld" = "Falha ao ler o diretório %lld: %lld";
"操作系统和浏览器分布" = "Distribuição do sistema operacional e navegador";
"缺少 Nginx 反向代理配置" = "Configuração ausente do proxy reverso Nginx";
"自动选择" = "seleção automática";
"证书类型: 自签名证书" = "Tipo de certificado: certificado autoassinado";
"服务器签名" = "Assinatura do servidor";
"删除配置文件失败: %@, 错误: %@" = "Falha ao excluir o arquivo de configuração: %@, erro: %@";
"删除配置文件失败: %lld, 错误: %lld" = "Falha ao excluir o arquivo de configuração: %lld, erro: %lld";
"正在扫描日志..." = "Analisando logs...";
"TCP 保活时间" = "Tempo de keepalive TCP";
"服务器字符集" = "Conjunto de caracteres do servidor";
"• 数据库名称和用户名只能包含字母、数字、下划线和连字符" = "• Os nomes do banco de dados e dos usuários devem conter apenas letras, números, sublinhados e hífens";
"输入代码" = "Insira o código";
"快捷操作" = "Operação rápida";
"终止" = "encerrar";
"正在重启 %@..." = "Reiniciando %@...";
"正在重启 %lld..." = "Reiniciando %lld...";
"最少2位字符" = "Mínimo de 2 caracteres";
"安装新版本" = "Instalar nova versão";
"允许文件上传" = "Permitir upload de arquivos";
"无法查看密码" = "Não é possível visualizar a senha";
"JSON 解析失败: %@" = "Falha na análise JSON: %@";
"JSON 解析失败: %lld" = "Falha na análise JSON: %lld";
"输出缓冲" = "buffer de saída";
"永久" = "permanente";
"暂无日志" = "Ainda não há registros";
"模板" = "Modelo";
"导出失败" = "Falha na exportação";
"已部署" = "Implantado";
"证书文件" = "arquivo de certificado";
"创建FTP账户: %@, 密码: %@" = "Criar conta FTP: %@, senha: %@";
"创建FTP账户: %lld, 密码: %lld" = "Criar conta FTP: %lld, senha: %lld";
"言語" = "fala";
"保存前自动备份" = "Backup automático antes de salvar";
"连接超时，请检查网络连接和防火墙设置" = "Tempo limite de conexão excedido. Verifique a conexão de rede e as configurações do firewall.";
"下载地址" = "Endereço de download";
"\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                " = "# Bloco do servidor HTTP - redirecionar para HTTPS\n                server {\n                    listen 80;\n                    server_name %@;\n\n                    # Forçar redirecionamento para HTTPS\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # Bloco do servidor HTTPS\n                server {\n                    listen 443 ssl;\n                    server_name %@;\n                    root %@;\n                    index index.php index.html index.htm;\n\n                    # Configuração do certificado SSL\n                    ssl_certificate %@;\n                    ssl_certificate_key %@;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # Log de acesso\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                    # Desabilitar acesso a arquivos 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                " = "# Bloco do servidor HTTP - redirecionar para HTTPS\n                server {\n                    listen 80;\n                    server_name %lld;\n\n                    # Forçar redirecionamento para HTTPS\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # Bloco do 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                    # Configuração do 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                    # Log de acesso\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                    # Desabilitar acesso a arquivos ocultos\n                    location ~ /\\. {\n                        deny all;\n                    }\n                }";
"重新加载失败" = "Falha ao recarregar";
"请使用工具菜单中的证书管理功能申请证书" = "Use a função de gerenciamento de certificados no menu de ferramentas para solicitar um certificado";
"%@ (已停止)" = "%@ (parado)";
"%lld (已停止)" = "%lld (parado)";
"时间" = "tempo";
"已重启 %@ 个Web服务" = "Os serviços web %@ foram reiniciados";
"已重启 %lld 个Web服务" = "%lld serviços web foram reiniciados";
"解码" = "Decodificar";
"未知主机，请检查服务器地址" = "Host desconhecido; verifique o endereço do servidor";
"路径: %@" = "Caminho: %@";
"路径: %lld" = "Caminho: %lld";
"授权失败: \\(grantResult.error ?? " = "Falha na autorização: \\(grantResult.error ??";
"进程控制扩展" = "extensão de controle de processo";
"开始分析" = "Iniciar análise";
"目录没有写权限" = "O diretório não possui permissão de gravação";
"\n                # HTTP VirtualHost - 重定向到 HTTPS\n                # Apache 后端端口：%@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ?? " = "# HTTP VirtualHost - redirecionar para HTTPS\n                # Porta de backend do Apache: %@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ??";
"\n                # HTTP VirtualHost - 重定向到 HTTPS\n                # Apache 后端端口：%lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ?? " = "# HTTP VirtualHost - redirecionar para HTTPS\n                # Porta de backend do Apache: %lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ??";
"批量删除失败" = "Falha ao excluir em lote";
"管理本地域名解析" = "Gerenciar resolução de nome de domínio local";
"忘记密码" = "Esqueceu a senha";
"服务器ID" = "ID do servidor";
"编码解码工具" = "Ferramentas de codificação e decodificação";
"导入选中项 (%@)" = "Importar itens selecionados (%@)";
"导入选中项 (%lld)" = "Importar itens selecionados (%lld)";
"用于解密ionCube Encoder加密脚本" = "Script criptografado para descriptografar o ionCube Encoder";
"确认密码" = "Confirmar senha";
"隐藏密码" = "Ocultar senha";
"系统内存: \\(String(format: " = "Memória do sistema: \\(String(format:";
"端口 %@ 已被占用，请选择其他端口" = "A porta %@ já está ocupada; escolha outra porta";
"端口 %lld 已被占用，请选择其他端口" = "A porta %lld já está ocupada; escolha outra porta";
"已导出 %@ 个站点配置到: %@" = "Configurações de %@ sites exportadas para: %@";
"已导出 %lld 个站点配置到: %lld" = "Configurações de %lld sites exportadas para: %lld";
"+%@ 个域名" = "+%@ nomes de domínio";
"+%lld 个域名" = "+%lld nomes de domínio";
"常规" = "regular";
"❌ 扫描证书目录失败: %@" = "❌ Falha ao analisar o diretório de certificados: %@";
"❌ 扫描证书目录失败: %lld" = "❌ Falha ao analisar o diretório de certificados: %lld";
"rewrite 规则已转换，请检查正则表达式语法" = "Regra de reescrita convertida; verifique a sintaxe da expressão regular";
"只能为 Apache 配置生成 Nginx 代理" = "O proxy Nginx só pode ser gerado para configurações do Apache";
"成功删除 %@ 个配置文件，失败 %@ 个" = "Arquivos de configuração de %@ excluídos com sucesso; falha em %@";
"成功删除 %lld 个配置文件，失败 %lld 个" = "Arquivos de configuração de %lld excluídos com sucesso; falha em %lld";
"确定要删除数据库 %@ 吗？此操作不可恢复！" = "Tem certeza de que deseja excluir o banco de dados %@? Esta operação é irreversível!";
"确定要删除数据库 %lld 吗？此操作不可恢复！" = "Tem certeza de que deseja excluir o banco de dados %lld? Esta operação é irreversível!";
"编辑 Hosts 条目" = "Editar entrada de Hosts";
"正在检查域名解析..." = "Verificando a resolução do nome de domínio...";
"显示高级配置项" = "Exibir itens de configuração avançada";
"性能优化" = "Otimização de desempenho";
"运行中" = "Em execução";
"创建" = "Criar";
"只能为 Nginx 代理配置生成 Apache 后端" = "A configuração de backend Apache só pode ser gerada para a configuração de proxy Nginx";
"正在加载..." = "Carregando...";
"重新检查" = "verificar novamente";
"端口监听" = "Escuta de porta";
"未检测到" = "não detectado";
"未检测" = "Não detectado";
"完成" = "Concluído";
"删除配置失败: %@" = "Falha ao excluir a configuração: %@";
"删除配置失败: %lld" = "Falha ao excluir a configuração: %lld";
"状态" = "Status";
"• 证书将自动写入站点配置并启用 HTTPS" = "• O certificado será automaticamente gravado na configuração do site e o HTTPS será habilitado";
"申请成功" = "Aplicação bem-sucedida";
"读取 hosts 文件失败: %@" = "Falha ao ler o arquivo hosts: %@";
"读取 hosts 文件失败: %lld" = "Falha ao ler o arquivo hosts: %lld";
"修改配置文件，将端口改为 %@" = "Modifique o arquivo de configuração e altere a porta para %@";
"修改配置文件，将端口改为 %lld" = "Modifique o arquivo de configuração e altere a porta para %lld";
"文档根目录" = "raiz do documento";
"%@ 启动失败,请检查日志" = "%@ falhou ao iniciar, verifique o log";
"%lld 启动失败,请检查日志" = "%lld falhou ao iniciar, verifique o log";
"启用 OPcache" = "Habilitar OPcache";
"修改权限" = "Modificar permissões";
"请选择MySQL版本" = "Selecione a versão do MySQL";
"验证成功" = "Verificação bem-sucedida";
"正在修改 %@ 端口..." = "Modificando a porta de %@...";
"正在修改 %lld 端口..." = "Modificando a porta de %lld...";
"✅ 已强力修复站点配置: %@" = "✅ A configuração do site foi corrigida com sucesso: %@";
"✅ 已强力修复站点配置: %lld" = "✅ A configuração do site foi corrigida com sucesso: %lld";
"修改路径" = "Modificar caminho";
"• 修改 hosts 文件需要管理员权限" = "• Modificar o arquivo hosts requer privilégios de administrador";
"扫描" = "verificar";
"端口" = "porta";
"检查 %@ 个站点" = "Verificar sites %@";
"检查 %lld 个站点" = "Verificar sites %lld";
"选择日志文件" = "Selecionar arquivo de log";
"修改密码失败，请检查当前密码是否正确" = "Falha na modificação da senha; verifique se a senha atual está correta";
"请输入当前密码" = "Por favor, insira a senha atual";
"未找到要编辑的 hosts 条目" = "A entrada de hosts a ser editada não foi encontrada";
"Let's Encrypt 证书已成功申请并配置。\\n\\n域名: %@\\n有效期: 90 天" = "O certificado Let's Encrypt foi solicitado e configurado com sucesso. \\n\\nNome de domínio: %@\\nPeríodo de validade: 90 dias";
"Let's Encrypt 证书已成功申请并配置。\\n\\n域名: %lld\\n有效期: 90 天" = "O certificado Let's Encrypt foi solicitado e configurado com sucesso. \\n\\nNome de domínio: %lld\\nPeríodo de validade: 90 dias";
"短标签" = "tag curta";
"域名列表不能为空" = "A lista de nomes de domínio não pode estar vazia";
"15 分钟" = "15 minutos";
"========== 扫描结束 ==========\\n" = "========== Verificação concluída ==========\\n";
"准备开始检查" = "Pronto para iniciar a verificação";
"安装独立的 %@ 实例，使用其他可用端口" = "Instalar uma instância independente de %@, usando outras portas disponíveis";
"安装独立的 %lld 实例，使用其他可用端口" = "Instalar uma instância independente de %lld, usando outras portas disponíveis";
"设置服务工作空间" = "Configurar um espaço de trabalho de serviço";
"OPcache 配置" = "Configuração do OPcache";
"支持的日志格式" = "Formatos de log suportados";
"未找到 mysqldump" = "mysqldump não encontrado";
"验证失败" = "Falha na autenticação";
"Web 服务器" = "Servidor web";
"支持虚拟用户，无需创建系统账户" = "Suporta usuários virtuais sem criar contas de sistema";
"有效请求" = "requisição válida";
"验证文件内容不匹配" = "O conteúdo do arquivo verificado não corresponde";
"404错误页" = "Página de erro 404";
"最小压缩长度" = "Comprimento mínimo de compactação";
"\n        # PureDB 文件认证\n        PureDB                      %@/pureftpd.pdb\n\n        " = "#Autenticação de arquivo PureDB\n        PureDB %@/pureftpd.pdb";
"\n        # PureDB 文件认证\n        PureDB                      %lld/pureftpd.pdb\n\n        " = "#Autenticação de arquivo PureDB\n        PureDB %lld/pureftpd.pdb";
"配置项" = "Itens de configuração";
"点击刷新按钮加载日志" = "Clique no botão de atualização para carregar o log";
"服务名称" = "Nome do serviço";
" combined\n                </VirtualHost>\n\n                # HTTPS VirtualHost\n                # Apache 后端端口：%@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ?? " = "combined\n                </VirtualHost>\n\n                # VirtualHost HTTPS\n                # Porta de backend do Apache: %@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ??";
" combined\n                </VirtualHost>\n\n                # HTTPS VirtualHost\n                # Apache 后端端口：%lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ?? " = "combined\n                </VirtualHost>\n\n                # VirtualHost HTTPS\n                # Porta de backend do Apache: %lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ??";
"路径缓存TTL" = "TTL do cache de caminho";
"批量删除" = "Exclusão em lote";
"保存日志配置失败: %@" = "Falha ao salvar a configuração de log: %@";
"保存日志配置失败: %lld" = "Falha ao salvar a configuração de log: %lld";
"确定要删除站点 %@ 吗？站点将被移到回收站，可以恢复。" = "Tem certeza de que deseja excluir o site %@? O site será movido para a Lixeira e poderá ser restaurado.";
"确定要删除站点 %lld 吗？站点将被移到回收站，可以恢复。" = "Tem certeza de que deseja excluir o site %lld? O site será movido para a Lixeira e poderá ser restaurado.";
"⚠ %@ 已禁用" = "⚠ %@ desativado";
"⚠ %lld 已禁用" = "⚠ %lld desativado";
"请选择SQL文件" = "Selecione um arquivo SQL";
"来源 (Origins)" = "Origens";
"✅ 已为 Apache 配置生成 Nginx 代理: %@" = "✅ Proxy Nginx gerado com sucesso para a configuração do Apache: %@";
"✅ 已为 Apache 配置生成 Nginx 代理: %lld" = "✅ Proxy Nginx gerado para a configuração do Apache: %lld";
"正在准备安装 %@..." = "Preparando para instalar %@...";
"正在准备安装 %lld..." = "Preparando para instalar %lld...";
"交互超时" = "Tempo limite de interação";
"恢复建议值" = "Restaurar valores recomendados";
"历史记录" = "Histórico";
"确定要彻底删除选中的 %@ 个站点吗？此操作不可恢复。" = "Tem certeza de que deseja excluir completamente os sites selecionados %@? Esta operação é irreversível.";
"确定要彻底删除选中的 %lld 个站点吗？此操作不可恢复。" = "Tem certeza de que deseja excluir completamente os %lld sites selecionados? Esta operação é irreversível.";
"持久化配置" = "Configuração de persistência";
"浮点数精度" = "Precisão de ponto flutuante";
"添加mysql数据库" = "Adicionar banco de dados MySQL";
"重新验证频率" = "Frequência de revalidação";
"配置已保存，但重载 Nginx 失败" = "Configuração salva, mas falha ao recarregar o Nginx";
"按天" = "por dia";
"上一步" = "Etapa anterior";
"邮件配置" = "Configuração de e-mail";
"允许跨域请求的域名，例如: http://localhost:8080，允许所有请填 *" = "Nomes de domínio que permitem solicitações entre domínios, por exemplo: http://localhost:8080; preencha * se todos forem permitidos";
"序列化精度" = "Precisão de serialização";
"日志文件不存在" = "Arquivo de log não existe";
"自动" = "automático";
"PHP-FPM 端口:" = "Porta do PHP-FPM:";
"最新版" = "Última versão";
"配置文件已备份" = "O arquivo de configuração foi salvo como backup";
"网站日志 - %@" = "Log do site - %@";
"网站日志 - %lld" = "Log do site - %lld";
"以下服务当前未运行：\\n\\n%@\\n\\n使用已停止的服务可能导致网站无法正常访问。\\n\\n建议先启动这些服务，或选择其他运行中的服务。" = "Os seguintes serviços não estão atualmente em execução:\\n\\n%@\\n\\nUsar serviços parados pode tornar o site inacessível. \\n\\nRecomenda-se iniciar esses serviços primeiro ou selecionar outros serviços em execução.";
"以下服务当前未运行：\\n\\n%lld\\n\\n使用已停止的服务可能导致网站无法正常访问。\\n\\n建议先启动这些服务，或选择其他运行中的服务。" = "Os seguintes serviços não estão atualmente em execução:\\n\\n%lld\\n\\nUsar serviços parados pode tornar o site inacessível. \\n\\nRecomenda-se iniciar esses serviços primeiro ou selecionar outros serviços em execução.";
"基于 GoAccess 生成" = "Gerado com base no GoAccess";
"安装 %@ 失败" = "Falha na instalação de %@";
"安装 %lld 失败" = "Falha na instalação de %lld";
"高性能异步网络通信引擎" = "Motor de comunicação de rede assíncrono de alto desempenho";
"繁體中文" = "Chinês tradicional";
"下次重启服务后生效" = "Entrará em vigor após a próxima reinicialização do serviço.";
"未找到 MySQL 配置文件" = "Arquivo de configuração do MySQL não encontrado";
"开机自启动" = "Iniciar automaticamente na inicialização";
"✓ PHP 运行正常" = "✓ O PHP está funcionando corretamente";
"root密码" = "senha do root";
"PHP攻击" = "Ataque ao PHP";
"复制配置" = "Copiar configuração";
"访问日志" = "log de acesso";
"MySQL默认主机" = "Host padrão do MySQL";
"连接成功" = "Conexão bem-sucedida";
"正在生成分析报告..." = "Gerando relatório de análise...";
"安装 acme.sh 失败：%@" = "Falha na instalação do acme.sh: %@";
"安装 acme.sh 失败：%lld" = "Falha na instalação do acme.sh: %lld";
"未部署" = "Não implantado";
"显示IP归属地信息" = "Exibir informações do endereço IP";
"(端口 %@)" = "(porta %@)";
"(端口 %lld)" = "(porta %lld)";
"编译中" = "Compilando";
"当日志文件达到指定大小时自动切割" = "Cortar automaticamente o arquivo de log quando atingir o tamanho especificado";
"通用名称 (CN)" = "Nome comum (CN)";
"GNU多精度算术库" = "Biblioteca de aritmética de precisão múltipla GNU";
"安装失败: %@" = "Falha na instalação: %@";
"安装失败: %lld" = "Falha na instalação: %lld";
"终端为系统应用，点击下方按钮将在外部打开。" = "O terminal é um aplicativo do sistema. Clicar no botão abaixo o abrirá externamente.";
"规则内容" = "Conteúdo da regra";
"正在检测 MySQL 状态..." = "Verificando o status do MySQL...";
"父目录:" = "Diretório pai:";
"• 证书有效期 90 天" = "• O certificado é válido por 90 dias";
"服务工作空间" = "área de trabalho do serviço";
"正在停止 %@..." = "Parando %@...";
"正在停止 %lld..." = "Parando %lld...";
"确认彻底删除" = "Confirmar exclusão completa";
"证书路径" = "Caminho do certificado";
"批量修复完成" = "Reparo em lote concluído";
"文件不存在：%@" = "Arquivo não existe: %@";
"文件不存在：%lld" = "Arquivo não existe: %lld";
"服务器名称" = "Nome do servidor";
"本地域名不支持 Let's Encrypt：%@" = "O Let's Encrypt não é compatível com nomes de domínio locais: %@";
"本地域名不支持 Let's Encrypt：%lld" = "O Let's Encrypt não é compatível com nomes de domínio locais: %lld";
"2. 确认防火墙已开放 80 和 443 端口" = "2. Confirme que o firewall abriu as portas 80 e 443";
"安装" = "Instalação";
"正在获取数据库列表..." = "Obtendo lista de bancos de dados...";
"⚠️ 备份旧配置失败: %@" = "⚠️ Falha ao fazer backup da configuração antiga: %@";
"⚠️ 备份旧配置失败: %lld" = "⚠️ Falha ao fazer backup da configuração antiga: %lld";
"default.conf: %@ (普通文件)" = "default.conf: %@ (arquivo normal)";
"default.conf: %lld (普通文件)" = "default.conf: %lld (arquivo normal)";
"操作失败" = "Falha na operação";
"请输入管理员密码" = "Insira a senha de administrador";
"已安装并信任" = "Instalado e confiável";
"已连通远程数据库，但本机客户端不支持该认证插件。您仍然可以保存此连接配置。" = "O banco de dados remoto foi conectado, mas o cliente local não suporta o plug-in de autenticação. Você ainda pode salvar esta configuração de conexão.";
"LRU采样数" = "Número de amostras LRU";
"端口配置" = "Configuração da porta";
"未知的 caching_sha2_password 状态: %@" = "Status desconhecido do caching_sha2_password: %@";
"未知的 caching_sha2_password 状态: %lld" = "Status desconhecido do caching_sha2_password: %lld";
"读入缓冲区大小" = "Tamanho do buffer de leitura";
"%@ 天" = "%@ dias";
"%lld 天" = "%lld dias";
"关联数据库" = "banco de dados relacional";
"默认: %@" = "Padrão: %@";
"默认: %lld" = "Padrão: %lld";
"========== 开始扫描配置文件 ==========" = "========== Iniciando a verificação dos arquivos de configuração ==========";
"读缓冲区" = "buffer de leitura";
"（无密码）" = "(sem senha)";
"伪静态规则转换" = "Conversão de regra pseudoestática";
"/（根目录）" = "/(diretório raiz)";
"刷新" = "Atualizar";
"日志分析" = "Análise de logs";
"仅当前用户" = "Apenas o usuário atual";
"⚠️ 证书目录不存在" = "⚠️ O diretório de certificados não existe";
"服务未运行" = "O serviço não está em execução";
"配置 PHP-FPM 端口..." = "Configurando a porta do PHP-FPM...";
"知道了" = "Entendido";
"❌ 解析失败: %@" = "❌ Falha na análise: %@";
"❌ 解析失败: %lld" = "❌ Falha na análise: %lld";
"提示信息" = "Mensagem de aviso";
"自定义目录" = "Diretório personalizado";
"内存表最大大小" = "Tamanho máximo da tabela em memória";
"请求方法 (Methods)" = "Métodos de solicitação (Métodos)";
"二进制日志格式" = "Formato do log binário";
"会话配置" = "Configuração de sessão";
"GoAccess 执行失败" = "Falha na execução do GoAccess";
"执行失败" = "Falha na execução";
"GC触发概率" = "Probabilidade de acionamento da coleta de lixo (GC)";
"保存成功" = "Salvo com sucesso";
"错误处理" = "Tratamento de erros";
"数据库名称已存在" = "O nome do banco de dados já existe";
"PHP运行" = "Executar PHP";
"未找到 Homebrew" = "Homebrew não encontrado";
"批量启用" = "Habilitar em lotes";
"状态码" = "código de status";
"加载错误日志失败: %@" = "Falha ao carregar o log de erros: %@";
"加载错误日志失败: %lld" = "Falha ao carregar o log de erros: %lld";
"每月切割一次" = "Cortar uma vez por mês";
"未找到可编辑的未管理条目，请改用“转为已管理后编辑”" = "Nenhuma entrada não gerenciada editável encontrada; use, em vez disso, \"Converter para gerenciada e, em seguida, editar\"";
"网站名" = "nome do site";
"生成成功" = "Gerado com sucesso";
"MySQL 性能优化" = "Otimização de desempenho do MySQL";
"编辑连接信息" = "Editar informações de conexão";
"客户端超时(0不超时)" = "Tempo limite do cliente (0 significa sem tempo limite)";
"✗ 端口 %@ 未监听" = "✗ A porta %@ não está escutando";
"✗ 端口 %lld 未监听" = "✗ A porta %lld não está escutando";
"SQL文件" = "Arquivo SQL";
"打开工作目录" = "Abrir diretório de trabalho";
"导出完成（部分失败）" = "Exportação concluída (parcialmente falhada)";
"申请证书" = "Solicitar um certificado";
"本地服务器" = "servidor local";
"查看更多历史记录" = "Ver mais histórico";
"最大连接数" = "Número máximo de conexões";
"保存配置时会自动创建备份" = "Um backup é criado automaticamente ao salvar a configuração";
"✅ 成功更新 %@ 个配置文件:" = "✅ Arquivos de configuração %@ atualizados com sucesso:";
"✅ 成功更新 %lld 个配置文件:" = "✅ %lld arquivos de configuração atualizados com sucesso:";
"证书即将过期，请及时续期" = "O certificado está prestes a expirar; renove-o em tempo hábil";
"✅ 扫描到证书: %@" = "✅ Escaneamento para o certificado: %@";
"✅ 扫描到证书: %lld" = "✅ Escaneamento para o certificado: %lld";
"代码格式化" = "Formatação de código";
"请选择批量操作" = "Selecione uma operação em lote";
"MySQL 未安装" = "O MySQL não está instalado";
"Root密码" = "Senha de root";
"加载站点数据失败，且无法恢复备份" = "Falha ao carregar os dados do site e impossibilidade de restaurar o backup";
"服务管理" = "Gerenciamento de serviços";
"%@个" = "%@";
"%lld个" = "%lld";
"无法执行配置检测: %@" = "Não foi possível executar a detecção de configuração: %@";
"无法执行配置检测: %lld" = "Não foi possível executar a detecção de configuração: %lld";
"cURL配置" = "Configuração do cURL";
"创建目录失败: %@" = "Falha ao criar diretório: %@";
"创建目录失败: %lld" = "Falha ao criar diretório: %lld";
"隐藏系统数据库" = "Ocultar banco de dados do sistema";
"安装 CA 证书失败" = "Falha na instalação do certificado CA";
"操作" = "Operação";
"📊 扫描完成，共找到 %@ 个配置" = "📊 Verificação concluída, um total de %@ configurações encontradas";
"📊 扫描完成，共找到 %lld 个配置" = "📊 Verificação concluída, um total de %lld configurações encontradas";
"保存服务缓存失败: %@" = "Falha ao salvar o cache do serviço: %@";
"保存服务缓存失败: %lld" = "Falha ao salvar o cache do serviço: %lld";
"日志级别" = "Nível de log";
"最大保持请求数" = "Número máximo de solicitações em espera";
"退出时保留服务" = "Manter o serviço ao sair";
"大小" = "tamanho";
"正在修改 Nginx 端口..." = "Modificando a porta do Nginx...";
"分析失败" = "Falha na análise";
"无备份" = "Sem backup";
"共 %@ 条" = "Total de %@ itens";
"共 %lld 条" = "Total de %lld itens";
"访问密码" = "senha de acesso";
"安装成功！" = "Instalação bem-sucedida!";
"本地 CA 证书" = "Certificado CA local";
"请输入加密/解密密码" = "Insira a senha de criptografia/descriptografia";
"建议值" = "Valor recomendado";
"设置路径" = "Definir caminho";
"使用 PureDB 文件存储用户信息，简单易用，适合小型站点" = "Usar arquivos PureDB para armazenar informações de usuário, simples e fácil de usar, adequado para sites pequenos";
"总请求" = "total de solicitações";
"刷新数据库列表" = "Atualizar lista de bancos de dados";
"PHP代码注入检测" = "Detecção de injeção de código PHP";
"版本数据来源于 Homebrew" = "Os dados da versão vêm do Homebrew";
"密码长度至少为8位" = "O comprimento da senha deve ter pelo menos 8 caracteres";
"本地" = "local";
"黑名单模式" = "modo de lista negra";
"Nginx 反向代理到 Apache 的 HTTP 端口" = "Proxy reverso do Nginx para a porta HTTP do Apache";
"• 建议在导入前备份现有数据" = "• Recomenda-se fazer backup dos dados existentes antes da importação";
"保持连接超时" = "Tempo limite de conexão persistente";
"未找到 MySQL 客户端" = "Cliente MySQL não encontrado";
"认证策略" = "Política de autenticação";
"开始扫描" = "Iniciar verificação";
"• 本地域名（如 localhost、.local、.test）无法使用" = "• Nomes de domínio locais (como localhost, .local, .test) não podem ser utilizados";
"暂不迁移" = "Ainda não está sendo migrado";
"网络连接中断" = "Conexão de rede interrompida";
"nginx -t 输出:\\n%@" = "Saída do comando nginx -t:\\n%@";
"nginx -t 输出:\\n%lld" = "Saída do comando nginx -t:\\n%lld";
"搜索数据库..." = "Pesquisando no banco de dados...";
"\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=" = "# Configuração do MySQL para o Pure-FTPd\n        # Por favor, modifique a seguinte configuração de acordo com a situação real\n\n        MYSQLServer localhost\n        MYSQLPort 3306\n        MYSQLSocket /tmp/mysql.sock\n        MYSQLUser pureftpd\n        MYSQLPassword sua_senha_aqui\n        MYSQLDatabase pureftpd\n        MYSQLCrypt md5\n\n        # Declaração da consulta SQL\n        MYSQLGetPW SELECT Password FROM users WHERE User=";
"占用" = "ocupar";
"HTML格式错误" = "Erro de formato HTML";
"%@ 停止失败,请检查日志" = "%@ Falha ao parar. Verifique o log, por favor.";
"%lld 停止失败,请检查日志" = "%lld Falha ao parar. Verifique o log, por favor.";
"• 修改密码后，使用该用户的应用需要更新配置" = "• Após alterar a senha, os aplicativos que utilizam este usuário precisam atualizar suas configurações";
"网站高级设置 - %@" = "Configurações avançadas do site – %@";
"网站高级设置 - %lld" = "Configurações avançadas do site – %lld";
"正在配置 Apache..." = "Configurando o Apache...";
"启用 AOF 持久化" = "Habilitar persistência AOF";
"配置文件迁移" = "Migração do arquivo de configuração";
"正在检测域名是否可以访问到当前站点" = "Verificando se o nome de domínio pode acessar o site atual";
"📁 扫描目录: %@" = "📁 Diretório de verificação: %@";
"📁 扫描目录: %lld" = "📁 Diretório de verificação: %lld";
"无法自动修复：未在工作空间中找到与域名 \\(config.parsed.domains.joined(separator: " = "Não é possível corrigir automaticamente: separador não encontrado no workspace com nome de domínio \\(config.parsed.domains.joined((separator:";
"删除用户" = "Excluir usuário";
"请输入服务器地址" = "Por favor, insira o endereço do servidor";
"未找到证书配置" = "Configuração de certificado não encontrada";
">\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                # Configuração do PHP-FPM\n                <FilesMatch \\.php$>\n                    SetHandler";
"正在验证服务状态..." = "Verificando o status do serviço...";
"工作空间未初始化" = "O espaço de trabalho não foi inicializado";
"点击下方按钮开始验证域名可达性" = "Clique no botão abaixo para começar a verificar a acessibilidade do domínio";
"• CA 证书将用于签发所有站点 SSL 证书" = "• O certificado CA será usado para emitir todos os certificados SSL do site";
"最大输入变量数" = "Número máximo de variáveis de entrada";
"\n            # Redis 配置\n            port 6379\n            bind 127.0.0.1\n            protected-mode yes\n            maxmemory 0\n            " = "# Configuração do Redis\n            porta 6379\n            bind 127.0.0.1\n            modo protegido sim\n            maxmemory 0";
"配置解析错误: %@" = "Erro ao analisar a configuração: %@";
"配置解析错误: %lld" = "Erro ao analisar a configuração: %lld";
"正在修改 Apache 端口..." = "Modificando a porta do Apache...";
"选择目录路径" = "Selecione o caminho do diretório";
"    # 原始条件: %@\\n" = "# Condição original: %@\\n";
"    # 原始条件: %lld\\n" = "# Condição original: %lld\\n";
"大写输出" = "Saída em maiúsculas";
"刷新 IP 列表" = "Atualizar lista de IPs";
"系统工具" = "ferramentas do sistema";
"立即迁移" = "Migrar agora";
"生成中..." = "Gerando...";
"批量导入: %@" = "Importação em lote: %@";
"批量导入: %lld" = "Importação em lote: %lld";
"用于加速PHP脚本" = "Usado para acelerar scripts PHP";
"• 支持自动申请和续期证书" = "• Suporta aplicação e renovação automáticas de certificados";
"仍然继续" = "ainda continuar";
"# 内置扩展，请在 php.ini 中禁用" = "# Extensão embutida, desative-a no arquivo php.ini";
"生成随机密码" = "Gerar senha aleatória";
"未检测到相关软件" = "Nenhum software relacionado detectado";
"关闭" = "fechar";
"重新生成功能开发中" = "A função de regeneração está em desenvolvimento";
"配置 %@" = "Configuração %@";
"配置 %lld" = "Configuração %lld";
"休眠超时" = "tempo limite de espera";
"软件: %@" = "Software: %@";
"软件: %lld" = "Software: %lld";
"仅CLI模式可用" = "Disponível apenas no modo CLI";
"✅ 已删除配置文件: %@" = "✅ Arquivo de configuração excluído: %@";
"✅ 已删除配置文件: %lld" = "✅ Arquivo de configuração excluído: %lld";
"清空" = "Limpar";
"开始验证" = "Iniciar verificação";
"设置PHP命令行版本" = "Definir versão da linha de comando do PHP";
"错误日志 (最近10行):\\n%@" = "Log de erros (últimas 10 linhas):\\n%@";
"错误日志 (最近10行):\\n%lld" = "Log de erros (últimas 10 linhas):\\n%lld";
"添加连接" = "Adicionar conexão";
"打开站点" = "abrir site";
"外部编辑器" = "editor externo";
"建议使用 8000-8999 范围内的端口" = "Recomenda-se usar portas na faixa de 8000 a 8999";
"扫描网站日志中的安全威胁" = "Verificar logs do site em busca de ameaças à segurança";
"CLI启用OPcache" = "CLI para habilitar o OPcache";
"\n            # 禁止访问隐藏文件\n            location ~ /\\\\. {\n                deny all;\n            }\n        }\n        " = "# Desabilitar acesso a arquivos ocultos\n            location ~ /\\\\. {\n                deny all;\n            }\n        }";
"正在配置..." = "Configurando...";
"深色" = "Escuro";
"支持分析任意 Apache/Nginx 访问日志" = "Suporta análise de quaisquer logs de acesso do Apache/Nginx";
")/\n                </VirtualHost>\n\n                # HTTPS VirtualHost\n                # Apache 后端端口：%@\n                <VirtualHost *:%@>\n                    ServerName \\(domains.first ?? " = ")/\n                </VirtualHost>\n\n                # VirtualHost HTTPS\n                # Porta de backend do 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                # Porta de backend do Apache: %lld\n                <VirtualHost *:%lld>\n                    ServerName \\(domains.first ??";
"MySQL 端口" = "Porta do MySQL";
"端口已更新，但部分配置同步失败" = "A porta foi atualizada, mas algumas sincronizações de configuração falharam";
"%@ (本机)" = "%@ (nativo)";
"%lld (本机)" = "%lld (nativo)";
"导入前将删除数据库中的所有现有数据" = "Todos os dados existentes no banco de dados serão excluídos antes da importação";
"SSL 证书正常（剩余 %@ 天）" = "Certificado SSL válido (%@ dias restantes)";
"SSL 证书正常（剩余 %lld 天）" = "Certificado SSL válido (%lld dias restantes)";
"安装 Apache 失败" = "Falha ao instalar o Apache";
"成功导入 %@ 个站点，失败 %@ 个" = "Sites %@ importados com sucesso, falha em %@";
"成功导入 %lld 个站点，失败 %lld 个" = "Sites %lld importados com sucesso, falha em %lld";
"等待转换" = "Aguardando conversão";
"常用配置" = "Configuração comum";
"热门请求" = "Solicitações populares";
"反选" = "Contraeleição";
"• 修改远程数据库密码需要提供正确的当前密码" = "• Alterar a senha do banco de dados remoto exige fornecer a senha atual correta";
"适用于 %@ 内存的服务器" = "Servidor para memória %@";
"适用于 %lld 内存的服务器" = "Servidor para memória %lld";
"免费的 SSL/TLS 证书，适用于生产环境" = "Certificado SSL/TLS gratuito para ambientes de produção";
"数据库数量" = "Número de bancos de dados";
"批量恢复" = "Recuperação em lote";
"正在安装 Apache..." = "Instalando o Apache...";
"%@ 错误日志" = "Log de erros %@";
"%lld 错误日志" = "Log de erros %lld";
"全选" = "Selecionar tudo";
"扫描目录" = "Verificar diretório";
"邮箱地址" = "Endereço de e-mail";
"POST 最大大小" = "Tamanho máximo de POST";
"正在加载错误日志..." = "Carregando log de erros...";
"例如 /admin/，根目录请填 /" = "Por exemplo, /admin/, preencha / para o diretório raiz.";
"页面浏览量" = "visualizações de página";
"带宽" = "Largura de banda";
"SSL 证书" = "Certificado SSL";
"\n            # Apache 后端端口：%@\n            <VirtualHost *:%@>\n                ServerName \\(domains.first ?? " = "# Porta de backend do Apache: %@\n            <VirtualHost *:%@>\n                ServerName \\(domains.first ??";
"\n            # Apache 后端端口：%lld\n            <VirtualHost *:%lld>\n                ServerName \\(domains.first ?? " = "# Porta de backend do Apache: %lld\n            <VirtualHost *:%lld>\n                ServerName \\(domains.first ??";
"将以下设置应用到选中的 %@ 个站点" = "Aplicar as seguintes configurações aos %@ sites selecionados";
"将以下设置应用到选中的 %lld 个站点" = "Aplicar as seguintes configurações aos %lld sites selecionados";
"下一步" = "Próximo passo";
"[BatchImport] 扫描目录失败: %@" = "[BatchImport] Falha ao escanear o diretório: %@";
"[BatchImport] 扫描目录失败: %lld" = "[BatchImport] Falha ao escanear o diretório: %lld";
"通用查询日志" = "Log de consultas gerais";
"暂无端口信息" = "Ainda não há informações sobre a porta";
"語言" = "idioma";
"运行目录:" = "Diretório de execução:";
"容量" = "Capacidade";
"进程 PID" = "PID do processo";
"无法读取文件" = "Não é possível ler o arquivo";
"颁发机构: JihePHPEnv Local CA" = "Autoridade emissora: JihePHPEnv Local CA";
"配置已恢复" = "Configuração restaurada";
"暂无网站" = "Nenhum site web ainda";
"当前密码" = "senha atual";
"✗ 复制错误页面目录失败: %@" = "✗ Falha ao copiar o diretório da página de erro: %@";
"✗ 复制错误页面目录失败: %lld" = "✗ Falha ao copiar o diretório da página de erro: %lld";
"复制失败" = "Falha na cópia";
"高精度数学运算" = "Operações matemáticas de alta precisão";
"Web 服务器:" = "Servidor web:";
"ImageMagick高性能图形库" = "Biblioteca gráfica de alto desempenho ImageMagick";
"全部" = "Todos";
"编码解码" = "Codificação e decodificação";
"MySQL 未运行" = "O MySQL não está em execução";
"CA 证书已成功生成并安装到 Keychain。" = "O certificado CA foi gerado e instalado com sucesso na Keychain.";
"剩余%@天" = "Restam%@ dias";
"剩余%lld天" = "Restam%lld dias";
"最近30天" = "Últimos 30 dias";
"根目录" = "diretório raiz";
"正在加载 PHP %@ 扩展列表..." = "Carregando lista de extensões PHP %@...";
"正在加载 PHP %lld 扩展列表..." = "Carregando lista de extensões PHP %lld...";
"迁移到工作空间" = "Migrar para o workspace";
"\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        " = "#Configuração padrão do site\n        # Gerado automaticamente pelo 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        " = "#Configuração padrão do site\n        # Gerado automaticamente pelo 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 diretório de usuário sem permissões sudo";
"MySQL 重启失败 (退出码: %@)" = "Falha ao reiniciar o MySQL (código de saída: %@)";
"MySQL 重启失败 (退出码: %lld)" = "Falha ao reiniciar o MySQL (código de saída: %lld)";
"php/{版本}/" = "php/{version}/";
"暂无错误记录" = "Ainda não há registros de erro";
"终止进程失败，可能需要管理员权限" = "Falha ao encerrar o processo; podem ser necessários direitos de administrador";
"基础配置" = "Configuração básica";
"成功: %@ 个，失败: %@ 个\\n导出目录: %@" = "Sucesso: %@, falhas: %@\\nDiretório de exportação: %@";
"成功: %lld 个，失败: %lld 个\\n导出目录: %lld" = "Sucesso: %lld, falhas: %lld\\nDiretório de exportação: %lld";
"\n\n                # 密码保护: %@\n                location %@ {\n                    auth_basic " = "# Proteção por senha: %@\n                localização %@";
"\n\n                # 密码保护: %lld\n                location %lld {\n                    auth_basic " = "# Proteção por senha: %lld\n                localização %lld {\n                    auth_basic";
"SQL 文件" = "Arquivo SQL";
"暂无访问日志" = "Ainda não há log de acesso";
"Web服务器类型: %@" = "Tipo de servidor web: %@";
"Web服务器类型: %lld" = "Tipo de servidor web: %lld";
"服务状态" = "Status do serviço";
"跨域访问 CORS 配置" = "Configuração CORS para acesso entre domínios";
"线程堆栈大小" = "Tamanho da pilha de threads";
"表缓存数量" = "Número de caches de tabelas";
"重启" = "Reiniciar";
"服务器地址" = "Endereço do servidor";
"重启数据库" = "Reiniciar banco de dados";
"缓冲池实例数" = "Número de instâncias do pool de buffers";
"\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        " = "# Configuração do phpMyAdmin\n            localização /phpmyadmin {\n                alias %@;\n                índice index.php;\n\n                localização ~ \\\\.php$ {\n                    incluir 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        " = "# Configuração do phpMyAdmin\n            localização /phpmyadmin {\n                alias %lld;\n                índice index.php;\n\n                localização ~ \\\\.php$ {\n                    incluir 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 diretório do site";
"\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                " = "# Bloco do servidor HTTP\n                servidor {\n                    escutar 80;\n                    nome_do_servidor %@;\n                    raiz %@;\n                    índice index.php index.html index.htm;\n\n                    # Log de acesso\n                    access_log %@;\n                    error_log %@;\n\n                    localização/{\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    localização ~ \\\\.php$ {\n                        incluir 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                    # Desabilitar acesso a arquivos ocultos\n                    localização ~ /\\\\. {\n                        negar tudo;\n                    }\n                }\n\n                # Bloco do servidor HTTPS\n                servidor {\n                    escutar 443 ssl;\n                    nome_do_servidor %@;\n                    raiz %@;\n                    índice index.php index.html index.htm;\n\n                    # Configuração do certificado SSL\n                    ssl_certificate %@;\n                    ssl_certificate_key %@;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n\n                    # Log de acesso\n                    access_log %@;\n                    error_log %@;\n\n                    localização/{\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    localização ~ \\\\.php$ {\n                        incluir 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                    # Desabilitar acesso a arquivos ocultos\n                    localização ~ /\\\\. {\n                        negar tudo;\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                " = "# Bloco do servidor HTTP\n                servidor {\n                    escutar 80;\n                    nome_do_servidor %lld;\n                    raiz %lld;\n                    índice index.php index.html index.htm;\n\n                    # Log de acesso\n                    access_log %lld;\n                    error_log %lld;\n\n                    localização/{\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    localização ~ \\\\.php$ {\n                        incluir 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                    # Desabilitar acesso a arquivos ocultos\n                    localização ~ /\\\\. {\n                        negar tudo;\n                    }\n                }\n\n                # Bloco do servidor HTTPS\n                servidor {\n                    escutar 443 ssl;\n                    nome_do_servidor %lld;\n                    raiz %lld;\n                    índice index.php index.html index.htm;\n\n                    # Configuração do 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                    # Log de acesso\n                    access_log %lld;\n                    error_log %lld;\n\n                    localização/{\n                        try_files $uri $uri/ /index.php?$args;\n                    }\n\n                    localização ~ \\\\.php$ {\n                        incluir 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                    # Desabilitar acesso a arquivos ocultos\n                    localização ~ /\\\\. {\n                        negar tudo;\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            " = "servidor {\n                escutar 80;\n                nome_do_servidor %@;\n                raiz %@;\n                índice index.php index.html index.htm;\n\n                # Log de acesso\n                access_log %@;\n                error_log %@;\n\n                localização/{\n                    try_files $uri $uri/ /index.php?$args;\n                }\n\n                localização ~ \\\\.php$ {\n                    incluir 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                # Desabilitar acesso a arquivos ocultos\n                localização ~ /\\\\. {\n                    negar tudo;\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            " = "servidor {\n                escutar 80;\n                nome_do_servidor %lld;\n                raiz %lld;\n                índice index.php index.html index.htm;\n\n                # Log de acesso\n                access_log %lld;\n                error_log %lld;\n\n                localização/{\n                    try_files $uri $uri/ /index.php?$args;\n                }\n\n                localização ~ \\\\.php$ {\n                    incluir 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                # Desabilitar acesso a arquivos ocultos\n                localização ~ /\\\\. {\n                    negar tudo;\n                }\n            }";
"批量启动" = "Início em lote";
"代码格式化工具" = "Ferramentas de formatação de código";
"CA签发" = "Emitido pela AC";
"返回站点列表" = "Voltar à lista de sites";
"phpMyAdmin 安装成功" = "phpMyAdmin instalado com sucesso";
"选择运行目录..." = "Selecionar diretório de execução...";
"无法解析 RewriteRule: %@" = "Não foi possível analisar a RewriteRule: %@";
"无法解析 RewriteRule: %lld" = "Não foi possível analisar a RewriteRule: %lld";
"无法执行 nginx -t: %@" = "Não foi possível executar nginx -t: %@";
"无法执行 nginx -t: %lld" = "Não foi possível executar o nginx -t: %lld";
"    # 请参考 Apache mod_proxy_fcgi 配置\\n" = "# Consulte a configuração do módulo Apache mod_proxy_fcgi\\n";
"[SiteManager] 添加 hosts 条目失败: %@ - %@" = "[SiteManager] Falha ao adicionar entrada no arquivo hosts: %@ - %@";
"[SiteManager] 添加 hosts 条目失败: %lld - %lld" = "[SiteManager] Falha ao adicionar entrada no arquivo hosts: %lld - %lld";
"\n        # MySQL 数据库认证\n        MySQLConfigFile             %@/pureftpd-mysql.conf\n\n        " = "# Autenticação do banco de dados MySQL\n        MySQLConfigFile %@/pureftpd-mysql.conf";
"\n        # MySQL 数据库认证\n        MySQLConfigFile             %lld/pureftpd-mysql.conf\n\n        " = "# Autenticação do banco de dados MySQL\n        MySQLConfigFile %lld/pureftpd-mysql.conf";
"日志记录" = "registro de eventos";
"连接失败" = "Falha na conexão";
"开源的PHP程序调试器" = "Depurador de programas PHP de código aberto";
"30 分钟" = "30 minutos";
"正在重启 Apache..." = "Reiniciando o Apache...";
"未找到匹配的日志文件: %@" = "Nenhum arquivo de log correspondente encontrado: %@";
"未找到匹配的日志文件: %lld" = "Nenhum arquivo de log correspondente encontrado: %lld";
"正在安装 acme.sh..." = "Instalando acme.sh...";
"首次启动设置向导" = "Iniciando o assistente de configuração pela primeira vez";
"正在应用..." = "Aplicando...";
"请输入要压缩的代码" = "Insira o código a ser compactado";
"))\n\n                    # 强制跳转到 HTTPS\n                    Redirect permanent / https://\\(domains.first ?? " = "))\n\n                    # Forçar redirecionamento para HTTPS\n                    Redirect permanent / https://\\(domains.first ??";
"留空则使用域名" = "Deixe em branco para usar o nome de domínio";
"单位: %@" = "Unidade: %@";
"单位: %lld" = "Unidade: %lld";
"运行目录" = "Diretório de execução";
"mcrypt加密/解密" = "Criptografia/descriptografia mcrypt";
"独立访客" = "Visitante único";
"高级" = "Avançado";
"没有写入权限" = "Sem permissão de gravação";
"数据库配置" = "Configuração do banco de dados";
"搜索服务或端口..." = "Pesquisar um serviço ou porta...";
"使用 GoAccess 生成详细的日志分析报告" = "Usar o GoAccess para gerar relatórios detalhados de análise de logs";
"随机读取缓冲区大小" = "Tamanho do buffer de leitura aleatória";
"缓冲池大小" = "Tamanho do pool de buffers";
"添加条目" = "Adicionar entrada";
"Nginx SSL listen 指令" = "Diretiva de escuta SSL do Nginx";
"数据库已备份" = "O banco de dados foi copiado de segurança";
"%@ 重启成功" = "%@ Reinicialização bem-sucedida";
"%lld 重启成功" = "%lld Reinicialização bem-sucedida";
"FTP 账号" = "Conta FTP";
"\n\n                    # SSL 配置\n                    SSLEngine on\n                    SSLCertificateFile " = "# Configuração SSL\n                    SSLEngine on\n                    SSLCertificateFile";
"当前 (0)" = "Atual (0)";
"远程数据库连接已保存，请使用以下工具进行管理" = "A conexão remota com o banco de dados foi salva; use as ferramentas a seguir para gerenciá-la";
"站点 %@ 的配置文件已重新生成" = "O arquivo de configuração para o site %@ foi regenerado";
"站点 %lld 的配置文件已重新生成" = "O arquivo de configuração para o site %lld foi regenerado";
"• 证书将自动配置到站点" = "• O certificado será configurado automaticamente no site";
"配置、数据、日志将存储在此目录" = "A configuração, os dados e os logs serão armazenados neste diretório";
"服务列表" = "Lista de serviços";
"已复制" = "Copiado";
"共 %@ 个端口" = "Total de %@ portas";
"共 %lld 个端口" = "Total de %lld portas";
"准备安装 Pure-FTPd..." = "Preparando a instalação do Pure-FTPd...";
"请输入连接名称" = "Por favor, insira um nome para a conexão";
"会话存储方式" = "Método de armazenamento da sessão";
"备份失败: %@" = "Falha no backup: %@";
"备份失败: %lld" = "Falha no backup: %lld";
"✓ 检测到版本: %@" = "✓ Versão detectada: %@";
"✓ 检测到版本: %lld" = "✓ Versão detectada: %lld";
"开始检查" = "Iniciando verificação";
"未管理" = "Não gerenciado";
"执行 brew 命令失败: %@" = "Falha ao executar o comando brew: %@";
"执行 brew 命令失败: %lld" = "Falha ao executar o comando brew: %lld";
"域名:" = "Nome de domínio:";
"部分 RewriteCond 需要手动转换" = "Algumas diretivas RewriteCond exigem conversão manual";
"HTTP 自动跳转到 HTTPS" = "HTTP redireciona automaticamente para HTTPS";
"SourceGuardian加密脚本解密" = "Descriptografia de script criptografado pelo SourceGuardian";
"完全退出" = "Sair completamente";
"查看" = "Visualizar";
"暂无限制规则" = "Ainda não há regras restritivas";
"过期时间: 已过期" = "Data de expiração: Expirada";
"淺色" = "cor clara";
"已被" = "foi";
"已启用 %@ / %@" = "Habilitado %@ / %@";
"已启用 %lld / %lld" = "Habilitado %lld / %lld";
"版本: %@" = "Versão: %@";
"版本: %lld" = "Versão: %lld";
"站点已创建成功，但 %@ Web 服务器当前未运行。\\n\\n请启动 %@ 后，网站才能正常访问。\\n\\n您可以在「服务管理」中启动 %@。" = "O site foi criado com sucesso, mas o servidor web %@ não está atualmente em execução.\n\nPor favor, ative %@ antes que o site possa ser acessado normalmente.\n\nVocê pode ativar %@ na Gerenciamento de Serviços.";
"站点已创建成功，但 %lld Web 服务器当前未运行。\\n\\n请启动 %lld 后，网站才能正常访问。\\n\\n您可以在「服务管理」中启动 %lld。" = "O site foi criado com sucesso, mas o servidor web %lld não está atualmente em execução.\n\nPor favor, ative %lld antes que o site possa ser acessado normalmente.\n\nVocê pode ativar %lld na Gerenciamento de Serviços.";
"默认" = "Padrão";
"• 默认文档，每行一个，优先级由上至下" = "• Documento padrão, um por linha, prioridade da parte superior para a inferior";
"✗ 证书已过期" = "✗ O certificado expirou";
"改密" = "Alterar senha";
"acme.sh 未安装" = "O acme.sh não está instalado";
"多字节字符串处理" = "Processamento de cadeias de caracteres multibyte";
"执行终止命令失败: %@" = "Falha ao executar o comando de encerramento: %@";
"执行终止命令失败: %lld" = "Falha ao executar o comando de encerramento: %lld";
"保存失败: %@" = "Falha ao salvar: %@";
"保存失败: %lld" = "Falha ao salvar: %lld";
"本地解析" = "análise local";
"读取失败: %@" = "Falha ao ler: %@";
"读取失败: %lld" = "Falha ao ler: %lld";
"无法解析输入的文本" = "Não é possível analisar o texto de entrada";
"设置后可避免权限问题，便于备份管理" = "Após a configuração, problemas de permissão podem ser evitados e a gestão de backups pode ser facilitada.";
"CGI路径修复" = "Reparação do caminho CGI";
"一个目录包含所有数据，轻松备份迁移" = "Um diretório contém todos os dados, facilitando o backup e a migração";
"数据库备注信息" = "Informações sobre observações do banco de dados";
"请先停止服务再卸载" = "Por favor, interrompa primeiro o serviço e, em seguida, desinstale-o.";
"端口不能为空" = "A porta não pode estar vazia";
"   - 端口: %@ }.joined(separator: " = "- Portas: %@ }.joined(separator:";
"   - 端口: %lld }.joined(separator: " = "- Portas: %lld }.joined(separator:";
"• 证书已自动安装到 Keychain 并设为信任" = "• O certificado é instalado automaticamente no Keychain e definido como confiável";
"已选择 %@ 个站点" = "%@ sites selecionados";
"已选择 %lld 个站点" = "%lld sites selecionados";
"Nginx SSL终止" = "Encerramento SSL do Nginx";
"default.conf: %@ -> %@ (软链接)" = "default.conf: %@ -> %@ (link simbólico)";
"default.conf: %lld -> %lld (软链接)" = "default.conf: %lld -> %lld (link simbólico)";
"缓存配置功能正在开发中" = "A funcionalidade de configuração de cache está em desenvolvimento";
"读取 hosts 原始内容失败：%@" = "Falha ao ler o conteúdo original do arquivo hosts: %@";
"读取 hosts 原始内容失败：%lld" = "Falha ao ler o conteúdo original do arquivo hosts: %lld";
"仅显示用户 %@) 的进程" = "Exibir apenas os processos do usuário %@";
"仅显示用户 %lld) 的进程" = "Exibir apenas os processos do usuário %lld)";
"系统数据库" = "Banco de dados do sistema";
"私钥文件缺失" = "Arquivo da chave privada ausente";
"说明" = "Descrição";
"正在解压安装包..." = "Descompactando o pacote de instalação...";
"客户端最大请求体" = "Corpo máximo da solicitação do cliente";
"安装完成！" = "A instalação foi concluída!";
"OPcache 内存" = "Memória do OPcache";
"无法启动站点：%@ Web 服务器未运行。请先在「服务管理」中启动 %@ 后再启动站点。" = "Não é possível iniciar o site: %@ O servidor web não está em execução. Inicie %@ em \"Gerenciamento de Serviços\" antes de iniciar o site.";
"无法启动站点：%lld Web 服务器未运行。请先在「服务管理」中启动 %lld 后再启动站点。" = "Não é possível iniciar o site: %lld O servidor web não está em execução. Inicie %lld em \"Gerenciamento de Serviços\" antes de iniciar o site.";
"批量操作" = "Operações em lote";
"随机读缓冲区" = "Buffer de leitura aleatória";
"同步配置文件" = "Sincronizar arquivos de configuração";
"GoAccess 未安装" = "O GoAccess não está instalado";
"创建配置文件失败: %@" = "Falha ao criar o arquivo de configuração: %@";
"创建配置文件失败: %lld" = "Falha ao criar o arquivo de configuração: %lld";
"删除失败" = "Falha ao excluir";
"写入 hosts 原始内容失败：%@" = "Falha ao gravar o conteúdo original do arquivo hosts: %@";
"写入 hosts 原始内容失败：%lld" = "Falha ao gravar o conteúdo original do arquivo hosts: %lld";
"# %@ 配置文件\\n" = "# Arquivo de configuração %@\\n";
"# %lld 配置文件\\n" = "# Arquivo de configuração %lld\\n";
"• IP 地址通常为 127.0.0.1（本地）" = "• O endereço IP geralmente é 127.0.0.1 (local)";
"与 %@ 配置冲突" = "Conflito com a configuração de %@";
"与 %lld 配置冲突" = "Conflito com a configuração de %lld";
"总访问量" = "visitas totais";
"加载更多" = "carregar mais";
"• 修改root密码后，所有使用该密码的应用程序都需要更新配置" = "• Após alterar a senha de root, todos os aplicativos que usam essa senha precisam atualizar suas configurações";
"%@ 年" = "%@ anos";
"%lld 年" = "%lld anos";
"✅ 已为 Nginx 代理生成 Apache 后端配置: %@" = "✅ Configuração de backend do Apache gerada com sucesso para proxy Nginx: %@";
"✅ 已为 Nginx 代理生成 Apache 后端配置: %lld" = "✅ Configuração de backend do Apache gerada com sucesso para proxy Nginx: %lld";
"恢复成功" = "Recuperação bem-sucedida";
") 配置正常" = ") a configuração está normal";
"(系统)" = "(sistema)";
"部分运行" = "Executando parcialmente";
"预览" = "Pré-visualização";
"请输入数据库名称" = "Por favor, insira o nome do banco de dados";
"确定要终止进程 %@ (PID: %@) 吗？\\n\\n这将停止占用端口 %@ 的服务。" = "Tem certeza de que deseja encerrar o processo %@ (PID: %@)? \\n\\nIsso interromperá o serviço que está utilizando a porta %@.";
"确定要终止进程 %lld (PID: %lld) 吗？\\n\\n这将停止占用端口 %lld 的服务。" = "Tem certeza de que deseja encerrar o processo %lld (PID: %lld)? \\n\\nIsso interromperá o serviço que está utilizando a porta %lld.";
"\n                </FilesMatch>\n\n                # 访问日志\n                ErrorLog " = "</FilesMatch>\n\n                #Registro de acesso\n                ErrorLog";
"字符集" = "conjunto de caracteres";
"Base64 解码失败" = "Falha na decodificação Base64";
"有效期（天）" = "Período de validade (dias)";
"本地数据库" = "banco de dados local";
"配置键" = "chave de configuração";
"设置默认站点" = "Definir site padrão";
"运行环境" = "Ambiente de operação";
"正在修改 MySQL 端口..." = "Modificando a porta do MySQL...";
"目标数据库" = "banco de dados de destino";
"数据盘" = "disco de dados";
"扫描 Nginx 和 Apache 配置目录，导入已有站点配置" = "Verificar os diretórios de configuração do Nginx e do Apache e importar as configurações de sites existentes";
"未找到 PHP-FPM 配置文件" = "Arquivo de configuração do PHP-FPM não encontrado";
"从配置文件导入: %@" = "Importar do arquivo de configuração: %@";
"从配置文件导入: %lld" = "Importar do arquivo de configuração: %lld";
"需要从官网下载" = "É necessário baixar do site oficial";
"(已停止)" = "(parado)";
"加载失败" = "Falha ao carregar";
"显示 %@ 个用户端口" = "Exibir %@ portas de usuário";
"显示 %lld 个用户端口" = "Exibir %lld portas de usuário";
"查看差异" = "Visualizar diferenças";
"导入进度" = "Progresso da importação";
"未找到版本信息" = "Informações de versão não encontradas";
"网络不通？手动安装" = "Sem conexão com a internet? Instalação manual";
"未知" = "desconhecido";
"网站健康检查" = "Verificação de integridade do site";
"已停止" = "Parado";
"选择自定义扫描目录" = "Selecionar um diretório personalizado para verificação";
"常规设置" = "Configurações gerais";
"HTTPS防篡站" = "Site HTTPS protegido contra adulterações";
"[VhostScan] 日志文件已创建: %@" = "[VhostScan] Arquivo de log criado: %@";
"[VhostScan] 日志文件已创建: %lld" = "[VhostScan] Arquivo de log criado: %lld";
"保存数据失败: %@" = "Falha ao salvar os dados: %@";
"保存数据失败: %lld" = "Falha ao salvar os dados: %lld";
"强制重置模式" = "Forçar modo de reinicialização";
"端口详情 - %@" = "Detalhes da porta - %@";
"端口详情 - %lld" = "Detalhes da porta - %lld";
"重新验证" = "revalidar";
"ZIP压缩文件处理" = "Processamento de arquivo compactado ZIP";
"• Let's Encrypt 仅适用于可公网访问的域名" = "• O Let's Encrypt funciona apenas em domínios acessíveis publicamente";
"服务端口" = "porta do serviço";
"默认存储引擎" = "Mecanismo de armazenamento padrão";
"正在启动中..." = "Iniciando...";
"按文件大小切割" = "Reduzir para o tamanho do arquivo";
"正在检测配置文件语法..." = "Detectando a sintaxe do arquivo de configuração...";
"mkcert 生成证书失败" = "Falha ao gerar o certificado com mkcert";
"请确保已安装 Homebrew 且路径配置正确" = "Certifique-se de que o Homebrew está instalado e que o caminho está configurado corretamente";
"打开 phpMyAdmin" = "Abrir phpMyAdmin";
"))\\n有效期: %@ 天" = "))\\nPeríodo de validade: %@ dias";
"))\\n有效期: %lld 天" = "))\\nPeríodo de validade: %lld dias";
"网站目录" = "diretório do site";
"配置文件缺失" = "Arquivo de configuração ausente";
"无法检测 Root 密码" = "Não foi possível detectar a senha de root";
"• 证书将自动安装到 Keychain 并设为信任" = "• O certificado será instalado automaticamente no Keychain e definido como confiável";
"未找到 Homebrew，请先安装 Homebrew" = "Homebrew não encontrado, por favor instale o Homebrew primeiro";
"(运行中)" = "(em execução)";
"源配置文件不存在" = "O arquivo de configuração de origem não existe";
"临时表大小" = "Tamanho da tabela temporária";
"Hosts 编辑器" = "Editor de Hosts";
"哈希算法" = "Algoritmo de hash";
"• Nginx Combined 格式" = "• Formato combinado do Nginx";
"彻底删除" = "Excluir completamente";
"设置访问密码" = "Definir senha de acesso";
"Nginx FastCGI 连接端口" = "Porta de conexão FastCGI do Nginx";
"mysqldump 执行失败" = "Falha na execução do mysqldump";
"正在启动 %@..." = "Iniciando %@...";
"正在启动 %lld..." = "Iniciando %lld...";
"无法读取文件: %@" = "Não foi possível ler o arquivo: %@";
"无法读取文件: %lld" = "Não foi possível ler o arquivo: %lld";
"域名可以正常访问，可以继续申请证书" = "O nome de domínio pode ser acessado normalmente e você pode continuar solicitando um certificado.";
"配置文件错误" = "Erro no arquivo de configuração";
"目录结构预览" = "Visualização da estrutura de diretórios";
"点击应用推荐值" = "Clique para aplicar recomendações";
"站点证书已成功签发。\\n\\n域名: \\(domainList.joined(separator: " = "O certificado do site foi emitido com sucesso. \\n\\nNome de domínio: \\(domainList.joined(separator:";
"简" = "Jane";
"添加限制规则" = "Adicionar regras de restrição";
"刷新日志" = "Atualizar log";
"恢复默认" = "Restaurar padrão";
"✅ 已从 JSON 加载 %@ 个证书" = "✅ %@ certificados carregados do JSON";
"✅ 已从 JSON 加载 %lld 个证书" = "✅ %lld certificados carregados do JSON";
"慢查询日志文件" = "Arquivo de log de consultas lentas";
"无法打开文件位置：路径未知" = "Não foi possível abrir o local do arquivo: caminho desconhecido";
"启动 %@ 失败，请展开查看详细信息" = "Falha ao iniciar %@. Expanda para visualizar os detalhes.";
"启动 %lld 失败，请展开查看详细信息" = "Falha ao iniciar %lld. Expanda para visualizar os detalhes.";
"PHP版本已停止" = "A versão do PHP foi descontinuada";
"\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                # 🔄 Configuração de proxy reverso do Nginx (backend Apache)\n                # Esta configuração encaminha solicitações para o servidor backend Apache\n                # Porta frontal: 80/443 (Nginx)\n                # Porta de backend: %@/%@ (Apache)\n                # ============================================\n\n                # Bloco do Servidor HTTP - Proxy reverso para o 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                # Bloco do Servidor HTTPS - Proxy reverso para o Apache\n                server {\n                    listen 443 ssl;\n                    server_name %@;\n\n                    # Configuração do 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                # 🔄 Configuração de proxy reverso do Nginx (backend Apache)\n                # Esta configuração encaminha solicitações para o servidor backend Apache\n                # Porta frontal: 80/443 (Nginx)\n                # Porta de backend: %lld/%lld (Apache)\n                # ============================================\n\n                # Bloco do Servidor HTTP - Proxy reverso para o 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                # Bloco do Servidor HTTPS - Proxy reverso para o Apache\n                server {\n                    listen 443 ssl;\n                    server_name %lld;\n\n                    # Configuração do 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 ?";
"选择任意日志文件进行分析" = "Selecione qualquer arquivo de log para análise";
"• 完成后 MySQL 服务将自动恢复正常运行" = "• O serviço MySQL retomará automaticamente a operação normal após a conclusão";
"安装成功" = "Instalação bem-sucedida";
"密码已更新" = "A senha foi atualizada";
"显示版本号" = "Exibir número da versão";
"PHP 7.2+ 已废弃，建议使用 openssl" = "O PHP 7.2+ está obsoleto; recomenda-se usar o OpenSSL";
"导入数据库" = "Importar banco de dados";
"批量导出" = "Exportação em lote";
"允许动态加载" = "Permitir carregamento dinâmico";
"mkcert 未安装" = "O mkcert não está instalado";
"端到端SSL" = "SSL de ponta a ponta";
"获取数据库列表失败" = "Falha ao obter a lista de bancos de dados";
"扫描结果" = "Resultados da verificação";
"文件缓存目录" = "Diretório de cache de arquivos";
"默认经度" = "Longitude padrão";
"端口号无效" = "Número de porta inválido";
"配置站点在遇到错误时显示的页面。默认使用 MacEnv 内置精美主题。" = "Configure a página exibida pelo site quando ocorrer um erro. Por padrão, são usados os temas elegantes integrados ao MacEnv.";
"解密失败: %@" = "Falha na descriptografia: %@";
"解密失败: %lld" = "Falha na descriptografia: %lld";
"正在保存配置文件..." = "Salvando o arquivo de configuração...";
"正在检查端口监听..." = "Verificando a escuta na porta...";
"JSON 格式有误: %@。已尝试强制格式化，请检查红线标注处。" = "JSON malformado: %@. Foi feita uma tentativa de formatação forçada; verifique a marcação em vermelho.";
"JSON 格式有误: %lld。已尝试强制格式化，请检查红线标注处。" = "JSON malformado: %lld. Foi feita uma tentativa de formatação forçada; verifique a marcação em vermelho.";
"未找到" = "não encontrado";
"默认纬度" = "Latitude padrão";
"其他程序" = "Outros programas";
"用户数据存储方式" = "Método de armazenamento de dados do usuário";
"PHP配置" = "Configuração do PHP";
"✅ 已备份旧 Nginx 配置: %@" = "✅ A antiga configuração do Nginx foi salva como backup: %@";
"✅ 已备份旧 Nginx 配置: %lld" = "✅ A antiga configuração do Nginx foi salva como backup: %lld";
"名称" = "Nome";
"建议使用 6379 或 6380-6399 范围内的端口" = "Recomenda-se usar portas na faixa 6379 ou 6380-6399";
"加载服务缓存失败: %@" = "Falha ao carregar o cache do serviço: %@";
"加载服务缓存失败: %lld" = "Falha ao carregar o cache do serviço: %lld";
"无法更新数据库权限，请检查 MySQL 连接。" = "Não foi possível atualizar as permissões do banco de dados; verifique sua conexão com o MySQL.";
"加密解密" = "Criptografia e descriptografia";
"签发站点证书" = "Emitir certificado do site";
"会话存储路径" = "Caminho de armazenamento das sessões";
"未知原因" = "motivo desconhecido";
"高级分析" = "Análises avançadas";
"PID 文件路径" = "Caminho do arquivo PID";
"%@ 字符" = "%@ caracteres";
"%lld 字符" = "%lld caracteres";
"请再次输入新密码" = "Por favor, insira novamente a nova senha";
"目录不存在：%@" = "Diretório não existe: %@";
"目录不存在：%lld" = "Diretório não existe: %lld";
"备份文件不存在" = "Arquivo de backup não existe";
"网站数量" = "Número de sites";
"伪静态转换" = "Conversão pseudoestática";
"SSL证书详情" = "Detalhes do certificado SSL";
">\n        <h1>恭喜，站点创建成功！</h1>\n        <h3>这是默认index.html，本文件位于站点根目录</h3>\n    </div>\n</body>\n</html>\n" = ">\n        <h1>Parabéns, o site foi criado com sucesso!</h1>\n        <h3>Este é o arquivo index.html padrão, localizado no diretório raiz do site</h3>\n    </div>\n</body>\n</html>";
"长连接超时" = "Tempo limite de conexão longa";
"日志过期天数" = "Dias de expiração dos logs";
"健康检查" = "verificação de integridade";
"⚠️ mkcert 不可用，使用 openssl 生成自签名证书" = "⚠️ mkcert não está disponível; use o openssl para gerar um certificado autoassinado";
"路径已存在但不是目录" = "O caminho existe, mas não é um diretório";
"系统软件" = "Software do sistema";
"浏览" = "Navegar";
"请输入要解码的文本" = "Insira o texto a ser decodificado";
"确定要删除证书 %@ 吗？\\n\\n此操作不可恢复。" = "Tem certeza de que deseja excluir o certificado %@? \\n\\nEssa operação é irreversível.";
"确定要删除证书 %lld 吗？\\n\\n此操作不可恢复。" = "Tem certeza de que deseja excluir o certificado %lld? \\n\\nEssa operação é irreversível.";
"错误页面设置" = "Configurações da página de erro";
"数据库已导出到: %@" = "O banco de dados foi exportado para: %@";
"数据库已导出到: %lld" = "O banco de dados foi exportado para: %lld";
"数据库已从备份恢复" = "Banco de dados restaurado a partir do backup";
"站点名称" = "Nome do site";
"保存备注失败: %@" = "Falha ao salvar as anotações: %@";
"保存备注失败: %lld" = "Falha ao salvar as anotações: %lld";
"配置文件" = "Arquivo de configuração";
"添加第三方源失败" = "Falha ao adicionar fonte de terceiros";
"设置PHP命令行版本功能正在开发中,敬请期待。\\n\\n此功能将允许您选择终端中使用的 PHP 版本。" = "A função para definir a versão da linha de comando do PHP está em desenvolvimento; portanto, fique atento. \\n\\nEssa funcionalidade permitirá que você selecione a versão do PHP utilizada no terminal.";
"站点已启用 SSL 但未关联证书" = "O site tem SSL habilitado, mas nenhum certificado associado";
"终端工具" = "Ferramentas de terminal";
"修改默认页面" = "Modificar página padrão";
"仪表盘" = "Painel de controle";
"SSL证书申请功能正在开发中，敬请期待。" = "A função para solicitar o certificado SSL está em desenvolvimento; portanto, fique atento.";
"Gzip 配置" = "Configuração Gzip";
"测试连接" = "testar conexão";
"警告: index.html 文件不存在: %@，跳过复制默认首页" = "Aviso: o arquivo index.html não existe: %@; ignorando a cópia da página inicial padrão";
"警告: index.html 文件不存在: %lld，跳过复制默认首页" = "Aviso: o arquivo index.html não existe: %lld; ignorando a cópia da página inicial padrão";
"未检测到可用 IP" = "Nenhum IP disponível detectado";
"• 不同 PHP 版本的扩展需要分别管理，切换版本后扩展状态会同步更新" = "• As extensões de diferentes versões do PHP precisam ser gerenciadas separadamente. Após alternar as versões, o status das extensões será atualizado simultaneamente.";
"目标路径不是目录: %@" = "O caminho de destino não é um diretório: %@";
"目标路径不是目录: %lld" = "O caminho de destino não é um diretório: %lld";
"未找到 mysqldump 客户端" = "Cliente mysqldump não encontrado";
"目录不存在" = "Diretório não existe";
"数据库不存在" = "Banco de dados não existe";
"应用配置" = "Configuração do aplicativo";
"写入失败: %@" = "Falha na gravação: %@";
"写入失败: %lld" = "Falha na gravação: %lld";
"⚠️ 修复站点 %@ 失败: %@" = "⚠️ Falha ao reparar o site %@: %@";
"⚠️ 修复站点 %lld 失败: %lld" = "⚠️ Falha ao reparar o site %lld: %lld";
"用于接收证书到期提醒" = "Usado para receber lembretes de expiração de certificados";
"编码" = "codificação";
"安装 PHP %@ 时发现端口冲突" = "Conflito de porta detectado ao instalar o PHP %@";
"安装 PHP %lld 时发现端口冲突" = "Conflito de porta detectado ao instalar o PHP %lld";
"颁发机构" = "autoridade emissora";
"暂无远程连接" = "Nenhuma conexão remota ainda";
"403 Forbidden (禁止访问)" = "403 Proibido (acesso proibido)";
"✓ 端口 %@ 可用" = "✓ A porta %@ está disponível";
"✓ 端口 %lld 可用" = "✓ A porta %lld está disponível";
"提示：可使用历史备份回滚配置" = "Dica: Você pode usar backups históricos para reverter as configurações";
"刷新服务列表" = "Atualizar lista de serviços";
"SSL 架构模式" = "Padrões de arquitetura SSL";
"端口修改后重启" = "Reiniciar após modificação da porta";
"\n        连接信息已复制到剪贴板\n\n        您可以在 Navicat 中使用以下信息：\n        主机: %@\n        端口: %@\n        用户名: root\n        密码: \\(password.isEmpty ? " = "Informações de conexão copiadas para a área de transferência\n\n        Você pode usar as seguintes informações no Navicat:\n        Host: %@\n        Porta: %@\n        Nome de usuário: root\n        Senha: \\(password.isEmpty ?";
"\n        连接信息已复制到剪贴板\n\n        您可以在 Navicat 中使用以下信息：\n        主机: %lld\n        端口: %lld\n        用户名: root\n        密码: \\(password.isEmpty ? " = "Informações de conexão copiadas para a área de transferência\n\n        Você pode usar as seguintes informações no Navicat:\n        Host: %lld\n        Porta: %lld\n        Nome de usuário: root\n        Senha: \\(password.isEmpty ?";
"级别" = "nível";
"独立账号访问" = "Acesso independente à conta";
"连接已断开" = "A conexão foi perdida";
"转换" = "Converter";
"Homebrew 未安装，请先安装 Homebrew" = "O Homebrew não está instalado; instale-o primeiro";
"不支持的协议版本: %@" = "Versão do protocolo não compatível: %@";
"不支持的协议版本: %lld" = "Versão do protocolo não compatível: %lld";
"遇到错误时继续执行，而不是停止导入" = "Continuar a execução ao encontrar um erro, em vez de interromper a importação";
"确定" = "OK";
"# 内置扩展，请在 php.ini 中启用" = "# Extensão embutida; habilite-a no arquivo php.ini";
"未选择文件" = "Nenhum arquivo selecionado";
"备份数据库失败" = "Falha ao fazer backup do banco de dados";
"重启所有Web相关服务" = "Reiniciar todos os serviços relacionados à web";
"工作空间用于存储网站文件、配置、数据库和日志。\\n设置后可避免权限问题，便于统一管理和备份。" = "O espaço de trabalho é usado para armazenar arquivos de sites, configurações, bancos de dados e logs.\nApós sua configuração, problemas de permissão podem ser evitados e a gestão unificada e o backup facilitados.";
"端口冲突：\\(version.conflictingService ?? " = "Conflito de porta: \\(version.conflictingService ??";
"服务器要求完整认证，请确保密码正确或启用 SSL" = "O servidor exige autenticação completa; certifique-se de que a senha esteja correta ou habilite o SSL";
"数据库关联功能正在开发中" = "A função de correlação de banco de dados está em desenvolvimento";
"切换 PHP 版本失败" = "Falha ao alternar a versão do PHP";
"• 支持阿里云、腾讯云等云厂商的云数据库" = "• Suporta bancos de dados em nuvem de provedores como Alibaba Cloud e Tencent Cloud";
"类型" = "Tipo";
"已成功导出 %@ 个数据库到: %@" = "Bancos de dados %@ exportados com sucesso para: %@";
"已成功导出 %lld 个数据库到: %lld" = "Bancos de dados %lld exportados com sucesso para: %lld";
"📄 找到 %@ 个文件" = "📄 Encontrados %@ arquivos";
"📄 找到 %lld 个文件" = "📄 Encontrados %lld arquivos";
"安装更多版本..." = "Instalar mais versões...";
"自动休眠空闲服务" = "Colocar automaticamente em modo de espera os serviços ociosos";
"字符数: %@" = "Número de caracteres: %@";
"字符数: %lld" = "Número de caracteres: %lld";
"查看连接配置" = "Visualizar configuração de conexão";
"站点管理设置功能正在开发中,敬请期待。" = "A função de configuração de gerenciamento de sites está em desenvolvimento; fique atento às novidades.";
"正在卸载 %@..." = "Desinstalando %@...";
"正在卸载 %lld..." = "Desinstalando %lld...";
"有备份(%@)" = "Existe um backup (%@)";
"有备份(%lld)" = "Existe um backup (%lld)";
"[BatchImport] 导入站点失败: %@ - %@" = "[Importação em lote] Falha ao importar o site: %@ - %@";
"[BatchImport] 导入站点失败: %lld - %lld" = "[Importação em lote] Falha ao importar o site: %lld - %lld";
"数据文件" = "arquivo de dados";
"使用默认路径" = "Usar caminho padrão";
"输出结果" = "Exibir resultados";
"创建网站目录失败: %@\\n请检查路径权限或修改网站目录" = "Falha ao criar o diretório do site: %@\\nVerifique as permissões do caminho ou modifique o diretório do site";
"创建网站目录失败: %lld\\n请检查路径权限或修改网站目录" = "Falha ao criar o diretório do site: %lld\\nVerifique as permissões do caminho ou modifique o diretório do site";
"确定要删除远程数据库连接「%@」吗？此操作不会删除远程服务器上的数据。" = "Tem certeza de que deseja excluir a conexão remota com o banco de dados \"%@\"? Esta operação não exclui os dados no servidor remoto.";
"确定要删除远程数据库连接「%lld」吗？此操作不会删除远程服务器上的数据。" = "Tem certeza de que deseja excluir a conexão remota com o banco de dados \"%lld\"? Esta operação não exclui os dados no servidor remoto.";
"自动创建同名库" = "Criar automaticamente uma biblioteca com o mesmo nome";
"父路径不是目录" = "O caminho pai não é um diretório";
"PHP 运行异常" = "Execução anormal do PHP";
"⏭️  跳过非配置文件: %@" = "⏭️ Ignorar arquivos que não são de configuração: %@";
"⏭️  跳过非配置文件: %lld" = "⏭️ Ignorar arquivos que não são de configuração: %lld";
"加密失败" = "Falha na criptografia";
"支持 TLS/SSL 加密传输" = "Suporte à transmissão criptografada TLS/SSL";
"mysql 执行失败" = "Falha na execução do mysql";
"安装目录" = "Diretório de instalação";
"伪静态规则" = "regras de pseudo-estático";
"配置修改" = "Modificação de configuração";
"自动识别" = "reconhecimento automático";
"持久化数据" = "Dados persistentes";
"正在配置环境..." = "Configurando ambiente...";
"工具箱" = "ferramentas";
"停止" = "parar";
"不切割" = "Sem corte";
"应用设置" = "Aplicar configurações";
"在添加远程数据库前，请先测试连接以确保配置正确。" = "Antes de adicionar um banco de dados remoto, teste a conexão para garantir que ele esteja configurado corretamente.";
"保存错误日志失败: %@" = "Falha ao salvar o log de erro: %@";
"保存错误日志失败: %lld" = "Falha ao salvar o log de erro: %lld";
"请输入用户名" = "Por favor, insira o nome de usuário";
"远程连接" = "conexão remota";
"错误: %@" = "Erro: %@";
"错误: %lld" = "Erro: %lld";
"请先安装 MySQL" = "Por favor, instale o MySQL primeiro";
"配置文件仍在 Homebrew 默认路径，建议迁移到工作空间以便统一管理" = "O arquivo de configuração ainda está no caminho padrão do Homebrew. Recomenda-se migrá-lo para o espaço de trabalho para gerenciamento unificado.";
"响应日志" = "Log de resposta";
"• 支持 .sql 和 .sql.gz 格式的文件" = "• Suporta arquivos nos formatos .sql e .sql.gz";
"全局设置" = "Configurações globais";
"站点创建成功" = "Site criado com sucesso";
"日志安全分析" = "Análise de segurança de logs";
"创建父目录失败: %@\\n请检查路径权限或修改网站目录" = "Falha ao criar o diretório pai: %@\\nVerifique as permissões do caminho ou modifique o diretório do site";
"创建父目录失败: %lld\\n请检查路径权限或修改网站目录" = "Falha ao criar o diretório pai: %lld\\nVerifique as permissões do caminho ou modifique o diretório do site";
"跳过DNS解析" = "Ignorar resolução DNS";
"✓ 版本匹配: %@" = "✓ Versão compatível: %@";
"✓ 版本匹配: %lld" = "✓ Versão compatível: %lld";
"读IO线程数" = "Número de threads de E/S de leitura";
"临时表缓存大小" = "Tamanho do cache de tabelas temporárias";
"显示启动错误" = "Exibir erro de inicialização";
"缓存" = "cache";
"打开日志目录" = "Abrir diretório de logs";
"快速打开终端" = "Abrir rapidamente um terminal";
"选择" = "Selecionar";
"• 请确保您有权限修改该数据库的密码" = "• Certifique-se de que você tenha permissão para alterar a senha do banco de dados";
"可选备注信息" = "Observações opcionais";
"\n        # 转换自 Apache 规则\n        # 注意：部分规则可能需要手动调整\n\n        " = "# Converter das regras do Apache\n        # Observação: algumas regras podem precisar ser ajustadas manualmente";
"添加 Hosts 条目" = "Adicionar entrada no arquivo Hosts";
"PHP %@ 运行正常" = "O PHP %@ está funcionando corretamente";
"PHP %lld 运行正常" = "O PHP %lld está funcionando corretamente";
"正在下载..." = "Baixando...";
"端口 %@ 已被 %@ 占用，请先停止该服务" = "A porta %@ está ocupada por %@; pare o serviço primeiro";
"端口 %lld 已被 %lld 占用，请先停止该服务" = "A porta %lld está ocupada por %lld; pare o serviço primeiro";
"Apache 端口" = "Porta do Apache";
"GoAccess 输出无效" = "A saída do GoAccess é inválida";
"安装 phpMyAdmin 失败: %@" = "Falha na instalação do phpMyAdmin: %@";
"安装 phpMyAdmin 失败: %lld" = "Falha na instalação do phpMyAdmin: %lld";
"Root密码已成功修改。" = "A senha raiz foi modificada com sucesso.";
"无密码" = "Sem senha";
"多个域名使用逗号隔开" = "Use vírgulas para separar vários nomes de domínio";
"二进制日志" = "Log binário";
"MySQL默认端口" = "Porta padrão do MySQL";
"如: public_html（留空则使用子目录根目录）" = "Por exemplo: public_html (deixe em branco para usar o diretório raiz do subdiretório)";
"网络配置" = "Configuração de rede";
"未开始验证" = "Verificação não iniciada";
"正在安装 Pure-FTPd..." = "Instalando o Pure-FTPd...";
"导入选项" = "Opções de importação";
"[VhostScan] 写入日志失败: %@" = "[VhostScan] Falha ao gravar o log: %@";
"[VhostScan] 写入日志失败: %lld" = "[VhostScan] Falha ao gravar o log: %lld";
"默认时区" = "Fuso horário padrão";
"请输入新密码" = "Insira a nova senha";
"计算中..." = "Calculando...";
"工作空间未配置" = "O espaço de trabalho não está configurado";
"无法创建目录" = "Não é possível criar o diretório";
"调试器" = "depurador";
"设置" = "configurações";
"续期" = "Renovar";
"⚠ HTTPS 连接失败" = "⚠ Falha na conexão HTTPS";
"SSL证书" = "Certificado SSL";
"✓ 配置文件存在: %@" = "✓ O arquivo de configuração existe: %@";
"✓ 配置文件存在: %lld" = "✓ O arquivo de configuração existe: %lld";
"网站根目录" = "Diretório raiz do site";
"高级日志分析" = "Análise avançada de logs";
"关联表缓存大小" = "Tamanho do cache da tabela de associação";
"运行用户" = "Usuário de execução";
"确认恢复" = "Confirmar recuperação";
"配置同步结果" = "Configurar resultados da sincronização";
"已安装扩展" = "Extensão instalada";
"Nginx listen 指令" = "Diretiva 'listen' do Nginx";
"伪静态规则管理功能正在开发中" = "A função de gerenciamento de regras pseudo-estáticas está em desenvolvimento";
"基本信息" = "Informações básicas";
"文件上传" = "Upload de arquivos";
"最大输入嵌套层级" = "Nível máximo de aninhamento de entrada";
"端口 %@ 已被 %@ 占用\\n\\n建议：%@" = "A porta %@ já está ocupada por %@\\n\\nSugestão: %@";
"端口 %lld 已被 %lld 占用\\n\\n建议：%lld" = "A porta %lld já está ocupada por %lld\\n\\nSugestão: %lld";
"X Protocol 端口" = "Porta do protocolo X";
"正在修改 Redis 端口..." = "Modificando a porta do Redis...";
"Pure-FTPd 是一个安全、高效的 FTP 服务器" = "Pure-FTPd é um servidor FTP seguro e eficiente";
"语言" = "idioma";
"XSS攻击" = "Ataque XSS";
"最小空闲进程" = "Processo ocioso mínimo";
"安全配置" = "Configuração de segurança";
"用户自定义参数" = "Parâmetros definidos pelo usuário";
"正在加载日志..." = "Carregando logs...";
"修改连接" = "Modificar conexão";
"最多保留 10 个备份" = "Manter até 10 backups";
"🔍 处理配置文件: %@" = "🔍 Processando arquivos de configuração: %@";
"🔍 处理配置文件: %lld" = "🔍 Processando arquivos de configuração: %lld";
"重置密码" = "redefinir senha";
"[SiteManager] 删除 hosts 条目失败: %@ - %@" = "[SiteManager] Falha ao excluir entrada de hosts: %@ - %@";
"[SiteManager] 删除 hosts 条目失败: %lld - %lld" = "[SiteManager] Falha ao excluir 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        " = "#Configuração padrão do site\n        # Gerado automaticamente pelo JihePHPEnv\n        #Hora da geração: %@.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            #Configuração do 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        " = "#Configuração padrão do site\n        # Gerado automaticamente pelo JihePHPEnv\n        #Hora da geração: %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            #Configuração do 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            }";
"准备安装" = "Pronto para instalar";
"导入" = "importar";
"无法解析 PHP 版本" = "Não foi possível analisar a versão do PHP";
"在 Nginx 和 Apache 配置目录中未找到可导入的站点配置" = "Configuração de site importável não encontrada nos diretórios de configuração do Nginx e do Apache";
"简体中文" = "Chinês simplificado";
"哈希值" = "valor hash";
"国际化扩展，支持多语言" = "Expansão internacional, com suporte a múltiplos idiomas";
"卸载" = "Desinstalar";
"查看错误日志" = "Visualizar log de erros";
"\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                # 🔄 Configuração de proxy reverso do Nginx (backend Apache)\n                # Esta configuração encaminha solicitações para o servidor backend Apache\n                # Porta frontal: 80/443 (Nginx)\n                # Porta de backend: %@/%@ (Apache)\n                # ============================================\n\n                # Bloco do servidor HTTP - redirecionar para HTTPS\n                server {\n                    listen 80;\n                    server_name %@;\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # Bloco do servidor HTTPS - Proxy reverso para o Apache\n                server {\n                    listen 443 ssl;\n                    server_name %@;\n\n                    # Configuração do certificado SSL\n                    ssl_certificate %@;\n                    ssl_certificate_key %@;\n                    ssl_protocols TLSv1.2 TLSv1.3;\n                    ssl_ciphers HIGH:!aNULL:!MD5;\n                %@\n                    # Configuração do proxy reverso\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                # 🔄 Configuração de proxy reverso do Nginx (backend Apache)\n                # Esta configuração encaminha solicitações para o servidor backend Apache\n                # Porta frontal: 80/443 (Nginx)\n                # Porta de backend: %lld/%lld (Apache)\n                # ============================================\n\n                # Bloco do servidor HTTP - redirecionar para HTTPS\n                server {\n                    listen 80;\n                    server_name %lld;\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # Bloco do servidor HTTPS - Proxy reverso para o Apache\n                server {\n                    listen 443 ssl;\n                    server_name %lld;\n\n                    # Configuração do 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                    # Configuração do proxy reverso\n                    location/{\n                        proxy_pass \\(site.sslMode == .nginxTermination ?";
"InnoDB日志缓冲区大小" = "Tamanho do buffer de log do InnoDB";
"无效的HTTP响应" = "Resposta HTTP inválida";
"显示错误" = "Mostrar erro";
"配置迁移失败: %@" = "Falha na migração da configuração: %@";
"配置迁移失败: %lld" = "Falha na migração da configuração: %lld";
"图像处理" = "processamento de imagens";
"证书状态" = "Status do certificado";
"系统默认页面" = "Página padrão do sistema";
"日志切割" = "corte de log";
"选择的 Web 服务器（%@）未安装。请先安装 %@ 或选择其他已安装的 Web 服务器。" = "O servidor web selecionado (%@) não está instalado. Por favor, instale primeiro o %@ ou escolha outro servidor web já instalado.";
"选择的 Web 服务器（%lld）未安装。请先安装 %lld 或选择其他已安装的 Web 服务器。" = "O servidor web selecionado (%lld) não está instalado. Por favor, instale primeiro o %lld ou escolha outro servidor web já instalado.";
"目录不存在且无法创建: %@\\n\\n请先在设置中配置默认站点主目录。" = "O diretório não existe e não pode ser criado: %@\\n\\nPor favor, configure primeiro o diretório inicial padrão do site nas configurações.";
"目录不存在且无法创建: %lld\\n\\n请先在设置中配置默认站点主目录。" = "O diretório não existe e não pode ser criado: %lld\\n\\nPor favor, configure primeiro o diretório inicial padrão do site nas configurações.";
"保存规则" = "salvar regra";
"• 证书有效期 90 天，需定期续期" = "• O certificado é válido por 90 dias e precisa ser renovado regularmente";
"限制目录" = "diretório restrito";
"无法连接到服务器，请检查地址和端口" = "Não foi possível conectar ao servidor; verifique o endereço e a porta";
"复制" = "Copiar";
"共扫描 %@ 条日志，发现 %@ 个威胁" = "Um total de %@ registros foi analisado e %@ ameaças foram encontradas";
"共扫描 %lld 条日志，发现 %lld 个威胁" = "Um total de %lld registros foi analisado e %lld ameaças foram encontradas";
"PDO SQLite驱动" = "Driver SQLite PDO";
"优化方案" = "Plano de otimização";
"正在检测已安装的服务..." = "Detectando serviços instalados...";
"PHP命令行版本" = "Versão da linha de comando do PHP";
"搜索..." = "Pesquisar...";
"✅ openssl 生成自签名证书成功: %@" = "✅ openssl gerou com sucesso um certificado autoassinado: %@";
"✅ openssl 生成自签名证书成功: %lld" = "✅ openssl gerou com sucesso um certificado autoassinado: %lld";
"复制输出" = "Copiar saída";
"查询缓存类型" = "Tipo de cache de consulta";
"签发" = "problema";
"将保持当前管理状态（未管理条目仍为未管理）。" = "O status atual de gerenciamento será mantido (entradas não gerenciadas permanecerão não gerenciadas).";
"找到 %@ 个配置文件" = "Encontrados %@ arquivos de configuração";
"找到 %lld 个配置文件" = "Encontrados %lld arquivos de configuração";
"搜索日志内容..." = "Pesquisar conteúdo do log...";
"父目录没有写权限" = "O diretório pai não possui permissões de gravação";
"配置、数据、日志、网站文件集中存储" = "Armazenamento centralizado de configurações, dados, logs e arquivos de sites";
"请输入站点名称" = "Por favor, insira o nome do site";
"全部监听端口" = "Todas as portas em escuta";
"数据库 %@ 已成功备份" = "O banco de dados %@ foi copiado com sucesso.";
"数据库 %lld 已成功备份" = "O banco de dados %lld foi copiado com sucesso.";
"（需要输入实际密码）" = "(requer a senha real)";
"配置文件 (%@)" = "Arquivo de configuração (%@)";
"配置文件 (%lld)" = "Arquivo de configuração (%lld)";
"白名单模式" = "modo de lista branca";
"远程数据库密码已成功修改。" = "A senha do banco de dados remoto foi modificada com sucesso.";
"启动进程数" = "Número de processos iniciados";
"新端口" = "nova porta";
"启用 SSL" = "Habilitar SSL";
"隐式刷新" = "atualização implícita";
"无效的密文格式" = "Formato de texto cifrado inválido";
"确认终止进程" = "Confirmar para encerrar o processo";
"✗ 证书文件不存在: %@" = "✗ Arquivo de certificado não existe: %@";
"✗ 证书文件不存在: %lld" = "✗ Arquivo de certificado não existe: %lld";
"检查第三方源..." = "Verificando fontes de terceiros...";
"迁移失败: %@" = "Falha na migração: %@";
"迁移失败: %lld" = "Falha na migração: %lld";
"确定要删除选中的 %@ 个数据库吗？此操作不可恢复。" = "Tem certeza de que deseja excluir os bancos de dados selecionados (%@)? Esta operação é irreversível.";
"确定要删除选中的 %lld 个数据库吗？此操作不可恢复。" = "Tem certeza de que deseja excluir os bancos de dados selecionados (%lld)? Esta operação é irreversível.";
"创建用户失败: \\(userResult.error ?? " = "Falha ao criar o usuário: \\(userResult.error ??";
"初始化失败" = "Falha na inicialização";
"及和团队" = "Equipe Jihe";
"黑名单 (仅拒绝)" = "Lista negra (apenas negação)";
"优化所有表" = "Otimizar todas as tabelas";
"所有域名解析正常" = "Todos os nomes de domínio são resolvidos normalmente";
"未配置" = "Não configurado";
"站点名称已存在" = "O nome do site já existe";
"远程数据库" = "banco de dados remoto";
"AES-GCM (推荐)" = "AES-GCM (recomendado)";
"重新生成" = "Regenerar";
"仅文件缓存" = "Apenas cache de arquivos";
"文档根目录不存在" = "A raiz do documento não existe";
"%@ 重新加载配置失败，请检查日志" = "%@ Falha ao recarregar a configuração; verifique os logs";
"%lld 重新加载配置失败，请检查日志" = "%lld Falha ao recarregar a configuração; verifique os logs";
"更换Web服务器失败" = "Falha ao substituir o servidor web";
"\\n    # PHP-FPM 配置需要在 Apache 中单独设置\\n" = "\n # A configuração do PHP-FPM precisa ser definida separadamente no Apache\n";
"• 从历史记录恢复：从最近5次配置备份中选择一个恢复" = "• Restaurar do histórico: selecione um dos últimos 5 backups de configuração para restaurar";
"点击查看SSL证书详情" = "Clique para visualizar os detalhes do certificado SSL";
"\n        # 默认站点配置\n        # 由 JihePHPEnv 自动生成\n        # 生成时间: %@.string(from: Date()))\n\n        <VirtualHost *:%@>\n            ServerName localhost\n            DocumentRoot " = "#Configuração do site padrão\n        # Gerado automaticamente pelo JihePHPEnv\n        #Hora da geração: %@.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 " = "#Configuração do site padrão\n        # Gerado automaticamente pelo JihePHPEnv\n        #Hora da geração: %lld.string(from: Date()))\n\n        <VirtualHost *:%lld>\n            ServerName localhost\n            DocumentRoot";
"连接名称" = "nome da conexão";
"配置已应用成功" = "A configuração foi aplicada com sucesso";
"数据库已成功导入。" = "O banco de dados foi importado com sucesso.";
"已选择文件：" = "Arquivo selecionado:";
"未配置SSL证书" = "Certificado SSL não configurado";
"❌ 目录不存在: %@" = "❌ Diretório não existe: %@";
"❌ 目录不存在: %lld" = "❌ Diretório não existe: %lld";
"根据系统内存自动选择最佳方案" = "Selecionar automaticamente a melhor solução com base na memória do sistema";
" 天" = "dia";
"默认配置" = "Configuração padrão";
"索引缓冲区大小" = "Tamanho do buffer de índice";
"慢查询阈值" = "Limiar de consulta lenta";
"无法自动检测 MySQL Root 密码，可能原因：" = "Não foi possível detectar automaticamente a senha raiz do MySQL. Possíveis causas:";
"限制模式" = "modo restrito";
"输入端口号" = "Insira o número da porta";
"选择包含站点子目录的父目录" = "Selecione o diretório pai que contém o subdiretório do site";
"默认字符集" = "Conjunto de caracteres padrão";
"导出完成" = "Exportação concluída";
"⚠️ 端口 %@ 已被 %@ 占用" = "⚠️ A porta %@ já está ocupada por %@";
"⚠️ 端口 %lld 已被 %lld 占用" = "⚠️ A porta %lld já está ocupada por %lld";
"续期功能开发中" = "A função de renovação está em desenvolvimento";
"其他" = "Outros";
"未找到 Nginx 配置文件" = "Arquivo de configuração do Nginx não encontrado";
"• CA 证书用于签发站点 SSL 证书" = "• Certificado CA usado para emitir o certificado SSL do site";
"系统会自动扫描一层子目录，并根据目录名推导域名" = "O sistema fará uma varredura automática de um nível de subdiretórios e derivará o nome de domínio com base no nome do diretório.";
"修改密码失败" = "Falha ao alterar a senha";
"配置 Nginx 失败" = "Falha ao configurar o Nginx";
"准备中" = "Em preparação";
"事务提交刷新策略" = "Estratégia de atualização de confirmação de transação";
"网站停用后的提示页" = "Página de aviso após a desativação do site";
"证书详情" = "Detalhes do certificado";
"系统未安装 mkcert 工具，无法自动安装信任 CA。请先通过 Homebrew 安装：brew install mkcert" = "A ferramenta mkcert não está instalada no sistema e a AC confiável não pode ser instalada automaticamente. Por favor, instale-a primeiro usando o Homebrew: brew install mkcert";
"更新 Nginx 站点配置中的监听端口" = "Atualizar a porta de escuta na configuração do site do Nginx";
"错误页面" = "página de erro";
"主配置 listen 指令:" = "Diretiva de escuta da configuração principal:";
"请求超时" = "Tempo limite da solicitação";
"需要从官网下载对应版本" = "Você precisa baixar a versão correspondente do site oficial";
"%@ 行" = "%@ linhas";
"%lld 行" = "%lld linhas";
"端口号必须在 1-65535 之间" = "O número da porta deve estar entre 1 e 65535";
"配置失败: %@" = "Falha na configuração: %@";
"配置失败: %lld" = "Falha na configuração: %lld";
"Redis扩展仅支持一个PHP版本安装使用" = "A extensão Redis suporta apenas uma versão do PHP para instalação e uso.";
"强大的内容缓存器" = "Cache de conteúdo poderoso";
"建议使用 3306 或 3307-3399 范围内的端口" = "Recomenda-se usar portas na faixa de 3306 ou 3307-3399";
"安装 Pure-FTPd" = "Instalar o Pure-FTPd";
"页" = "página";
"搜索域名..." = "Pesquisar nome de domínio...";
"主机" = "Host";
"已占用" = "Já ocupado";
"取消全选" = "Desmarcar todos";
"❌ 扫描失败: %@" = "❌ Falha na verificação: %@";
"❌ 扫描失败: %lld" = "❌ Falha na verificação: %lld";
"HTTP状态码：%@" = "Código de status HTTP: %@";
"HTTP状态码：%lld" = "Código de status HTTP: %lld";
"• opcache/apcu等脚本缓存扩展，请只安装其中1个，否则可能导致站点程序异常" = "• Extensões de cache de scripts, como opcache/apcu; instale apenas uma delas, caso contrário pode causar exceções no programa do site";
"写IO线程数" = "Número de threads de E/S de gravação";
"保存并重启前自动备份" = "Salvar e fazer backup automaticamente antes de reiniciar";
"停止 %@" = "Parar %@";
"停止 %lld" = "Parar %lld";
"CA 证书" = "Certificado CA";
"上传临时目录" = "Diretório temporário de upload";
"安装扩展" = "Instalar extensão";
"暂无已安装版本" = "Nenhuma versão instalada ainda";
"复制自: %@" = "Copiado de: %@";
"复制自: %lld" = "Copiado de: %lld";
"保存" = "Salvar";
"快速关闭" = "Fechar rapidamente";
"另存为模板" = "Salvar como modelo";
"✓ 配置文件语法正确" = "✓ A sintaxe do arquivo de configuração está correta";
"停止失败" = "Falha ao parar";
"当前值" = "valor atual";
"重写时不同步" = "Fora de sincronia ao reescrever";
"[VhostScan] 创建日志目录失败: %@" = "[VhostScan] Falha ao criar o diretório de logs: %@";
"[VhostScan] 创建日志目录失败: %lld" = "[VhostScan] Falha ao criar diretório de log: %lld";
"用户名: %@" = "Nome de usuário: %@";
"用户名: %lld" = "Nome de usuário: %lld";
"有 %@ 个域名配置异常" = "Há %@ nomes de domínio com configurações anormais";
"有 %lld 个域名配置异常" = "Há %lld nomes de domínio com configurações anormais";
"忽略重复错误" = "Ignorar erros duplicados";
"密码" = "Senha";
"未运行" = "Não está em execução";
"加载站点数据失败: %@" = "Falha ao carregar os dados do site: %@";
"加载站点数据失败: %lld" = "Falha ao carregar os dados do site: %lld";
"确认卸载" = "Confirmar desinstalação";
"添加远程数据库" = "Adicionar banco de dados remoto";
"私钥路径" = "Caminho da chave privada";
"已啟用 %@ / %@" = "Ativado %@ / %@";
"已啟用 %lld / %lld" = "Ativado %lld / %lld";
"... (显示最后 %@ 行)\\n\\n" = "... (exibir as últimas %@ linhas)\\n\\n";
"... (显示最后 %lld 行)\\n\\n" = "... (exibir as últimas %lld linhas)\\n\\n";
"提示" = "Dicas";
"权限被拒绝" = "Permissão negada";
"SSL状态" = "Status SSL";
"选择文件" = "Selecionar arquivo";
"密码已复制到剪贴板" = "Senha copiada para a área de transferência";
"日志查看器" = "Visualizador de logs";
"服务器排序规则" = "Regra de ordenação do servidor";
"已存在" = "Já existe";
"请输入要编码的文本" = "Por favor, insira o texto a ser codificado";
"• 注意2：请确保填写的管理员账号具备足够的权限" = "• Observação 2: Certifique-se de que a conta de administrador que você inserir tenha permissões suficientes";
"日志保留天数" = "Dias de retenção de logs";
"导入成功" = "Importação bem-sucedida";
"暂不支持该软件的端口修改" = "Atualmente, a modificação da porta deste software não é suportada.";
"访问限制" = "Restrições de acesso";
"高级设置" = "Configurações avançadas";
"例如: 192.168.1.100" = "Por exemplo: 192.168.1.100";
"安装失败" = "Falha na instalação";
"修复" = "Reparar";
"邮件服务" = "Serviço de e-mail";
"域名" = "nome de domínio";
"✗ 配置文件语法错误" = "✗ Erro de sintaxe no arquivo de configuração";
"修改 %@ %@ 的端口配置" = "Modificar a configuração de porta de %@ %@";
"修改 %lld %lld 的端口配置" = "Modificar a configuração de porta de %lld %lld";
"创建数据库: %@, 密码: %@" = "Criar banco de dados: %@, senha: %@";
"创建数据库: %lld, 密码: %lld" = "Criar banco de dados: %lld, senha: %lld";
"默认路径: %@)" = "Caminho padrão: %@)";
"默认路径: %lld)" = "Caminho padrão: %lld)";
"未安装" = "Não instalado";
"批量导入站点" = "Importar sites em lote";
"MySQL自动重连" = "MySQL reconecta automaticamente";
"密码验证" = "Verificação de senha";
"• 浏览器将自动信任由该 CA 签发的证书" = "• Os navegadores confiarão automaticamente em certificados assinados por esta AC";
"数据库文件" = "arquivo de banco de dados";
"需要安装 acme.sh" = "É necessário instalar o acme.sh";
"私钥文件" = "arquivo de chave privada";
"扫描行为" = "comportamento de verificação";
"数据库名称不能为空" = "O nome do banco de dados não pode estar vazio";
"设置工作空间" = "Configurar área de trabalho";
"正在停止中..." = "Parando...";
"关系型数据库管理系统" = "sistema de gerenciamento de banco de dados relacional";
"修复所有站点" = "Reparar todos os sites";
"✓ HTTPS 连接正常" = "✓ A conexão HTTPS está normal";
"正在安装 Nginx..." = "Instalando Nginx...";
"备份旧版本失败: %@" = "Falha ao fazer backup da versão antiga: %@";
"备份旧版本失败: %lld" = "Falha ao fazer backup da versão antiga: %lld";
"压缩级别(1-9)" = "Nível de compactação (1-9)";
"后台任务频率" = "Frequência da tarefa em segundo plano";
"已启用" = "Habilitado";
"系统盘" = "disco do sistema";
"绑定地址" = "Endereço de vinculação";
"AOF 同步策略" = "Estratégia de sincronização AOF";
"您可以开始使用了" = "Você está pronto para começar";
"if 条件已转换为 RewriteCond，请验证逻辑" = "se a condição foi convertida para RewriteCond, verifique a lógica";
"关系型数据库" = "banco de dados relacional";
"正在导入 %@ (%@/%@)" = "Importando %@ (%@/%@)";
"正在导入 %lld (%lld/%lld)" = "Importando %lld (%lld/%lld)";
"备份失败" = "Falha no backup";
"正在识别子目录并推导域名" = "Identificando subdiretórios e derivando nomes de domínio";
"添加新规则" = "Adicionar nova regra";
"确定要删除选中的 %@ 个 hosts 条目吗？" = "Tem certeza de que deseja excluir as entradas selecionadas de hosts %@?";
"确定要删除选中的 %lld 个 hosts 条目吗？" = "Tem certeza de que deseja excluir as entradas selecionadas de hosts %lld?";
"邮件服务器协议支持" = "Suporte ao protocolo do servidor de e-mail";
"6-32位字符" = "6-32 caracteres";
"建议使用 1024-65535 范围内的端口" = "Recomenda-se usar portas na faixa de 1024 a 65535";
"⚠ %@ → %@ (非本地)" = "⚠ %@ → %@ (não local)";
"⚠ %lld → %lld (非本地)" = "⚠ %lld → %lld (não local)";
"详细信息:" = "Detalhes:";
"站点设置 - %@" = "Configurações do site - %@";
"站点设置 - %lld" = "Configurações do site - %lld";
"安装模式" = "Modo de instalação";
"检测到配置冲突：同时存在完整的 Nginx 和 Apache 配置" = "Conflito de configuração detectado: existem tanto configurações completas do Nginx quanto do Apache";
"暂无站点证书" = "Ainda não há certificado para o site";
"所有人" = "todos";
"日志配置" = "Configuração de log";
"日志文件大小" = "Tamanho do arquivo de log";
"域名可达性验证" = "Verificação de acessibilidade do nome de domínio";
"GC触发除数" = "Divisor para acionamento da coleta de lixo (GC)";
"应用" = "Aplicativo";
"• 支持多个域名，用逗号分隔" = "• Suporta múltiplos nomes de domínio, separados por vírgulas";
"已包含" = "Já incluído";
"需要安装 GoAccess 才能使用高级日志分析功能" = "O GoAccess é necessário para utilizar recursos avançados de análise de logs";
"工作空间用于存储服务配置、数据库文件、日志和网站文件。" = "O espaço de trabalho é usado para armazenar as configurações dos serviços, arquivos de banco de dados, logs e arquivos de sites.";
"证书申请" = "Solicitação de certificado";
"选择父目录，自动识别子目录并批量创建站点" = "Selecione um diretório pai, identifique automaticamente subdiretórios e crie sites em lote";
"服务运行正常" = "O serviço está funcionando normalmente";
"AI 助手设置" = "Configurações do Assistente de IA";
"配置第三方大模型用于错误分析" = "Configure modelos de linguagem de terceiros (LLMs) para análise de erros";
"AI 解释日志设置" = "Configurações de Análise de Logs por IA";
"配置第三方大模型，用于一键分析和解释错误日志" = "Configure modelos de linguagem de terceiros (LLMs) para análise e explicação de logs de erro com um único clique";
"添加模型" = "Adicionar Modelo";
"当前使用中" = "Atualmente em uso";
"编辑模型" = "Editar Modelo";
"提供商名称:" = "Nome do Provedor:";
"API 地址:" = "URL da API:";
"模型名称:" = "Nome do Modelo:";
"API Key:" = "Chave da API:";
"通义千问" = "Qwen";
"智谱 GLM" = "Zhipu GLM";
"火山引擎 (Doubao)" = "Volcengine (Doubao)";
"DeepSeek (中文)" = "DeepSeek";
"自定义模型" = "Modelo Personalizado";
"Log view" = "Visualização de Logs";
"Port management" = "Gerenciamento de Portas";
"模型标识，例如 qwen-plus" = "Identificador do modelo, por exemplo, qwen-plus";
"✅ 扫描到 %d 个证书文件" = "✅ Verificou %d arquivos 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    // Visualização de conteúdo\n    private var contentView: some View {\n        VStack(spacing: 0) {\n            // Área de seleção de plano\n            planSelectionArea\n\n            Divider()\n\n            // Lista de configurações\n            configListView\n        }\n    }\n\n    // Área de seleção de plano\n    private var planSelectionArea: some View {\n        HStack(spacing: 24) {\n            // Seleção de plano de otimização\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    /// Detecta a porta em que o Apache está escutando\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            // Permissões de acesso\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            // Nome do site\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                    // Opção de conteúdo estático puro\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 instalação específico para o Apache, com suporte a diferentes modos de instalação\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            // Banco de dados local exibe valor padrão\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: - Alteração da porta do 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            // Ao mudar o tema, recarrega e processa novamente a imagem de fundo desfocada\n            if let originalImage = loadBackgroundImage() {\n                self.blurredBackgroundImage = await processAndBlurImage(originalImage)\n            } else {\n                self.blurredBackgroundImage = nil\n            }\n        }\n        .onAppear {\n            // Carrega assincronamente imagens remotas\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        // Usa o diretório raiz padrão do site\n        let rootPath = appSettings.defaultSiteRoot.isEmpty ? ";
"操作: \\(log.operation)" = "Operação: \\(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        // Detecta conflitos de porta\n        versions = detectPortConflicts(in: versions)\n\n        return versions.sorted { $0.version > $1.version }\n    }\n\n    /// Detecta conflitos de porta\n    private nonisolated func detectPortConflicts(in versions: [SoftwareVersion]) -> [SoftwareVersion] {\n        var result = versions\n        var portMap: [Int: [Int]] = [:]  // porta -> [índices]\n\n        // Coleta todas as versões instaladas que possuem uma porta definida\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        // Marca conflitos\n        for (port, indices) in portMap where indices.count > 1 {\n            for index in indices {\n                result[index].hasPortConflict = true\n                // Identifica os nomes dos serviços conflitantes (exceto ele mesmo)\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    // Caminho de configuração do 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            // Serviço carregado, mas MySQL não encontrado\n            return (";
"]),\n    ]\n\n    // MARK: - PHP 配置字典\n    static let php: [String: ConfigMeta] = [\n        // ===== 资源限制 =====\n        " = "]),\n    ]\n\n    // MARK: - Dicionário de configuração do PHP\n    static let php: [String: ConfigMeta] = [\n        // ===== Limites 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 diretório personalizado de execução (se necessário)\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                // Calcula o nível de chaves dentro do bloco http\n                httpBraceCount += line.filter { $0 == ";
"确定要删除数据库「\\(db.name)」吗？此操作不可恢复！" = "Tem certeza de que deseja excluir o banco de dados \"\\(db.name)\"? Esta operação não pode ser desfeita!";
")\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) // Move para cima, alinhando com as luzes de tráfego nativas\n                .frame(height: 38)\n                \n                // Área de conteúdo do painel flutuante\n                HStack(spacing: 20) {\n                    // Barra lateral (painel flutuante 1)\n                    SidebarViewV2(selectedTab: $selectedTab, selectedTool: $selectedTool)\n                        .frame(width: 240)\n                        .fixedSize(horizontal: true, vertical: false)\n                    \n                    // Área principal de conteúdo (painel flutuante 2)\n                    MainContentViewV2(selectedTab: selectedTab, selectedTool: $selectedTool)\n                        .frame(maxWidth: .infinity, maxHeight: .infinity)\n                }\n                .padding([.bottom, .horizontal], 20)\n            }\n            .ignoresSafeArea(edges: .top) // Permite que a barra superior ultrapasse a área segura e ocupe a posição da barra de título nativa\n            \n            // Janela flutuante de orientação do Douyin (apenas no ambiente de chinês simplificado, exibida no canto inferior direito)\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 // Desativa permanentemente\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    /// Caminho dinâmico do 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                // Converter diretivas de reescrita\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    /// Caminho dinâmico do 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            // Cabeçalho da tabela\n            databaseTableHeader\n\n            Divider()\n\n            // Lista de bancos de dados\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 ferramentas inferior\n            bottomToolbar\n        }\n        .sheetWithLocale(isPresented: $showAddDatabase) {\n            RemoteAddDatabaseSheet(connector: connector)\n        }\n    }\n\n    // Lista de bancos de dados filtrada\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 ferramentas inferior\n    private var bottomToolbar: some View {\n        HStack {\n            // Operações em lote\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            // Tamanho\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)" = "Falha ao ler: \\(error.localizedDescription)";
"\n        }\n    }\n\n    // 获取默认配置文件路径\n    private func getDefaultConfigPath() -> String? {\n        let homebrewPrefix = FileManager.default.fileExists(atPath: " = "\n        }\n    }\n\n    // Obter o caminho padrão do arquivo de configuração\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                // Processar 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(" = "  // Nome do serviço com falha, usado para visualizar os logs\n\n    let statusBarManager = StatusBarManager()\n    private var cancellables = Set<AnyCancellable>()\n\n    // MARK: - Cache do Homebrew (acelera a detecção inicial)\n    private nonisolated(unsafe) static var cachedBrewPath: String?\n    /// Saída em cache do comando \"brew services list\", reutilizada pelo SoftwareManager\n    nonisolated(unsafe) static var cachedBrewServicesOutput: String?\n    /// Lista em cache de pacotes instalados, reutilizada pelo SoftwareManager\n    nonisolated(unsafe) static var cachedInstalledPackages: [String: String]?\n\n    // MARK: - Caminho do arquivo de cache de serviços\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 sites em lote\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    // Parar sites em lote\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    // Excluir sites em lote para a lixeira\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 o arquivo SQL como entrada padrão\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 atualização de progresso (pois não é possível obter o progresso exato da importação)\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    // Excluir bancos de dados em lote\n    func deleteDatabases(_ databaseIds: Set<UUID>) async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        // Excluir bancos de dados locais em lote\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    // Atualizar informações do banco de dados\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            // Atualizar lista de IPs\n            Button(action: {\n                Task {\n                    await pmaService.detectLocalIPAddresses()\n                }\n            }) {\n                HStack {\n                    Image(systemName: ";
"\\(cert.daysRemaining) 天" = "\\(cert.daysRemaining) dias";
")\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ão para alterar senha\n            Button(action: {\n                // TODO: Exibir caixa de diálogo para alterar senha\n            }) {\n                Image(systemName: ";
"已成功导出 \\(successCount) 个数据库到: \\(url.path)" = "Exportação bem-sucedida de \\(successCount) bancos de dados para: \\(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 ferramentas\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        // Adicionar à lista de versões sendo instaladas\n        installingVersions[software.id] = InstallingVersion(\n            id: software.id,\n            name: software.name,\n            version: software.version,\n            progress: initialProgress\n        )\n\n        // Se for uma versão antiga, certifique-se primeiro de que o tap já foi adicionado\n        if isLegacyVersion {\n            installProgressMap[software.id]?.currentStep = ";
"删除选中项 (\\(selectedConfigs.count))" = "Excluir itens selecionados (\\(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: - Analisar uma única linha de log de erro\n    private func parseErrorLogLine(_ line: String) -> SiteErrorLogEntry? {\n        // Formato do log de erro do Nginx:\n        // 2026/02/10 00:28:51 [error] 12345#0: *1 mensagem\n\n        // Formato do log de erro do Apache:\n        // [Dom Fev 01 23:00:27.587593 2026] [authz_core:error] [pid 2534102:tid 139652715497216] mensagem\n\n        // Tentar analisar no formato Nginx\n        if let entry = parseNginxErrorLog(line) {\n            return entry\n        }\n\n        // Tentar analisar no formato Apache\n        if let entry = parseApacheErrorLog(line) {\n            return entry\n        }\n\n        // Se nenhuma das duas formas funcionar, retornar a linha 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 formato IPv4\n        let parts = address.split(separator: ";
")\n                    return\n                }\n\n                // 查询 mysql.db 表获取对该数据库有权限的用户\n                let query = " = ")\n                    return\n                }\n\n                // Consultar a tabela mysql.db para obter usuários com permissão para esse banco de dados\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    /// Diretório de logs\n    var logsPath: String { ";
") {\n                    break\n                }\n\n                // 检测 server 块开始\n                if !inServerBlock && line.hasPrefix(" = ") {\n                    break\n                }\n\n                // Detecta o início do bloco \"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    /// Detecta se o serviço está instalado e obtém suas informações\n    private nonisolated func detectService(name: String, brewName: String, port: Int) -> ServiceStatus? {\n        // Verifica se o serviço foi instalado via Homebrew\n        guard isBrewPackageInstalled(brewName) else {\n            return nil\n        }\n\n        // Obtém a versão\n        let version = getBrewPackageVersion(brewName)\n\n        // Obtém o estado de execução\n        let state = getServiceState(brewName)\n\n        // Obtém o caminho do arquivo de configuração\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    /// Localiza o caminho do arquivo de configuração do serviço\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    // Conteúdo do formulário\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // Informações do banco de dados\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            // Conteúdo do formulário\n            formContent\n\n            Divider()\n\n            // Botões 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        // Verifica o formato da porta\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 diretório de execução\n                        if showCustomRunningDir {\n                            TextField(";
"否" = "Não";
"\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    // Copia os diretórios de páginas de erro e a página inicial padrão para o diretório do site\n    private func copyErrorPagesToSite(documentRoot: String) async throws {\n        let fileManager = FileManager.default\n        \n        // Obtém o caminho do diretório \"_errorpage_\" (deve estar empacotado nos Recursos do Aplicativo ou lido a partir de WorkspaceConfig)\n        // Primeiro tentamos localizá-lo no Bundle; caso não seja encontrado, tentamos o caminho padrão no diretório do usuário atual\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  // Indica se a senha root deve ser exibida\n    @State private var rootPassword: String? = nil  // Senha root do MySQL (nil indica que não foi detectada)\n    @State private var actualMySQLPort: Int? = nil  // Porta real em que o MySQL está escutando\n    @State private var isDetectingPort = false  // Indica se a detecção da porta está em andamento\n    @State private var isDetectingPassword = false  // Indica se a detecção da senha está em andamento\n    @State private var passwordDetectionStatus = ";
"]),\n\n        // ===== 输出控制 =====\n        " = "]),\n\n        // ===== Controle de saída =====\n        ";
")/\n                </VirtualHost>\n\n                # HTTPS VirtualHost\n                # Apache 后端端口：\\(httpsPort)\n                <VirtualHost *:\\(httpsPort)>\n                    ServerName \\(domains.first ?? " = ")/\n                </VirtualHost>\n\n                # VirtualHost HTTPS\n                # Porta de backend do 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. Atualiza a tabela mysql.user\n        let sql1 = ";
"\\(certificate.daysRemaining) 天" = "\\(certificate.daysRemaining) dias";
" }\n\n    /// Redis 配置文件\n    var redisConfigFile: String { " = " }\n\n    /// Arquivo de configuração do 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)) // Vermelho do Apache\n        } else if name.contains(";
"  // 用户主机\n    var backupCount: Int = 0  // 备份数量\n    var location: String = " = "  // Host do usuário\n    var backupCount: Int = 0  // Número de backups\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        // Verifica se é igual à porta atual\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    // Gera uma senha aleatória (dígitos + 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        // Verifica se o diretório de origem 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    /// Desabilitar o site padrão (excluir o arquivo de configuração)\n    func disableDefaultSite() async -> Bool {\n        // Priorizar o caminho do workspace\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        // Sugestão 1: Interromper o processo que está ocupando a porta\n        suggestions.append(";
"  // 运行目录\n    @State private var previousDomain = " = "  // Diretório de execução\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        // Salvar imediatamente (operação crítica)\n        try await forceSaveDatabases()\n    }\n\n    // Atualizar informações de conexão com o banco de dados remoto\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    /// Obter o caminho do arquivo de configuração do Nginx\n    func getNginxConfigPath() -> String {\n        if let config = config, config.isInitialized {\n            return config.nginxConfigFile\n        }\n        return ";
"显示最近 \\(logLines.count) 条日志" = "Exibindo os últimos \\(logLines.count) registros de log";
"\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    /// Executar consulta SQL (priorizando o cliente nativo)\n    private func executeSQL(_ sql: String) async -> (success: Bool, output: String?, error: String?) {\n        // Tentar primeiro o cliente nativo\n        let nativeResult = await executeWithNativeClient(sql)\n        if nativeResult.success {\n            return nativeResult\n        }\n\n        // Se o cliente nativo falhar, tentar o cliente de linha de comando\n        let cliResult = await executeSQLWithCLI(sql)\n        if cliResult.success {\n            return cliResult\n        }\n\n        // Se ambos falharem, retornar o erro do cliente nativo (geralmente mais significativo)\n        return nativeResult\n    }\n\n    // MARK: - Gerenciamento de bancos de dados\n\n    /// Obter lista de bancos de dados (com informações detalhadas)\n    func fetchDatabases() async {\n        isLoading = true\n        errorMessage = nil\n\n        // Usar information_schema para obter informações detalhadas\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        // Tratamento especial para PureFTP: exibir opções de instalação\n        if software.id == ";
"⚠️ 证书目录不存在: \\(sslPath)" = "⚠️ Diretório de certificados não encontrado: \\(sslPath)";
"\n        } else {\n            // 标准端口，不需要指定\n            urlString = " = "\n        } else {\n            // Porta padrão, não é necessário especificá-la\n            urlString = ";
"未找到 Homebrew，无法重载 \\(webServerDisplayName(webServer))。" = "Homebrew não encontrado; impossível recarregar \\(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                // Procurar o serviço em 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    /// Obter descrição do status de integridade da configuração\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        // Procurar o serviço em 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 e propriedades computadas\n    \n    // Texto de status do 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() // Os logs mais recentes aparecem primeiro\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: - Análise de uma única linha de log de acesso\n    private func parseAccessLogLine(_ line: String) -> LogEntry? {\n        // Formato combinado de log do 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: - Linha de diretório raiz do site\n    private var siteRootRow: some View {\n        HStack {\n            Text(";
"\\(inputCharCount) 字符" = "\\(inputCharCount) caracteres";
"\n    // 需要使用第三方 tap 的 PHP 版本（Homebrew 官方不再支持的旧版本）\n    private let legacyPHPVersions = [" = "\n    // Versões legadas do PHP que exigem um tap de terceiros (versões antigas não mais suportadas oficialmente pelo 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)" = "Parar \\(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    // Itens de informação\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    // Item de senha do root\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    // Salvar configuração\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: - Backup e importação\n\n    /// Obter diretório de backup\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: - Caminhos do Nginx\n\n    /// Obter caminho do arquivo de configuração do Nginx (retorna preferencialmente o arquivo 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 configuração 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    /// Alterar senha do usuário\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 = " = " // Informações detalhadas\n    var progress: Double = 0.0 // Progresso de 0,0 a 1,0\n}\n\n/// Informações de extensão (em tempo de execução)\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? // Progresso da instalação\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/// Gerenciador de extensões PHP\n@MainActor\nclass PHPExtensionManager: ObservableObject {\n    static let shared = PHPExtensionManager()\n\n    // Armazenar estado das extensões por versão do 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        // Somente adicionar se o diretório existir\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    // Visualização de carregamento\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            // Atualizar apenas o estado de execução do software específico, sem recarregar toda a 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        // Garantir que o diretório 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 exibição/ocultação da senha\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                // Contagem de caracteres e botão para limpar\n                HStack {\n                    Text(";
")\n\n        // 使用用户主目录下的日志路径，避免沙盒权限问题\n        let homeDir = FileManager.default.homeDirectoryForCurrentUser.path\n        let logDir = " = ")\n\n        // Usar o caminho de logs no diretório inicial do usuário para evitar problemas de permissão de 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 = " = "  // Diretório de execução\n    @State private var showCustomRunningDir = false  // Diretório de execução personalizado\n    @State private var availableSubdirs: [String] = []  // Subdiretórios disponíveis\n    @State private var forceHttps = false  // Forçar 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            // Validade\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    // Salvar arquivo hosts\n    // Escrever o arquivo com privilégios de administrador\n    private func writeWithAdminPrivileges(content: String, toPath path: String) throws {\n        // Criar arquivo temporário\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    /// Recarregar 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            // Conteúdo\n            VStack(spacing: 24) {\n                // Ícone\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    /// Detecta o nome do pacote PHP padrão atual (por meio de links 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 conteúdo\n    private var contentArea: some View {\n        HStack(spacing: 0) {\n            // Esquerda: regras de origem\n            VStack(alignment: .leading, spacing: 12) {\n                HStack {\n                    Text(";
") {\n                // 本地 MySQL 连接（只显示一个连接入口）\n                if mysqlServiceInfo.installed {\n                    Button(action: {\n                        openDatabaseTab(name: " = ") {\n                // Conexão MySQL local (exibe apenas uma entrada de conexão)\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                // Exibe aviso somente em caso de falha\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        // Garante que o diretório de destino exista\n        var targetIsDirectory: ObjCBool = false\n        if !fileManager.fileExists(atPath: documentRoot, isDirectory: &targetIsDirectory) {\n            // Se o diretório de destino não existir, tenta criá-lo\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 conteúdo\n            VStack(alignment: .leading, spacing: 20) {\n                // Seleção de permissões de acesso\n                HStack(spacing: 16) {\n                    Text(";
"安装失败: \\(message)" = "Falha na instalação: \\(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 diretório 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    // Botões de ação\n    private var actionButtons: some View {\n        HStack(spacing: 6) {\n            // Botão de edição (exibido apenas para bancos de dados locais)\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            // Informações do domínio\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        // Tenta o diretório de recursos do SPM\n        if let resourceURL = Bundle.main.url(forResource: baseName, withExtension: ";
"请分析以下日志:\\n\\n\\(text)" = "Por favor, analise os seguintes logs:\\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    // Visualização de carregamento\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    /// O usuário modificou manualmente o diretório do site? (após modificação manual, ele não seguirá automaticamente o espaço de trabalho)\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    // Banco de dados\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 escuro MariaDB\n        } else {\n            let firstLetter = String(service.name.prefix(1)).uppercased()\n            return (firstLetter, Color.blue)\n        }\n    }\n\n    // Obtém a descrição do serviço\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 da saída: ";
"创建网站目录失败: \\(error.localizedDescription)\\n请检查路径权限或修改网站目录" = "Falha ao criar o diretório do site: \\(error.localizedDescription)\\nVerifique as permissões do caminho ou modifique o diretório do site";
"启动 \\(software.name) 失败" = "Falha ao 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: - Gerenciamento de cache de serviços\n\n    /// Carrega os serviços do cache, exibindo imediatamente a lista de serviços e atualizando o estado de forma assíncrona\n    private func loadServicesWithCache() async {\n        // Tenta carregar do cache\n        if let cachedServices = loadServicesFromCache(), !cachedServices.isEmpty {\n            // Exibe imediatamente a lista de serviços, mas define o estado como 'carregando'\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            // Atualiza o estado real de forma assíncrona\n            Task {\n                let updatedServices = await updateServicesState(cachedServices)\n                await MainActor.run {\n                    self.services = updatedServices\n                }\n            }\n        } else {\n            // Sem cache, executa detecção completa\n            await detectInstalledServicesAsync()\n        }\n    }\n\n    /// Carrega a lista de serviços do arquivo de cache\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        # Autenticação de arquivo 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  // Exibir janela pop-up de configurações em lote\n    @State private var currentImportingId: UUID?  // ID do site atualmente sendo importado\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    // Conteúdo do formulário\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // Nome do banco de dados\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        // Porta padrão\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ão para alterar o caminho do log\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 regras convertidas\n    private func copyConvertedRules() {\n        let pasteboard = NSPasteboard.general\n        pasteboard.clearContents()\n        pasteboard.setString(convertedRules, forType: .string)\n    }\n}\n\n// Visualização de comparação de diferenças nas regras\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    // Obter sufixo da unidade\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            // Informações do banco de dados\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    /// Criar diretórios para a versão especificada do 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    /// Criar diretórios para a versão especificada do 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: - Obtenção de caminhos (compatibilidade com caminhos antigos)\n\n    /// Obter o caminho do arquivo de configuração do MySQL\n    func getMySQLConfigPath(version: String) -> String {\n        if let config = config, config.isInitialized {\n            return config.mysqlConfigFile(version: version)\n        }\n        // Retornar ao caminho padrão do 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  // Não implantado\n    }\n\n    // Botões de ação\n    private var actionButtons: some View {\n        HStack(alignment: .center, spacing: 6) {\n            if site.isDeleted {\n                // Modo lixeira: exibir apenas restaurar e exclusão permanente\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        // Se o destino já existir, fazer backup primeiro\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    /// Obter o diretório raiz do site\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    // Configuração de logs\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  // Diretório do site (diretório raiz acessível pelo 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            // Progresso ou botões de ação\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: - Leitura do log de acesso\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: - Leitura do log de erros\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: - Análise do log de acesso\n    private func parseAccessLog(path: String, limit: Int) throws -> [LogEntry] {\n        // Usar o comando tail para ler as últimas N linhas, melhorando o desempenho\n        let process = Process()\n        process.executableURL = URL(fileURLWithPath: ";
")\n        }\n\n        // 2. 创建验证文件\n        let verificationFilePath = " = ")\n        }\n\n        // 2. Criar arquivo de verificação\n        let verificationFilePath = ";
"站点配置已导出到: \\(url.path)" = "A configuração do site foi exportada para: \\(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 formulário\n    private func submitForm() {\n        // Validar se as senhas coincidem\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 = " = ")\n            .map { $0.trimmingCharacters(in: .whitespaces) }\n            .filter { !$0.isEmpty }\n        return !domainList.isEmpty && !siteDirectory.isEmpty && !runningDirectory.isEmpty\n    }\n    \n    // Gerar senha aleatória\n    private func generateRandomPassword(length: Int = 16) -> String {\n        let characters = ";
"))\\n有效期: \\(validityDays) 天" = "))\\nValidade: \\(validityDays) dias";
")\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) 个候选站点" = "Encontrados \\(candidates.count) sites 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        // Criar novo site\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 da análise de configuração\nstruct ParsedConfigLine {\n    let lineNumber: Int          // Número da linha\n    let key: String              // Chave da configuração\n    let value: String            // Valor da configuração\n    let isEnabled: Bool          // Habilitado (não está comentado)\n    let originalLine: String     // Conteúdo original da linha\n    let section: String?         // Seção à qual pertence (ex.: [mysqld])\n}\n\n/// Analisador de arquivos de configuração\n@MainActor\nclass ConfigParser {\n\n    /// Analisar arquivo de configuração\n    /// - Parâmetros:\n    ///   - filePath: Caminho do arquivo de configuração\n    ///   - fileType: Tipo do arquivo de configuração\n    /// - Retorna: Array com os resultados da análise\n    static func parseConfigFile(at filePath: String, type fileType: ConfigFileType) throws -> [ParsedConfigLine] {\n        // Ler o conteúdo do arquivo\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    /// Analisar conteúdo do arquivo de configuração\n    /// - Parâmetros:\n    ///   - content: Conteúdo do arquivo de configuração\n    ///   - fileType: Tipo do arquivo de configuração\n    /// - Retorna: Array com os resultados da análise\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            // Ignorar linhas em branco\n            if trimmedLine.isEmpty {\n                continue\n            }\n\n            // Detectar título de seção [section]\n            if trimmedLine.hasPrefix(";
"当前版本暂不支持自动创建 FTP 账号，请先创建站点后手动配置。" = "A versão atual não suporta a criação automática de contas FTP. Por favor, crie o site primeiro e configure-o manualmente.";
"✗ \\(domain) 未配置" = "✗ \\(domain) não 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: carregar diretamente do diretório raiz do 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        // Estratégia alternativa: tentar carregar do diretório Documents do usuário\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            // Informações de configuração\n            VStack(alignment: .leading, spacing: 8) {\n                // Primeira linha: domínio e tipo de servidor\n                HStack(spacing: 8) {\n                    // Domínio\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 (clicável para aplicar) – parâmetros personalizados pelo usuário não possuem valor sugerido\n            if item.isUserDefined {\n                Text(LocalizedStringKey(";
"  // 当前阶段：下载中、编译中、安装中等\n    var detail: String = " = "  // Etapa atual: baixando, 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: " = "             // Caminho do sistema\n        ]\n\n        for path in possiblePaths {\n            if FileManager.default.fileExists(atPath: path) {\n                return path\n            }\n        }\n\n        // Tentar localizar usando o comando 'which'\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: - Verificação de acessibilidade do domínio\n    /// Verificar se o domínio pode acessar o site atual\n    /// - Parâmetros:\n    ///   - domain: Domínio a ser verificado\n    ///   - documentRoot: Diretório raiz do site\n    /// - Retorna: Status da verificação\n    func verifyDomainReachability(domain: String, documentRoot: String) async -> DomainVerificationStatus {\n        // 1. Verificar se é um domínio 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 recebidos externamente\n    var detectedRootPassword: String? = nil  // Senha atual preenchida automaticamente (local)\n    var isLocal: Bool = true  // É um banco de dados local?\n    var host: String = ";
")\n        }\n    }\n\n    // Apache 端口配置\n    @Published var apachePort: Int {\n        didSet {\n            UserDefaults.standard.set(apachePort, forKey: " = ")\n        }\n    }\n\n    // Configuração de porta do Apache\n    @Published var apachePort: Int {\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ão para visualizar logs\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                // Menu suspenso de configurações avançadas\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        // Fazer backup do arquivo de configuração antigo (se existir)\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    /// Analisar dados da linha\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/saída de rede\n\n    /// Enviar pacote\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                // Recursos adicionais\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 (incluindo menu suspenso de seleção de banco de dados)\n            databaseTitleBar\n\n            // Barra de abas (bancos de dados abertos)\n            if !databaseManager.openedDatabaseTabs.isEmpty {\n                databaseTabBar\n            }\n\n            // Área principal de conteúdo\n            VStack(spacing: 0) {\n                // Cartão com informações do banco de dados\n                databaseInfoCard\n\n                Divider()\n\n                // Barra de ferramentas superior\n                topToolbar\n\n                Divider()\n\n                // Conteúdo da tabela\n                tableContent\n\n                Divider()\n\n                // Paginação inferior\n                bottomPagination\n            }\n            .modifier(V2ConditionalCardModifier())\n            .zIndex(1) // Índice z menor que o das abas\n        }\n        .padding(.horizontal, 20) // Adicionar espaçamento ao redor de toda a visualização\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                // Banco de dados local: passar a senha detectada\n                RootPasswordSheet(detectedRootPassword: rootPassword)\n                    .environmentObject(databaseManager)\n            } else {\n                // Banco de dados remoto: passar a senha salva e as informações de conexão\n                let remotePassword = getRemoteDatabaseInfo()?.password ?? ";
"  // 完整路径（网站目录 + 运行目录）\n    @State private var runningDirectory = " = "  // Caminho completo (diretório do site + diretório de execução)\n    @State private var runningDirectory = ";
"正在启动 \\(software.name)..." = "Iniciando \\(software.name)...";
"\\(service.name) 重启失败,请检查日志" = "Falha ao reiniciar \\(service.name); verifique os logs";
" ? 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                // Recursos adicionais\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    // Carregar modelo\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        // Limpar resultados da conversão\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: - Verificação de execução do PHP\n\n    private func checkPHP(for site: Site) async -> HealthCheckResult {\n        var details: [String] = []\n\n        // Construir o caminho do diretório raiz do documento\n        let documentRoot = site.documentRoot\n        let publicDir = site.publicDirectory.isEmpty ? ";
") {\n                                // 认证插件错误，仍允许保存连接\n                                let tip = " = ") {\n                                // Erro no plug-in de autenticação, mas ainda permite salvar a conexão\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    // Coluna de backup\n    private var backupColumn: some View {\n        HStack(spacing: 4) {\n            Button(";
"MySQL 版本: \\(version)" = "Versão do 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 bancos de dados remotos, usar o phpMyAdmin fornecido pelo 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            // Opções de configuração\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    /// Gerar configuração de proxy Nginx ausente para configuração do 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        // Adicionar conteúdo após a área gerenciada\n        if !afterManaged.isEmpty {\n            newContent += afterManaged.joined(separator: ";
"写入 hosts 原始内容失败：\\(error.localizedDescription)" = "Falha ao gravar o conteúdo original do arquivo 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 PostgreSQL\n        } else if name.contains(";
" }\n\n    /// MySQL 配置文件路径\n    func mysqlConfigFile(version: String) -> String {\n        " = " }\n\n    /// Caminho do arquivo de configuração do MySQL\n    func mysqlConfigFile(version: String) -> String {\n        ";
") {\n                    // 提取生效日期\n                    let dateStr = line.replacingOccurrences(of: " = ") {\n                    // Extrair a data de vigência\n                    let dateStr = line.replacingOccurrences(of: ";
", 9000, 20),\n                    // 低优先级 - 缓存和工具\n                    (" = ", 9000, 20),\n                    // Baixa prioridade - cache e ferramentas\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: - Visualização de Configuração\n    private var configurationView: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            // Seleção do diretório pai\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            // Obter detalhes do processo\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 do banco de dados\n    var port: Int = 3306  // Porta do banco de dados\n\n    var body: some View {\n        VStack(spacing: 0) {\n            // Barra de título\n            headerBar\n\n            Divider()\n\n            // Conteúdo do formulário\n            formContent\n\n            Divider()\n\n            // Botões inferiores\n            bottomButtons\n        }\n        .frame(width: 500, height: 380)\n        .onAppear {\n            // Preencher automaticamente a senha atual\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 V2 fictícia\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            // Cabeçalho\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建议先启动这些服务，或选择其他运行中的服务。" = "Os seguintes serviços não estão em execução no momento:\\n\\n\\(serviceList)\\n\\nUsar serviços interrompidos pode impedir o acesso normal ao site.\\n\\nRecomenda-se iniciar primeiro esses serviços ou escolher outros que estejam em execução.";
"接管系统中已安装的 \\(serverType.displayName)，使用 80/443 端口" = "Assumir o controle do \\(serverType.displayName) já instalado no sistema, utilizando as portas 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 erro\n        }\n    }\n\n    /// Pré-carregar cache da lista de serviços do brew\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    // Se o site padrão está habilitado\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    // Coluna de backup\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        // Tratar php.ini\n        let brewIniPath = ";
", text: $organization)\n                        .macInput()\n                }\n\n                // 有效期\n                FormField(" = ", text: $organization)\n                        .macInput()\n                }\n\n                // Validade\n                FormField(";
"✅ Nginx 反向代理配置文件已生成: \\(configFilePath)" = "✅ Arquivo de configuração do proxy reverso Nginx gerado com sucesso: \\(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            // Observação\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    /// Ler configuração de porta de um arquivo\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 porta\n    private func validatePort() {\n        // Limpar mensagens de validação 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: - Tela de boas-vindas para o primeiro lançamento do espaço de trabalho\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 conteúdo\n            VStack(spacing: 24) {\n                Spacer()\n\n                // Ícone\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áfego\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 início de outras seções\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    /// Ler arquivo de log\n    private func readLogFile(path: String) -> String {\n        // Tratar caminhos com curinga (ex.: *.err do 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 se há alterações nas informações-chave\n                if tab.host != newHost || tab.port != newPort || tab.name != newName {\n                    // Informações alteradas, atualizar a guia\n                    // Gerar novo 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 resposta de autenticação\n    private func sendAuthResponse() throws {\n        var packet = Data()\n\n        // Sinais de capacidade do 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        // Tamanho máximo do pacote\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        // Nome de usuário (terminado em nulo)\n        packet.append(contentsOf: username.utf8)\n        packet.append(0)\n\n        // Hash da senha\n        let authResponse = generateAuthResponse()\n        packet.append(UInt8(authResponse.count))\n        packet.append(authResponse)\n\n        // Nome do plug-in de autenticação\n        packet.append(contentsOf: authPluginName.utf8)\n        packet.append(0)\n\n        try sendPacket(packet)\n    }\n\n    /// Gerar resposta de autenticação\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ão 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 pesquisa\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: - 默认文档视图\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        // 构建详细信息文本\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                // 搜索框\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            // 右侧：转换后的规则\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    // 选择目录\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: - 安装软件\n\n    /// Nginx 专用安装方法，支持不同安装模式\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 使用 innodb_redo_log_capacity 替代 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    // 测试连接\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    // 执行 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 = [";
"显示 \\(displayPorts.count) 个结果" = "显示 \\(displayPorts.count) 个结果";
"\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    /// 获取 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    // 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: - 获取已安装扩展\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            // 内容\n            ScrollView {\n                VStack(alignment: .leading, spacing: 20) {\n                    // 站点信息\n                    GroupBox(";
"  // 网站目录（站点根目录）\n    @State private var documentRoot = " = "  // 网站目录（站点根目录）\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                // 只在失败时显示提示\n                if !success {\n                    self.showServiceAlert(\n                        title: ";
"无法创建验证文件：\\(error.localizedDescription)" = "无法创建验证文件：\\(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    /// 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        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/// 端口同步规则\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 ";
"，按空白分割后会拆成多个 token\n                // 这里从尾部寻找包含 " = "，按空白分割后会拆成多个 token\n                // 这里从尾部寻找包含 ";
"  // 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        // 尝试使用 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                // Analisar informações do certificado\n                let certInfo = CertificateManager.shared.parseCertificateInfo(certPath: certPath)\n\n                // Criar registro do 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    /// Diretório de configuração dos sites do 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    // Substituir diretamente uma única entrada na área não gerenciada para manter seu estado não gerenciado\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 错误日志或系统日志），解释它代表什么错误或行为，用中文以大白话说明，并提供可能的修复建议或排查思路。" = "Você é um engenheiro de operações e programador experiente. Por favor, analise o seguinte trecho de log de software (pode ser um log de erro do Nginx, PHP, MySQL ou um log do sistema), explique em português simples o que esse erro ou comportamento representa e forneça sugestões de correção ou orientações para 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        // Se o diretório pai estiver vazio (diretório raiz), pule a verificação\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 a porta de escuta no arquivo de configuração do Nginx (escolhendo o arquivo conforme o estado do workspace)\n    private func configureNginxPort(port: Int) async throws {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        var modified = false\n\n        // Verificar se o workspace já foi inicializado\n        if let config = WorkspaceManager.shared.config, config.isInitialized {\n            // Modo workspace: modificar apenas o arquivo de configuração do workspace\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 o arquivo default.conf no diretório servers do workspace\n            let wsServersDir = ";
"   - 端口: \\(parsed.ports.map { String($0) }.joined(separator: " = "   - Porta: \\(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, // Sites copiados têm SSL desativado por padrão\n            status: .stopped,\n            isEnabled: false,\n            hasBackup: false,\n            backupCount: 0,\n            dailyTraffic: ";
"HTTP状态码：\\(httpResponse.statusCode)" = "Código de status 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            // Exibir botões diferentes conforme o estado de instalação\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: ";
"停用站点前自动备份" = "Fazer backup automático antes de desativar o site";
"\\(address) (本机)" = "\\(address) (máquina 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            // Se houver backend Apache, não adicionar a configuração de proxy do Nginx (já está refletida na configuração do Apache)\n        }\n\n        // Verificar se há conflito entre a configuração direta do Nginx (não proxy) e a configuração do Apache\n        for nginxConfig in nginxDirectConfigs {\n            var updatedConfig = nginxConfig\n\n            // Procurar configurações do Apache com o mesmo domínio\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                // Cenário 3: existem tanto configurações completas do Nginx quanto do Apache (conflito)\n                updatedConfig.completenessStatus = .conflictingConfigs\n                updatedConfig.relatedConfigPath = conflictApache.filePath\n            }\n\n            result.append(updatedConfig)\n        }\n\n        return result\n    }\n\n    /// Corrigir integridade da configuração (gerar automaticamente arquivos de configuração ausentes)\n    func repairConfigCompleteness(_ config: FoundVhostConfig) async throws {\n        switch config.completenessStatus {\n        case .complete:\n            // Configuração completa, nenhuma correção necessária\n            return\n\n        case .needsProxyConfig:\n            // Configuração do Apache existe, mas falta a configuração de proxy do Nginx\n            try await generateMissingNginxProxy(for: config)\n\n        case .missingBackendConfig:\n            // Configuração de proxy do Nginx existe, mas falta a configuração de backend do Apache\n            // Tentar pesquisar, com base nas informações do domínio, o diretório correspondente no workspace\n            try await generateMissingApacheBackend(for: config)\n\n        case .conflictingConfigs:\n            // Conflito de configurações; exige escolha manual do usuário, sem correção 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        // Ler configuração existente\n        guard var config = try? String(contentsOfFile: configPath, encoding: .utf8) else {\n            return false\n        }\n\n        // Modificar a porta de escuta\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    // Atualizar a lista de subdiretórios disponíveis\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            // Conteúdo das diferenças\n            HStack(spacing: 0) {\n                // Regras de origem\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            // Botões de ação\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    // Coluna de senha\n    private var passwordColumn: some View {\n        Group {\n            if database.userPassword.isEmpty {\n                // Banco de dados remoto já existente, senha não pode ser recuperada\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 o diretório padrão do site no Finder\n    func openDefaultSiteInFinder() {\n        if let path = getActiveDefaultSitePath() {\n            // Verificar se o diretório existe\n            if FileManager.default.fileExists(atPath: path) {\n                NSWorkspace.shared.selectFile(nil, inFileViewerRootedAtPath: path)\n            } else {\n                lastError = ";
"生效时间: \\(formatDate(certificate.validFrom))" = "Data de vigência: \\(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 erro\n        }\n    }\n\n    /// Analisar e armazenar em cache o caminho executável do Homebrew\n    private nonisolated func resolveBrewPath() -> String? {\n        // Já resolvido anteriormente, reutilizar diretamente\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 a 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: - Detecção de endereços IP\n\n    /// Detecta todos os endereços IP válidos do dispositivo 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                // Sempre adicionar 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            // Retornar as últimas linhas como mensagem de erro\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        // O appState ainda está sendo carregado; usar detecção de porta como solução alternativa\n        // Verificar se o cliente MySQL está presente\n        let mysqlExists = MySQLClientPaths.findMysql() != nil\n        if mysqlExists {\n            // Cliente MySQL encontrado; usar detecção de porta para verificar o estado de execução\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 com tempo limite\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            // Painel direito: regras 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    /// Gerar sugestão para conflito de portas\n    private func generatePortConflictSuggestion(\n        port: Int,\n        processName: String?,\n        software: SoftwareVersion\n    ) -> String {\n        let process = processName ?? ";
"❌ 目录不存在: \\(path)" = "❌ Diretório não 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 abas\n    private var databaseTabBar: some View {\n        ScrollView(.horizontal, showsIndicators: false) {\n            HStack(alignment: .bottom, spacing: 4) {\n                // Aba de todos os bancos de dados\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    /// Executar 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                # Porta de backend do Apache: \\(httpPort)\n                <VirtualHost *:\\(httpPort)>\n                    ServerName \\(domains.first ?? ";
"  // 网站目录（站点根目录）\n    @State private var runningDirectory = " = "  // Diretório do site (raiz do site)\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        // Dica flutuante para falha na detecção de senha\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 automaticamente a aba de conexão MySQL local por padrão\n            openDatabaseTab(name: ";
"上次修改: \\(formatDate(lastModified))" = "Última modificação: \\(formatDate(lastModified))";
"无法添加 \\(phpTap)，请检查网络连接" = "Não foi possível adicionar \\(phpTap); verifique sua conexão à internet";
"\n                ]\n\n                // 如果已经检测到 root 密码，附加密码参数\n                if let pwd = self.rootPassword, !pwd.isEmpty {\n                    arguments.insert(" = "\n                ]\n\n                // Se a senha root já tiver sido detectada, inserir o parâmetro de senha\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 conteúdo\n            contentArea\n\n            Divider()\n\n            // Botões inferiores\n            bottomButtons\n        }\n        .frame(minWidth: 700, minHeight: 500)\n        .alert(";
"\\(service.name) 启动失败,请检查日志" = "Falha ao iniciar \\(service.name); verifique os logs";
"\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  // Exibir nova senha em texto claro?\n    @State private var showCurrentPassword = false  // Exibir senha atual em texto claro? (modo remoto)\n    @State private var forgotPassword = false   // Modo esqueci minha senha (apenas local)\n    @State private var isSubmitting = false\n    @State private var showError = false\n    @State private var errorMessage = ";
"• 强力修复：根据站点信息重新生成配置文件，并补齐缺失的 hosts 记录" = "• Correção robusta: regenerar arquivos de configuração com base nas informações do site e preencher registros ausentes no arquivo hosts";
"  // 关联的用户名（通常与数据库名相同）\n    var userPassword: String = " = "  // Nome de usuário associado (geralmente igual ao nome do banco de dados)\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        // Verificação assíncrona se a porta está em 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    // Carregamento preguiçoso da configuração do formulário (carregado apenas ao alternar para o modo de formulário)\n    private func loadFormConfig() {\n        // Se os grupos de configuração já foram carregados, basta reanalisar os valores (o arquivo de configuração pode ter sido atualizado)\n        if configSections.isEmpty {\n            // Primeiro carregamento: criar grupos de configuração\n            configSections = ServiceConfigDefinitions.getConfigs(\n                for: service.name,\n                version: service.version\n            )\n        }\n        \n        // Analisar os valores do arquivo de configuração e atualizá-los no formulário (somente se o conteúdo do arquivo de configuração não estiver vazio)\n        if !configContent.isEmpty {\n            parseConfigValues(from: configContent)\n        }\n    }\n\n    // Analisar valores a partir do conteúdo do arquivo de configuração\n    private func parseConfigValues(from content: String) {\n        let lines = content.components(separatedBy: .newlines)\n\n        // Atualizar os valores dos itens de configuração conhecidos\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        // Carregar sincronamente a porta atual\n        loadCurrentPort()\n    }\n\n    // MARK: - Métodos de configuração de porta\n\n    /// Carregar a porta atual (sincronamente)\n    private func loadCurrentPort() {\n        let portData = loadPortData()\n        currentPort = portData.port\n        newPort = portData.port\n    }\n\n    /// Carregar dados da porta (executado em uma thread em 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            // Rótulo do 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ão de ação\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            // Detalhes (exibidos ao 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    // Obter mensagem de progresso\n    private func getProgressMessage() -> String {\n        // Retornar informações de progresso mais específicas com base no tipo de verificação\n        switch result.type {\n        case .hosts:\n            return ";
"配置迁移失败: \\(error.localizedDescription)" = "Falha na migração de configuração: \\(error.localizedDescription)";
"确定要删除数据库「\\(name)」吗？此操作不可恢复！" = "Tem certeza de que deseja excluir o banco de dados \"\\(name)\"? Esta operação não pode ser desfeita!";
"\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êntese direito assimé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    // Gerenciador de armazenamento JSON (usando actor para garantir segurança de thread)\n    private let storage: JSONStorage<Database>\n\n    init() {\n        // Inicializar o gerenciador de armazenamento\n        storage = JSONStorage<Database>(fileURL: databasesFilePath, backupURL: databasesBackupURL, saveDelay: 0.5)\n        loadDatabases()\n    }\n\n    // Carregar lista de bancos de dados (assíncrono)\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            // Chamar o método de teste de conexão do 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)" = "📁 Diretório de varredura: \\(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        // Obter extensões suportadas por esta versão\n        let allExtensions = ServiceConfigDefinitions.getExtensions(for: phpVersion)\n\n        // Obter extensões instaladas\n        let installed = await getInstalledExtensions(for: phpVersion)\n        installedExtensionsByVersion[phpVersion] = installed\n\n        // Construir lista de informações sobre extensões\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 bancos de dados do sistema\n        if hideSystemDatabases {\n            result = result.filter { !systemDatabases.contains($0.lowercased()) }\n        }\n\n        // Filtro de pesquisa\n        if !searchText.isEmpty {\n            result = result.filter { $0.localizedCaseInsensitiveContains(searchText) }\n        }\n\n        return result\n    }\n\n    // Paginação inferior\n    private var bottomPagination: some View {\n        HStack(spacing: 12) {\n            // Operações em lote\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                    // Rótulo de status\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ão de fechar (exibido apenas em caso de falha)\n                if isFailed, let dismiss = onDismiss {\n                    Button(action: dismiss) {\n                        Image(systemName: ";
"本地域名不支持 Let's Encrypt：\\(domain)" = "Domínios locais não são compatíveis com o 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    // Extrair mensagem de erro da saída do 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        // Salvar imediatamente\n        try await forceSaveDatabases()\n    }\n\n    // Excluir conexão remota com banco de dados\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                // Regras 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    // Redefinir forçadamente a senha do root (usado quando a senha é esquecida)\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    // Seleciona automaticamente o servidor web padrão com base no estado de execução\n    private func selectDefaultWebServer() {\n        let nginxRunning = isWebServerRunning(.nginx)\n        let apacheRunning = isWebServerRunning(.apache)\n\n        if nginxRunning && apacheRunning {\n            // Ambos estão em execução; seleciona Nginx como padrão\n            selectedWebServer = .nginx\n        } else if nginxRunning {\n            // Apenas o Nginx está em execução\n            selectedWebServer = .nginx\n        } else if apacheRunning {\n            // Apenas o Apache está em execução\n            selectedWebServer = .apache\n        }\n        // Se nenhum estiver em execução, mantém o valor padrão .nginx\n    }\n\n    // Verifica se o 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)" = "Falha ao instalar o 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: - Linha do diretório de dados\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                    // Rótulo da porta\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: - Inicialização\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: - Definição da estrutura de diretórios\n\n/// Lista de diretórios a serem criados\nenum WorkspaceDirectories {\n    /// Obtém todos os diretórios básicos necessários para criação\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    // Opções de conjunto de caracteres\n    private let charsetOptions = [";
") {\n            // MySQL 错误日志路径（按优先级尝试）\n            // 先尝试直接扫描目录找到 .err 文件\n            let mysqlDataDir = " = ") {\n            // Caminho do log de erros do MySQL (tentado por ordem de prioridade)\n            // Primeiro tenta escanear diretamente o diretório para encontrar arquivos .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ão de conversão e avisos\n            VStack(spacing: 16) {\n                Button(action: { convertRules() }) {\n                    HStack {\n                        Image(systemName: ";
"\n\n    // 端口配置相关\n    @State private var currentPort: String = " = "\n\n    // Configuração de portas\n    @State private var currentPort: String = ";
"✗ 证书文件不存在: \\(certPath)" = "✗ Arquivo de certificado não encontrado: \\(certPath)";
" }\n\n    // MARK: - Apache 路径\n\n    /// Apache 配置目录\n    var apacheConfPath: String { " = " }\n\n    // MARK: - Caminhos do Apache\n\n    /// Diretório de configuração do 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    // Testa conexão remota com o banco de dados (versão simplificada)\n    func testRemoteConnection(serverAddress: String, port: Int, username: String, password: String) async -> (success: Bool, message: String?, version: String?) {\n        // Usa MySQLClientPaths para localizar o cliente disponível\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                // Ignora versões já presentes na lista predefinida\n                if predefinedBrewNames.contains(packageName) {\n                    continue\n                }\n\n                // Extrai o número da versão\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            // Analisa a saída do lsof\n            let lines = output.components(separatedBy: .newlines)\n            guard lines.count > 1 else { return nil }\n\n            // A segunda linha contém informações do processo\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    // Obtém o caminho do arquivo de configuração\n    private func getConfigPath() -> String? {\n        var configPaths: [String] = []\n\n        switch service.name {\n        case ";
"批量导入: \\(candidate.directoryName)" = "Importação em lote: \\(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            // Abas superiores\n            tabSelector\n\n            Divider()\n\n            // Área de conteúdo\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    // Configura o 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            // Ignora o erro\n        }\n\n        // Solução alternativa: verifica se o processo 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        // Usa o caminho do 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: - Instalação do 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 para o caminho 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: - Folha de Configuração de Permissões Remotas\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) 天" = "Dias restantes: \\(remainingDays) dias";
"\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    /// Obter a lista de caminhos de pesquisa para a configuração do 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    /// Obter todos os certificados válidos\n    var validCertificates: [Certificate] {\n        return certificates.filter { $0.status == .valid }\n    }\n\n    /// Obter certificados que expirarão em breve\n    var expiringSoonCertificates: [Certificate] {\n        return certificates.filter { $0.status == .expiringSoon }\n    }\n\n    /// Obter certificados expirados\n    var expiredCertificates: [Certificate] {\n        return certificates.filter { $0.status == .expired }\n    }\n\n    // MARK: - Análise de Certificados\n\n    /// Estrutura de informações do certificado\n    struct CertificateInfo {\n        let domains: [String]\n        let issuer: String\n        let validFrom: Date\n        let validUntil: Date\n    }\n\n    /// Analisar informações do arquivo de certificado\n    func parseCertificateInfo(certPath: String) -> CertificateInfo? {\n        // Usar o comando openssl para ler as informações do 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                    // Botões de ação\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        // Parar o serviço independentemente de estar ou não em execução\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 o fundo da janela padrão do 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            // Nenhum servidor web em execução; usar o caminho padrão nas configurações\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 formulário\n    private func submitForm() {\n        Task {\n            isSubmitting = true\n            do {\n                // Analisar a lista de domínios (separados por vírgula)\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 conflito de porta\n        if let port = service.port, port > 0 {\n            if isPortOccupied(port) {\n                return .portConflict\n            }\n        }\n\n        // 2. Verificar se o arquivo de configuração existe\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 a sintaxe do arquivo de configuração\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        // Selecionar uma porta adequada a partir da lista de portas da configuração do site\n        var port: Int? = nil\n        if !site.ports.isEmpty {\n            // Se houver portas configuradas, usar a primeira que corresponda ao protocolo\n            if useSSL {\n                // Procurar porta HTTPS (443 ou outra)\n                port = site.ports.first { $0 == 443 } ?? site.ports.first { $0 > 1024 }\n            } else {\n                // Procurar porta HTTP (80 ou outra)\n                port = site.ports.first { $0 == 80 } ?? site.ports.first\n            }\n        }\n\n        // Construir URL\n        var urlString: String\n        if let port = port, port != defaultPort {\n            // Porta não padrão, precisa ser especificada na URL\n            urlString = ";
"配置文件 (\\(configFileName))" = "Arquivo de configuração (\\(configFileName))";
"]),\n        ]\n    }\n\n    // MARK: - Nginx 配置字典\n    static let nginx: [String: ConfigMeta] = [\n        // 基础配置\n        " = "]),\n        ]\n    }\n\n    // MARK: - Dicionário de configuração do Nginx\n    static let nginx: [String: ConfigMeta] = [\n        // Configuração 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 exibir a configuração do 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 se é um diretório\n                        var isDirectory: ObjCBool = false\n                        guard fileManager.fileExists(atPath: itemPath, isDirectory: &isDirectory),\n                              isDirectory.boolValue else {\n                            continue\n                        }\n\n                        // Ignorar diretórios 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            // Analisar linha de configuração\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    /// Analisar uma única linha de configuração\n    /// - Parâmetros:\n    ///   - line: conteúdo da linha de configuração\n    ///   - lineNumber: número da linha\n    ///   - section: seção à qual pertence\n    ///   - commentPrefix: prefixo de comentário\n    /// - Retorna: resultado da análise; retorna nil se não for uma linha de configuração 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 se é uma linha de comentário\n        if workingLine.hasPrefix(commentPrefix) {\n            isEnabled = false\n            // Remover o prefixo de comentário e espaços subsequentes\n            workingLine = String(workingLine.dropFirst(commentPrefix.count))\n                .trimmingCharacters(in: .whitespaces)\n        }\n\n        // Localizar o separador igual ou dois pontos\n        guard let separatorRange = workingLine.range(of: ";
"创建目录失败: \\(error.localizedDescription)" = "Falha ao criar diretório: \\(error.localizedDescription)";
"\n            // 创建 MySQL 配置文件模板\n            try await createPureFTPMySQLConfig(configDir: configDir)\n        } else {\n            config += " = "\n            // Criar modelo de arquivo de configuração do MySQL para o PureFTP\n            try await createPureFTPMySQLConfig(configDir: configDir)\n        } else {\n            config += ";
"删除配置文件失败: \\(config.filePath), 错误: \\(error.localizedDescription)" = "Falha ao excluir arquivo de configuração: \\(config.filePath), erro: \\(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            // Analisar 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    // Extrair valor da linha de configuração\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ão de configurações\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 se o arquivo 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: - Visualização durante análise\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. Executar solicitação HTTP\n        do {\n            let (data, response) = try await URLSession.shared.data(from: url)\n\n            // 5. Verificar resposta\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            // Apenas 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    /// Criar usuário\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ão de correção\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ão para visualizar logs\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 configuração do site\n    func exportSiteConfig(_ site: Site) -> String {\n        // Gerar JSON de configuração do site\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            // Exibir botões diferentes com base no status de instalação\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        // Status do serviço\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ão para copiar a porta\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    // Conteúdo do formulário\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 20) {\n                // Campo de senha atual (diferente para local e remoto)\n                if isLocal {\n                    // Banco de dados local: preenchido automaticamente, exibição somente leitura, com opção \"esqueci minha senha\"\n                    localCurrentPasswordField\n                } else {\n                    // Banco de dados remoto: senha atual deve ser inserida manualmente\n                    remoteCurrentPasswordField\n                }\n\n                // Nova senha (com botão 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    // Atualizar lista de bancos de dados (recarregar do servidor)\n    func refreshDatabases() async throws {\n        isLoading = true\n        errorMessage = nil\n\n        defer { isLoading = false }\n\n        // Obter lista de bancos de dados MySQL locais\n        let localDbs = await fetchLocalMySQLDatabases()\n        localMySQLDatabases = localDbs\n\n        saveDatabases()\n    }\n\n    // Obter lista real de bancos de dados MySQL locais\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    // Obter lista de versões PHP instaladas (incluindo status de execução)\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 }  // Ordenação decrescente, versões mais novas primeiro\n    }\n\n    // Verificar se uma versão específica do PHP está em execução\n    private func isPHPVersionRunning(_ version: String) -> Bool {\n        return softwareManager.phpVersions\n            .first { $0.version == version && $0.installed }?\n            .isRunning ?? false\n    }\n\n    // Obter lista de servidores web instalados (incluindo status de execução)\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)..." = "Parando \\(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            // Informações do site\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    // Layout antigo V1\n    private var v1Body: some View {\n        HStack(spacing: 0) {\n            // Menu lateral esquerdo\n            v1LeftMenu\n\n            Divider()\n\n            // Conteúdo lateral direito\n            rightContent\n        }\n        .background(Color(NSColor.windowBackgroundColor))\n    }\n    \n    // Layout V2 com efeito de vidro\n    private var v2Body: some View {\n        HStack(spacing: 0) {\n            // Menu lateral esquerdo\n            v2LeftMenu\n                .frame(width: 200)\n\n            // Área de conteúdo lateral direito\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    // Menu lateral esquerdo 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                // Encerramento bem-sucedido; atualizar 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 prioridade mais baixa (120+)\n                additionalVersions.append((displayName, packageName, 3306, 120 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// Detectar dinamicamente todas as versões instaladas do PostgreSQL (não presentes na 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            // Correspondência com formato postgresql ou 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    // Obter o certificado associado ao 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    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        // Destino do link simbólico semelhante a: ../Cellar/php@8.3/8.3.30/bin/php\n        // Extrair o nome do pacote\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            // Conteúdo das diferenças\n            HStack(spacing: 0) {\n                // Regras de origem\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            // Observação (clicável para edição)\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                // Etapa 4: Interromper o MySQL em modo seguro\n                safeProcess.terminate()\n                Thread.sleep(forTimeInterval: 2)\n\n                // Etapa 5: Iniciar normalmente o serviço 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 o caminho do workspace\n        if let config = workspaceManager.config, config.isInitialized {\n            paths.append(config.mysqlConfigFile(version: ver))\n        }\n\n        // Caminho padrão do Homebrew\n        paths.append(";
")\n        } else if status == 0x04 {\n            // 需要完整认证（需要 SSL 或明文密码）\n            throw MySQLError.authenticationFailed(" = ")\n        } else if status == 0x04 {\n            // Requer autenticação completa (SSL ou senha em texto simples)\n            throw MySQLError.authenticationFailed(";
"AI 分析结果" = "Resultado da análise por 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            // Em caso de falha, manter o registro e extrair informações de erro\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 Logs\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ão para carregar mais\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 itens pendentes para verificação\n            VStack(spacing: 6) {\n                PendingCheckRow(type: .hosts)\n                PendingCheckRow(type: .webServer)\n                PendingCheckRow(type: .ports)\n\n                // Verificação do PHP (se necessário)\n                if shouldShowPHPCheck(for: site) {\n                    PendingCheckRow(type: .php)\n                }\n\n                // Verificação SSL (se 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        // Verificação simples: exibir verificação do PHP se o diretório raiz de documentos existir\n        return FileManager.default.fileExists(atPath: site.documentRoot)\n    }\n}\n\n// MARK: - Linha de Verificação Pendente\n\nstruct PendingCheckRow: View {\n    let type: HealthCheckType\n\n    var body: some View {\n        HStack(spacing: 10) {\n            // Ícone de verificação pendente\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                // Analisar saída e atualizar progresso\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) falhou ao reiniciar (código de saída: \\(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    // Obter contagem de domínios únicos (sem repetições)\n    private var uniqueDomainsCount: Int {\n        Set(site.domains).count\n    }\n\n    var body: some View {\n        HStack(alignment: .center, spacing: 8) {\n            // Caixa de seleção\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 da sincronização de portas\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            // Recarregar o 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                // Coletar RewriteCond\n                if trimmed.hasPrefix(";
"⚠️ 端口 \\(port) 已被占用" = "⚠️ A porta \\(port) já está em 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: - Alteração da porta do 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    /// Arquivo de configuração principal do 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        // Detectando diferentes fases\n        if lowercased.contains(";
"版本: \\(log.version)" = "Versão: \\(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        // Carregando dados dos certificados\n        loadCertificates()\n    }\n\n    // MARK: - Persistência de dados\n\n    /// Carrega os dados dos certificados\n    func loadCertificates() {\n        // Primeiro, carrega os dados dos certificados salvos a partir do arquivo 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                // Redimensiona o ícone para um tamanho adequado à barra de status (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                // Se desejar que o ícone se torne automaticamente um contorno monocromático nos modos claro/escuro, defina 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 // A primeira linha é o cabeçalho\n        } catch {\n            return false\n        }\n    }\n\n    /// Obtém a lista de caminhos para arquivos de configuração correspondentes ao software (chamado na thread principal, caminhos pré-calculados)\n    private func getConfigPathsForVersion(_ version: SoftwareVersion) -> [String] {\n        let name = version.name.lowercased()\n        let pathProvider = ConfigPathProvider.shared\n\n        if name.contains(";
"FTP 账号和数据库自动创建将在后续版本开放，当前版本先确保站点主链路稳定可用。" = "A criação automática de contas FTP e bancos de dados estará disponível nas próximas versões; nesta versão, priorizamos a estabilidade e disponibilidade da cadeia principal do site.";
"\n    }\n\n    /// PHP 日志目录\n    var phpLogsPath: String { " = "\n    }\n\n    /// Diretório de logs do PHP\n    var phpLogsPath: String { ";
"查询失败: \\(msg)" = "Falha na consulta: \\(msg)";
"  // 用户密码\n    var userHost: String = " = "  // Senha do usuário\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        // Se o arquivo de destino já existir, exclua-o primeiro\n        if fileManager.fileExists(atPath: targetIndexFile) {\n            try fileManager.removeItem(atPath: targetIndexFile)\n        }\n        \n        // Copia o arquivo\n        do {\n            try fileManager.copyItem(atPath: errorPageIndexFile, toPath: targetIndexFile)\n            print(";
"恢复失败: \\(error.localizedDescription)" = "Falha na restauração: \\(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                // Se a gravação falhar, tente restaurar o backup\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    /// Gravação forçada (executada imediatamente, sem debounce)\n    func forceSave(_ items: [T]) async throws {\n        // Cancela a tarefa de debounce\n        saveTask?.cancel()\n        saveTask = nil\n        \n        // Grava imediatamente\n        try await saveImmediately(items)\n    }\n    \n    /// Valida a integridade dos dados\n    func validate(_ items: [T]) -> Bool {\n        // Validação básica: verifica se o array não é nulo\n        // É possível adicionar mais lógica de validação aqui\n        return true\n    }\n    \n    /// Restaura o backup\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        // Verifica o domínio\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 ferramentas superior\n    private var headerToolbar: some View {\n        HStack(spacing: 12) {\n            // Seleção da versão do 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            // Localização do banco de dados\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                            // Prioriza a porta 3306 (porta padrão do MySQL)\n                            // Exclui a porta 33060 (porta do MySQL X Protocol)\n                            if mysqlPorts.contains(3306) {\n                                continuation.resume(returning: 3306)\n                                return\n                            }\n\n                            // Se não houver 3306, retorna a primeira porta diferente de 33060\n                            if let port = mysqlPorts.first(where: { $0 != 33060 }) {\n                                continuation.resume(returning: port)\n                                return\n                            }\n\n                            // Se houver apenas 33060, retorna o padrão 3306\n                            if !mysqlPorts.isEmpty {\n                                continuation.resume(returning: 3306)\n                                return\n                            }\n                        }\n                    }\n\n                    // Se todas as tentativas falharem, retorna a porta padrão 3306\n                    continuation.resume(returning: 3306)\n                } catch {\n                    continuation.resume(returning: 3306)\n                }\n            }\n        }\n    }\n    \n    // Copia a porta para a área de transferência\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: - Visualização de carregamento\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: " = ", no índice: 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 a porta real em que o MySQL está escutando\n    // Observação:\n    // - Originalmente, esta verificação usava mysqlServiceInfo.isRunning como condição, mas mysqlServiceInfo depende de actualMySQLPort,\n    //   criando uma dependência circular do tipo \"quem veio primeiro, o ovo ou a galinha\", impedindo que a detecção de porta seja executada de fato; a interface exibe continuamente \"Detectando... / Parado\".\n    // - Agora, decidimos se devemos detectar a porta com base apenas no critério de \"instalado\"; a detecção da porta propriamente dita é feita usando lsof/netstat para verificar se há realmente um processo escutando nessa porta.\n    private func detectMySQLPort() {\n        // Se até mesmo o cliente MySQL não existir, não faz sentido detectar a porta\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    // Obter a porta real em que o MySQL está escutando\n    private func getMySQLListeningPort() async -> Int? {\n        return await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                // Usar o comando lsof para localizar a porta em que o MySQL está escutando\n                let process = Process()\n                process.executableURL = URL(fileURLWithPath: ";
"端口 \\(port) 已被运行中的 \\(conflictSoftware) 占用" = "A porta \\(port) já está sendo usada por \\(conflictSoftware), que está em execução";
"\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    // Atualizar linha de configuração\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 a seção [mysqld] (configuração do MySQL)\n            if trimmed.lowercased() == ";
" combined\n                </VirtualHost>\n\n                # HTTPS VirtualHost\n                # Apache 后端端口：\\(httpsPort)\n                <VirtualHost *:\\(httpsPort)>\n                    ServerName \\(domains.first ?? " = " combined\n                </VirtualHost>\n\n                # VirtualHost HTTPS\n                # Porta de backend do Apache: \\(httpsPort)\n                <VirtualHost *:\\(httpsPort)>\n                    ServerName \\(domains.first ?? ";
"(端口 \\(port))" = "(porta \\(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    // Estrutura de informações sobre versões do PHP\n    struct PHPVersionInfo: Identifiable {\n        let id: String\n        let version: String\n        let isRunning: Bool\n    }\n\n    // Obter lista de versões do PHP instaladas (incluindo status de execução)\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 }  // Ordenação decrescente, versões mais novas primeiro\n    }\n\n    // Verificar se uma versão específica do PHP está em execução\n    private func isPHPVersionRunning(_ version: String) -> Bool {\n        return softwareManager.phpVersions\n            .first { $0.version == version && $0.installed }?\n            .isRunning ?? false\n    }\n\n    // Obter 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                    // Status SSL\n                    GroupBox(";
"复制自: \\(site.name)" = "Copiado 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 pesquisa\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 erro\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        // Tratamento especial para o Apache: exibir opções de instalação\n        if software.id == ";
"准备安装 \\(software.name)..." = "Preparando para instalar \\(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: - Conteúdo do formulário\n    private var formContent: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 24) {\n                // Verificar o estado de instalação do acme.sh\n                if isCheckingAcme {\n                    acmeCheckingView\n                } else if !isAcmeInstalled {\n                    acmeInstallationView\n                } else {\n                    // Aviso importante\n                    importantNotice\n\n                    // Seleção de site\n                    siteSelectionSection\n\n                    // Parte de verificação de domínio\n                    if selectedSite != nil {\n                        domainVerificationSection\n                    }\n\n                    // Parte de solicitação de certificado (exibida somente após verificação bem-sucedida)\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. Criar o diretório 'servers' do workspace (mantendo compatibilidade com o Homebrew)\n        if !fm.fileExists(atPath: wsServersDir) {\n            try fm.createDirectory(atPath: wsServersDir, withIntermediateDirectories: true)\n        }\n\n        // 2. Copiar os arquivos de configuração do Homebrew para o workspace (se ainda não existirem no workspace)\n        if !fm.fileExists(atPath: wsNginxConf) && fm.fileExists(atPath: brewNginxConf) {\n            try fm.copyItem(atPath: brewNginxConf, toPath: wsNginxConf)\n            // Remover o bloco de exemplo 'server' da configuração principal para evitar conflitos com 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 arquivos de suporte essenciais do Nginx (mime.types, fastcgi_params, etc.)\n        let supportFiles = [\n            ";
"证书即将过期（\\(remainingDays)天）" = "Certificado prestes a expirar (\\(remainingDays) dias)";
"\n                # HTTP VirtualHost - 重定向到 HTTPS\n                # Apache 后端端口：\\(httpPort)\n                <VirtualHost *:\\(httpPort)>\n                    ServerName \\(domains.first ?? " = "\n                # VirtualHost HTTP - Redirecionar para HTTPS\n                # Porta de backend do Apache: \\(httpPort)\n                <VirtualHost *:\\(httpPort)>\n                    ServerName \\(domains.first ?? ";
"默认: \\(item.defaultValue)" = "Padrão: \\(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 conteúdo\n    private var contentArea: some View {\n        ScrollView {\n            VStack(alignment: .leading, spacing: 16) {\n                // Informações explicativas\n                infoCard\n\n                // Lista de sites\n                sitesList\n            }\n            .padding()\n        }\n    }\n\n    // Cartão de informações explicativas\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 se o certificado já consta na lista\n                if certificates.contains(where: { $0.certificatePath == certPath }) {\n                    continue\n                }\n\n                // Ler informações do 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    // Obtém o caminho do arquivo de configuração (prioriza o caminho do workspace)\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        // ===== Serviço de e-mail =====\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ão para selecionar arquivo\n            HStack {\n                Text(";
"\n    }\n\n    // 判断服务是否支持端口配置\n    private var supportsPortConfig: Bool {\n        [" = "\n    }\n\n    // Verifica se o serviço suporta a configuração de porta\n    private var supportsPortConfig: Bool {\n        [";
"确定要从备份 \\(backup.name) 恢复数据库吗？当前数据将被覆盖！" = "Deseja realmente restaurar o banco de dados a partir do backup \\(backup.name)? Os dados atuais serão sobrescritos!";
")\n            mkcertStatus = .installed\n            return\n        }\n\n        // 检查系统安装的 mkcert\n        let isInstalled = await isCommandAvailable(" = ")\n            mkcertStatus = .installed\n            return\n        }\n\n        // Verifica se o mkcert está instalado no sistema\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            // Analisa a saída\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    // Importa o banco de dados\n    func importDatabase(_ database: Database, from filePath: String, progressCallback: @escaping (Double) -> Void) async throws {\n        // Usa o comando mysql para importar\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// Enumeração de modos de instalação do 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            // Conteúdo do formulário\n            formContent\n\n            Divider()\n\n            // Botões 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 simples (na prática, deveria usar o framework 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 配置正常" = "Configuração do Nginx + Apache está normal";
", 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    /// Carrega os dados da porta do Nginx\n    private func loadNginxPortData() -> (port: String, error: String?) {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n\n        // Verifica a configuração do workspace\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    /// Faz backup do banco de dados\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            // Ícone e nome do item de verificação\n            Label(type.rawValue, systemImage: type.icon)\n                .font(.subheadline)\n\n            Spacer()\n\n            // Status de verificação pendente\n            Text(";
"\n\n    // 可用的数据库版本（从服务状态获取）\n    private let availableVersions = [\n        DatabaseVersion(version: " = "\n\n    // Versões disponíveis do banco de dados (obtidas a partir do status do serviço)\n    private let availableVersions = [\n        DatabaseVersion(version: ";
"建议端口: \\(suggestedPort)" = "Porta recomendada: \\(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                // Usa valor sugerido dinâmico (ajustado conforme a configuração do workspace)\n                let value = ServiceConfigDefinitions.getDynamicSuggestedValue(\n                    for: item.key,\n                    serviceName: service.name,\n                    version: service.version\n                ) ?? item.defaultValue\n\n                // Gera linha de configuração em formato diferente conforme o tipo de serviço\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        // Registra se estava em execução\n        let wasRunning = software.isRunning\n\n        // Se estiver em execução, interrompe o serviço primeiro\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                // Diretório de execução\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 Layout antigo\n    private var v1Body: some View {\n        HStack(spacing: 0) {\n            // Lista de categorias à esquerda\n            VStack(alignment: .leading, spacing: 0) {\n                // Espaço em branco superior para corresponder ao protótipo\n                Color.clear.frame(height: 16)\n                \n                // Lista de menus\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 de contagem\n                            Text(";
"错误 \\(errorCode): \\(message)" = "Erro \\(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            // Caminho alternativo\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. Verificar logs de erros recentes\n        if hasRecentError {\n            return .crashExit\n        }\n\n        return .manual\n    }\n\n    /// Verificar se uma porta está ocupada\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            # 🔄 Configuração de proxy reverso Nginx (backend Apache)\n            # Esta configuração encaminha solicitações para o servidor backend Apache\n            # Porta frontend: 80 (Nginx)\n            # Porta 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 porta do PHP-FPM\n    private func configurePHPFPMPort(phpVersion: String, port: Int) async throws {\n        let version = phpVersion == ";
"修改配置文件，将端口改为 \\(altPort)" = "Modifique o arquivo de configuração e altere a porta para \\(altPort)";
") {\n            // 简单验证是否像域名格式\n            let components = directoryName.split(separator: " = ") {\n            // Validação simples para verificar se o formato é semelhante ao de um domínio\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: - Detecção 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. Criar usuário\n        let createUserSQL = ";
"📊 扫描完成，共找到 \\(configs.count) 个配置" = "📊 Verificação concluída. Foram encontradas \\(configs.count) configurações";
"\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        // Se não for encontrado, adicione ao final da seção [mysqld]\n        if !found && mysqldSectionIndex >= 0 {\n            // Encontre o final da seção [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 comprimento da senha\n        guard password.count >= 8 else {\n            errorMessage = ";
"\n        主机: \\(tab.host)\n        端口: \\(tab.port)\n        用户名: root\n        密码: \\(password)\n        " = "\n        Host: \\(tab.host)\n        Porta: \\(tab.port)\n        Nome de usuário: root\n        Senha: \\(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    // Botões 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        // Voltar para a versão 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    // Formatar 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 linha estatí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 estatísticas do site\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: - Caminhos Redis\n\n    /// Obter caminho do arquivo de configuração do Redis (retornar primeiro arquivo 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            // Conteúdo do formulário\n            formContent\n\n            Divider()\n\n            // Botões inferiores\n            bottomButtons\n        }\n        .frame(width: 600, height: 550)\n        .onAppear {\n            // Selecionar por padrão a primeira versão em execução\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    /// Obtém o caminho do arquivo de configuração do 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) 个站点吗？此操作不可恢复。" = "Tem certeza de que deseja excluir permanentemente os \\(selectedSites.count) sites selecionados? Esta ação não pode ser desfeita.";
"\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 versões à direita\n            VStack(spacing: 0) {\n                // Barra de ferramentas\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: - Seção de configuração de site padrão\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    /// Obtém a lista de caminhos de pesquisa para arquivos de configuração do 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    // Verifica subdiretórios no diretório do site\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 do bloco 'server', calcula o nível de chaves\n                if inServerBlock {\n                    braceCount += line.filter { $0 == ";
") {\n                    // 提取过期日期\n                    let dateStr = line.replacingOccurrences(of: " = ") {\n                    // Extrai a data de expiração\n                    let dateStr = line.replacingOccurrences(of: ";
"))\n                    // 尝试提取时间部分（前面的日期时间）\n                    let datePattern = #" = "))\n                    // Tenta extrair a parte de horário (data e 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            // Verifica se o serviço está em execução\n            if !service.isRunning {\n                // Se o serviço não estiver em execução, exibe uma notificação na thread principal\n                await MainActor.run {\n                    // Usa NSAlert para exibir a mensagem de notificação\n                    let alert = NSAlert()\n                    alert.messageText = ";
")\n                }\n            }\n\n            // 解析 VirtualHost 端口\n            if trimmed.hasPrefix(" = ")\n                }\n            }\n\n            // Analisa a porta do VirtualHost\n            if trimmed.hasPrefix(";
"确定要卸载 \\(software.displayName) 吗？此操作无法撤销。" = "Tem certeza de que deseja desinstalar \\(software.displayName)? Esta ação não pode ser revertida.";
"\n\n        // 检查 Homebrew 是否安装\n        guard await isHomebrewInstalled() else {\n            installationStatus = .failed(" = "\n\n        // Verifica se o 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                    // Se não houver a seção [mysqld], adiciona-a ao final do arquivo\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                // Garante que o diretório 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                // Grava a nova configuração\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        // Trata o arquivo www.conf (versões mais recentes do 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    // Aplica predefinições de plano\n    private func applyPlanPreset(_ plan: MemoryPlan) {\n        let presetValues = PerformancePresets.getRecommendedValues(for: plan)\n\n        if plan == .custom {\n            // O modo personalizado não altera os valores atuais\n            return\n        }\n\n        for (key, value) in presetValues {\n            configValues[key] = value\n        }\n\n        updateEstimatedMemory()\n    }\n\n    // Seleção automática de plano\n    private func autoSelectPlan() {\n        selectedPlan = PerformancePresets.autoSelectPlan()\n        applyPlanPreset(selectedPlan)\n    }\n\n    // Obtém 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    // Verifica se o diretório existe e se há permissão de acesso\n    func validateDirectory(_ path: String) -> (exists: Bool, writable: Bool, error: String?) {\n        let fileManager = FileManager.default\n        var isDirectory: ObjCBool = false\n        \n        // Verifica se o caminho 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            // Aplica a configuração\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)" = "Falha na operação: \\(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 com nova porta\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            // Limpar 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) instalado(s)";
"站点 \\(site.name) 的配置文件已重新生成，并已补齐缺失的 hosts 记录" = "O arquivo de configuração do site \\(site.name) foi regenerado e os registros ausentes no arquivo hosts foram adicionados";
", 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            // Limpar arquivos temporários\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: - Proprietário do arquivo\n\n/// Informações sobre o proprietário do arquivo\nstruct FileOwner: Codable, Equatable {\n    let user: String\n    let group: String\n    let userId: Int?\n    let groupId: Int?\n}\n\n// MARK: - Tipo de editor\n\n/// Tipos de editores suportados\nenum EditorType: String, CaseIterable {\n    case vscode = ";
"加密失败: \\(error.localizedDescription)" = "Falha na criptografia: \\(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ão de atualização\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    /// Carregar dados da porta 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 nova porta para o PHP \\(phpVersion), sem afetar os serviços 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        // Garantir que o diretório exista\n        try? FileManager.default.createDirectory(\n            atPath: configDir,\n            withIntermediateDirectories: true\n        )\n\n        // Gerar configuração\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 também a configuração no caminho do Homebrew\n        let brewPath = ";
"✅ .htpasswd 文件已生成: \\(htpasswdPath)" = "✅ Arquivo .htpasswd gerado com sucesso: \\(htpasswdPath)";
"确定要删除选中的 \\(selectedDatabases.count) 个数据库吗？此操作不可恢复。" = "Tem certeza de que deseja excluir os \\(selectedDatabases.count) bancos de dados selecionados? Esta ação não pode ser desfeita.";
"\n            } else {\n                // 不强制HTTPS：HTTP和HTTPS都可以访问\n                configContent = " = "\n            } else {\n                // Sem HTTPS obrigatório: HTTP e HTTPS são ambos permitidos\n                configContent = ";
"以下站点修复失败，请处理后重试：\\n\\(failedSites.joined(separator: " = "A correção falhou para os seguintes sites. Por favor, resolva os problemas e tente novamente:\\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            // Nome de usuário\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 valor atual\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: - Instalação da CA mkcert\n\n    /// Instalar a autoridade certificadora mkcert (requer senha do usuário)\n    func installMkcertCA() async throws {\n        // Obter o caminho do mkcert (priorizar versão embutida)\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                                    // Se o nome do site estiver vazio, usar o primeiro domínio\n                                    if siteName.isEmpty || siteName == previousDomain {\n                                        siteName = String(firstDomain)\n                                        previousDomain = String(firstDomain)\n                                    }\n                                    // Gerar automaticamente o caminho do diretório (usando o primeiro domínio)\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    /// Verifica se o token é 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    /// Invalida o token\n    func invalidateToken() {\n        currentToken = nil\n        try? FileManager.default.removeItem(at: tokenStorageURL)\n    }\n\n    /// Salva o token em um arquivo\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        // Executa a instalação\n        let success = await runBrewCommandWithProgress(\n            brewPath,\n            arguments: [";
"仅显示用户 \\(NSUserName()) 的进程" = "Exibir apenas os processos do usuário \\(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            // Cabeçalhos da requisição\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        // O macOS requer privilégios de administrador para alterar proprietários; não implementado por enquanto\n        throw FileSystemError.notImplemented\n    }\n\n    // MARK: - Conteúdo do arquivo\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: - Ações 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            # Porta do backend 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    // Verifica permissões do diretório\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) 个配置文件:" = "✅ Arquivos de configuração atualizados com sucesso: \\(updatedFiles.count):";
"brew 输出:\\n\\(startResult.output)" = "Saída do brew:\\n\\(startResult.output)";
"[SiteManager] 恢复 hosts 条目失败: \\(domain) - \\(error)" = "[SiteManager] Falha ao restaurar entrada no hosts: \\(domain) - \\(error)";
"查看回收站" = "Visualizar lixeira";
"目录不存在：\\(directoryPath)" = "Diretório não 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        // Cria novo site\n        // Determina a porta de escuta com base na configuração 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  // Aviso de PHP parado\n    @State private var showServiceStoppedWarning = false  // Aviso de serviço parado (múltiplos serviços)\n    @State private var stoppedServices: [String] = []  // Lista de serviços parados\n    @StateObject private var hostsManager = HostsManager()\n\n    // Estrutura de informações sobre versões do 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        // Verifica se há condições de verificação de existência de arquivos\n        let hasFileCheck = conditions.contains { cond in\n            cond.contains(";
"证书申请失败：\\(message)" = "Falha ao solicitar certificado: \\(message)";
")\n        }\n    }\n\n    // 默认站点端口\n    @Published var defaultSitePort: Int {\n        didSet {\n            UserDefaults.standard.set(defaultSitePort, forKey: " = ")\n        }\n    }\n\n    // Porta padrão do site\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        // Sugestão 2: Modifique a configuração para usar outra porta\n        let name = software.name.lowercased()\n        if name.contains(";
":\n            configPaths = []  // Memcached 通常通过命令行参数配置\n        case " = ":\n            configPaths = []  // O Memcached geralmente é configurado via parâmetros de linha de comando\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                // Exibe informações detalhadas de progresso (se houver)\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                    (" = ") as? Int ?? 8080\n\n                // Define a ordem de detecção dos serviços: Nginx/Apache/MySQL vêm primeiro\n                let serviceDefinitions: [(name: String, brewName: String, port: Int, priority: Int)] = [\n                    // Alta prioridade - 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                            // A parte restante é a linha de comando\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                        // Se não for possível analisar a data, use todo o texto restante como linha de comando\n                        commandLine = remainingText\n                    }\n                }\n            }\n        } catch {\n            // Ignorar erro\n        }\n\n        // Usar lsof para obter o caminho do executável e o diretório de trabalho\n        if let lsofPath = findLsofPath() {\n            let lsofProcess = Process()\n            lsofProcess.executableURL = URL(fileURLWithPath: lsofPath)\n            lsofProcess.arguments = [";
"]),\n\n        // ===== OPcache 配置 =====\n        " = "]),\n\n        // ===== Configuração do 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 ?? " = "  // Diretório público (subdiretórios acessíveis pelo navegador do usuário)\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á na lixeira?\n    var deletedAt: Date?  // Data da exclusão\n    var advancedConfig: SiteAdvancedConfig = SiteAdvancedConfig()  // Configurações avançadas\n    var directorySize: Int64 = 0  // Capacidade do diretório do site (em bytes)\n    var directorySizeUpdatedAt: Date?  // Data da atualização da capacidade\n    var certificateId: UUID?  // ID do certificado SSL associado\n    var forceHttps: Bool = false  // Forçar HTTPS (redirecionamento automático de HTTP para HTTPS)\n    var sslMode: SSLMode = .nginxTermination  // Modo de arquitetura SSL (padrão: término SSL no Nginx)\n    var ports: [Int] = []  // Lista de portas monitoradas\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                // Se o serviço não for encontrado, verifique installedWebServers\n                let isInstalled = siteManager.installedWebServers.contains(webServer)\n                return (installed: isInstalled, running: false)\n            }\n        }\n    }\n\n    // Barra de título\n    private var headerBar: some View {\n        HStack {\n            Text(";
"检测到冲突：该域名在 \\(conflictServer.rawValue) 中也有配置" = "Conflito detectado: este domínio também está configurado em \\(conflictServer.rawValue)";
":\n            // MySQL 配置文件可能在多个位置\n            configPaths = [\n                " = ":\n            // O arquivo de configuração do MySQL pode estar em vários locais\n            configPaths = [\n                ";
")\n                    .foregroundColor(.secondary)\n            }\n\n            Divider()\n\n            // PHP 扩展管理\n            Text(" = ")\n                    .foregroundColor(.secondary)\n            }\n\n            Divider()\n\n            // Gerenciamento de extensões 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        // Carregar configuração da porta PHP\n        if let data = UserDefaults.standard.data(forKey: ";
"❌ 扫描配置目录失败: \\(error.localizedDescription)" = "❌ Falha ao escanear o diretório de configuração: \\(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            // Definir a lista de servidores web instalados\n            siteManager.installedWebServers = installedWebServers\n\n            let configs = await siteManager.scanVhostConfigs { path in\n                // Atualizar o diretório atual sendo escaneado\n                currentScanningPath = path\n            }\n            await MainActor.run {\n                foundConfigs = configs\n                isScanning = false\n                currentScanningPath = ";
"端口 \\(conflictPort)" = "Porta \\(conflictPort)";
"有効 \\(Self.allCases.count) / \\(Self.maxLanguageCapacity)" = "Ativo \\(Self.allCases.count) / \\(Self.maxLanguageCapacity)";
"无法执行 nginx -t: \\(error.localizedDescription)" = "❌ Falha ao executar nginx -t: \\(error.localizedDescription)";
"⚠️ 备份旧配置失败: \\(error.localizedDescription)" = "⚠️ Falha ao fazer backup da configuração antiga: \\(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            // Se a criação do usuário falhar, reverta o banco de dados\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 boas-vindas\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                // Se a rotação por tamanho for selecionada, exibir o campo de entrada de tamanho\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                // Observação\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. Atualizar a tabela mysql.db\n        let sql2 = ";
"\n        # 默认站点配置\n        # 由 JihePHPEnv 自动生成\n        # 生成时间: \\(ISO8601DateFormatter().string(from: Date()))\n\n        <VirtualHost *:\\(port)>\n            ServerName localhost\n            DocumentRoot " = "\n        # Configuração padrão do site\n        # Gerado automaticamente pelo JihePHPEnv\n        # Data de geração: \\(ISO8601DateFormatter().string(from: Date()))\n\n        <VirtualHost *:\\(port)>\n            ServerName localhost\n            DocumentRoot ";
"解密失败: \\(error.localizedDescription)" = "Falha na descriptografia: \\(error.localizedDescription)";
")\n\n        // 确保日志目录存在\n        try? fileManager.createDirectory(atPath: logsDir, withIntermediateDirectories: true)\n\n        // 日志文件路径\n        let accessLog = (logsDir as NSString).appendingPathComponent(" = ")\n\n        // Garantir que o diretório de logs exista\n        try? fileManager.createDirectory(atPath: logsDir, withIntermediateDirectories: true)\n\n        // Caminho do arquivo de logs\n        let accessLog = (logsDir as NSString).appendingPathComponent(";
"显示 \\(displayPorts.count) 个用户端口" = "Exibindo \\(displayPorts.count) portas de usuário";
",\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    // Seletor de abas\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                    // Mesmo que a inicialização falhe, se a senha for redefinida com sucesso, retornar sucesso\n                }\n\n                continuation.resume(returning: resetSuccess)\n            }\n        }\n    }\n\n    // Forçar redefinição de senha usando o parâmetro init-file\n    private func forceResetWithInitFile(brewPath: String) -> Bool {\n        // Criar arquivo SQL temporário\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 se o diretório existe\n        guard fileManager.fileExists(atPath: fullPath) else {\n            return false\n        }\n\n        // Verificar se há arquivos .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 o caminho do workspace\n        let workspaceManager = WorkspaceManager.shared\n        if let config = workspaceManager.config, config.isInitialized {\n            // Garantir que o diretório do workspace exista e criar o link simbólico\n            try await ensurePHPWorkspaceConfig(version: version, workspaceConfig: config)\n\n            // Verificar o arquivo de configuração no workspace\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: - Funções de funcionalidade\n\n    /// Atualizar lista de portas\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    /// Verificar portas de serviços comuns\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 serviços e portas a serem verificados\n        let servicePorts: [(service: String, port: Int)] = [\n            (";
"创建运行目录失败: \\(error.localizedDescription)\\n请检查路径权限或修改运行目录" = "Falha ao criar diretório de execução: \\(error.localizedDescription)\\nVerifique as permissões do caminho ou modifique o diretório de execução";
"❌ 加载证书数据失败: \\(error)" = "❌ Falha ao carregar dados do 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                // Permissões de acesso\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    /// Diretório de configuração do Nginx\n    private var nginxConfigDir: String {\n        // Priorizar o caminho do workspace\n        let workspaceManager = WorkspaceManager.shared\n        if let config = workspaceManager.config, config.isInitialized {\n            return config.nginxVhostsPath\n        }\n\n        // Alternativa para o caminho padrão do 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áginas de erro\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    // Alterar 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            // Atualizar status\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 uso estimado de memória (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    // Visão geral de todas as conexões\n    private var allConnectionsOverview: some View {\n        ScrollView {\n            VStack(spacing: 0) {\n                // Cabeçalho da tabela\n                tableHeader\n\n                // Lista de bancos de dados MySQL locais (obtidos em tempo real do MySQL)\n                if !databaseManager.localMySQLDatabases.isEmpty {\n                    localMySQLDatabasesSection\n                }\n\n                // Bancos de dados adicionados pelo usuário\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    // Visualização de estado vazio\n    private var emptyStateView: some View {\n        VStack(spacing: 12) {\n            Image(systemName: ";
"]),\n        // HTTP 配置\n        " = "]),\n        // HTTP Configuration\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                    // Output Area\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    // Filter configuration sections based on display type\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    // Raw mode view\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    // Configuration toolbar\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    /// Change PHP port\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            // Wait for initialization to complete\n            Thread.sleep(forTimeInterval: 5)\n            initProcess.terminate()\n        } catch {\n            return false\n        }\n\n        // Restart MySQL service\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: - Helper Views\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)" = "Invalid JSON format: \\(error.localizedDescription)";
"确定要删除数据库「\\(database.name)」吗？此操作不可恢复！" = "Are you sure you want to delete the database \"\\(database.name)\"? This action cannot be undone!";
")\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                // Expand/Collapse button\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    /// Create database and grant user privileges\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. Create database\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        // Filter by search text\n        if !searchText.isEmpty {\n            filtered = filtered.filter { line in\n                line.localizedCaseInsensitiveContains(searchText)\n            }\n        }\n\n        return filtered\n    }\n\n    // MARK: - Utility Functions\n\n    /// Load log file\n    private func loadLog(for service: ServiceLog) {\n        isLoading = true\n        logContent = ";
"剩余天数: \\(extractDays(from: site.sslStatus.rawValue)) 天" = "Days remaining: \\(extractDays(from: site.sslStatus.rawValue)) days";
")\n            return (domain, .auto)\n        }\n\n        // 规则3: 检查是否为 IPv4 地址\n        let ipv4Pattern = " = ")\n            return (domain, .auto)\n        }\n\n        // Rule 3: Check if it's an IPv4 address\n        let ipv4Pattern = ";
"已修复 \\(activeSites.count) 个站点的配置文件，并补齐缺失的 hosts 记录" = "Fixed configuration files for \\(activeSites.count) sites and added missing hosts entries";
", 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    // Capacity usage percentage (assuming max 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 User Information\nstruct MySQLUserInfo: Identifiable {\n    let id = UUID()\n    let user: String\n    let host: String\n\n    var displayName: String {\n        ";
"JSON 格式有误: \\(error.localizedDescription)。已尝试强制格式化，请检查红线标注处。" = "Invalid JSON format: \\(error.localizedDescription). Attempted forced formatting; please check locations marked with red lines.";
", 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        // Apache directory (prefer custom path)\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            // Form content\n            VStack(spacing: 16) {\n                // Database name (read-only)\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/// SSL Certificate Status\nenum CertificateStatus: Codable {\n    case valid          // Valid\n    case expiringSoon   // Expiring soon (within 30 days)\n    case expired        // Expired\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    // Get default configuration content\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)" = "Configurações de \\(sitesToExport.count) sites exportadas para: \\(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    /// Gerar configuração do 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            // Localizar e atualizar a configuração dentro da seção [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            // Adicionar formulário de regra\n            if showAddRuleForm {\n                VStack(alignment: .leading, spacing: 20) {\n                    HStack {\n                        Text(LocalizedStringKey(";
"已导出到: \\(url.path)" = "Exportado para: \\(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) // Largura fixa para a porta\n                }\n\n                // Nome de administrador e senha\n                HStack(spacing: 20) {\n                    FormField(";
":\n            // 提取主次版本号\n            let majorMinor = extractMajorMinorVersion(version)\n            configPaths = [\n                " = ":\n            // Extrair as versões principal e secundária\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        // Analisar o formato RewriteRule: RewriteRule padrão substituição [flags]\n        let parts = rule.components(separatedBy: .whitespaces).filter { !$0.isEmpty }\n        guard parts.count >= 3 else {\n            warnings.append(";
"\\(service.displayName) 可执行文件不存在：\\(binaryPath)" = "O executável de \\(service.displayName) não 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    // Carregar arquivo 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á desativado";
"❌ 更新失败 \\(failedFiles.count) 个配置文件:" = "❌ Falha na atualização de \\(failedFiles.count) arquivos de configuração:";
"\n        # MySQL 数据库认证\n        MySQLConfigFile             \\(configDir)/pureftpd-mysql.conf\n\n        " = "\n        # Autenticação do banco de dados MySQL\n        Arquivo de configuração MySQL             \\(configDir)/pureftpd-mysql.conf\n\n        ";
"端口 \\(port) 已被 \\(process) 占用\\n\\n建议：\\(suggestion)" = "A porta \\(port) já está em uso pelo processo \\(process)\\n\\nSugestão: \\(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                        // Detectar automaticamente a disponibilidade da porta\n                        Task {\n                            try? await Task.sleep(nanoseconds: 500_000_000) // Atraso de 0,5 segundo\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        // Analisar o arquivo de configuração\n        let lines = content.components(separatedBy: .newlines)\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n\n            // Analisar 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 site padrão\n\n    /// Abrir o site padrão no 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            // Conteúdo do formulário\n            formContent\n\n            Divider()\n\n            // Botões 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        // Gerar arquivos 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        // Analisar informações do 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 ferramentas 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] Falha ao adicionar entrada no 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        # Configuração do Nginx para phpMyAdmin\n        # Gerada automaticamente pelo JihePHPEnv\n        # Data de geração: \\(ISO8601DateFormatter().string(from: Date()))\n\n        location /phpmyadmin {\n            alias \\(phpMyAdminPath);\n            index index.php;\n\n            # Restrição de acesso 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            # Proibir acesso a arquivos sensíveis\n            location ~ /\\\\. {\n                deny all;\n            }\n        }\n        ";
" {\n                // V2 上一页按钮\n                MacButton(icon: " = " {\n                // Botão de página anterior no 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            // Botões de ação\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            // Caixa de seleção para ocultar bancos de dados do 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            // Informações detalhadas (se houver)\n            if let detail = log.errorDetail, !detail.isEmpty {\n                VStack(alignment: .leading, spacing: 4) {\n                    Text(";
"✅ 已为 Apache 配置生成 Nginx 代理: \\(siteName)" = "✅ Proxy Nginx gerado com sucesso 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 no 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 o colchete esquerdo e sua posição em 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 uma instância independente do \\(serverType.displayName), utilizando outras portas disponíveis";
", 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            // Alternador de modo de exibição\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                    // Se não encontrado, retorna o nome do banco de dados como nome de usuário padrão\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    // Gerar nome de usuário aleatório (letras aleatórias)\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 backups (por serviço/site)\n    private let maxBackupsPerService = 5\n\n    private init() {}\n\n    // MARK: - Operações de backup\n\n    /// Criar backup de arquivo de configuração\n    func createBackup(serviceName: String, configPath: String, note: String? = nil) throws -> ConfigBackup {\n        let fileManager = FileManager.default\n\n        // Verificar se o arquivo de origem existe\n        guard fileManager.fileExists(atPath: configPath) else {\n            throw BackupError.sourceFileNotFound\n        }\n\n        // Obter tamanho do arquivo\n        let attributes = try fileManager.attributesOfItem(atPath: configPath)\n        let fileSize = attributes[.size] as? Int64 ?? 0\n\n        // Gerar nome do arquivo de backup: nomedoserviço_datahora.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    /// Criar banco de dados\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 se a versão está na lista de versões compatíveis\n        return versions.contains { supportedVer in\n            majorMinor.hasPrefix(supportedVer) || supportedVer == majorMinor\n        }\n    }\n\n    /// Extrair números da versão principal e secundária\n    private func extractMajorMinorVersion(_ version: String) -> String {\n        // Tratar versões com sublinhado (ex.: ";
")\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 bancos de dados do sistema\n            Toggle(isOn: $hideSystemDatabases) {\n                Text(";
"✅ 已备份旧配置: \\(configFilePath)" = "✅ Backup da configuração antiga realizado com sucesso: \\(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    // Conteúdo do formulário\n    private var formContent: some View {\n        ScrollView {\n            VStack(spacing: 20) {\n                // Nome do banco de dados + conjunto de caracteres\n                databaseNameField\n\n                // Nome de usuário\n                usernameField\n\n                // Senha\n                passwordField\n\n                // Permissões de acesso\n                accessHostField\n\n                // Adicionar a\n                targetServerField\n\n                // Mensagem de erro\n                if let error = errorMessage {\n                    errorView(error)\n                }\n            }\n            .padding(24)\n        }\n    }\n\n    // Campo do nome do banco de dados\n    private var databaseNameField: some View {\n        HStack(alignment: .center, spacing: 12) {\n            Text(";
") {\n            // Nginx 配置文件信息\n            let nginxBin = " = ") {\n            // Informações do arquivo de configuração do 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            // Informações\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            // Botões de ação\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    /// Obtém informações do arquivo de configuração (usado para depurar falhas ao 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        // Verifica o estado do workspace\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            // Informações de ação\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ão de permissões (exibido apenas para bancos de dados locais)\n            if database.type == .local {\n                Button(";
"  // 密码检测状态\n    @State private var detectedMySQLVersion: String = " = "  // Estado da verificação de senha\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            // Status\n            statusView\n                .frame(width: 60, alignment: .leading)\n\n            // Backup\n            backupView\n                .frame(width: 50, alignment: .leading)\n\n            // Diretório raiz\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: - Executar 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 do GoAccess:\n        // -f: caminho do arquivo de log\n        // -o: formato de saída (json)\n        // --log-format=COMBINED: usa o formato de log 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 prioridade mais baixa (140+)\n                additionalVersions.append((displayName, packageName, 6379, 140 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// Pré-carrega o cache da lista de pacotes 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    // Verifica se o servidor web está em execução\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                        // Mensagem de estado vazio\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    // Adiciona uma entrada no hosts\n    func addEntry(_ entry: HostEntry) async throws {\n        // Verifica se já existe uma entrada idêntica\n        if entries.contains(where: { $0.hostname == entry.hostname && $0.ip == entry.ip }) {\n            // Já existe, não adiciona novamente\n            return\n        }\n\n        // Verifica conflito (mesmo nome de domínio, mas IP diferente)\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                // Aguarda o serviço parar completamente\n                Thread.sleep(forTimeInterval: 2)\n\n                // Etapa 2: Inicia o MySQL em modo seguro (ignorando verificação de permissões)\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            // Modifica o arquivo default.conf no diretório servers (incluído pela configuração 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        // Reinicia sequencialmente todos os serviços web\n        for service in webServices {\n            restartService(service)\n        }\n\n        // Exibe mensagem de sucesso\n        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {\n            let alert = NSAlert()\n            alert.messageText = ";
"错误: \\(error.localizedDescription)" = "Erro: \\(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. Verifica se há erros recentes nos logs de erro\n        if hasRecentError {\n            return .crashExit\n        }\n\n        // Por padrão, considera-se que foi interrompido manualmente\n        return .manual\n    }\n\n    /// Verifica se uma porta está ocupada por outro processo (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        // Garante que o diretório 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        // Caminho do diretório de logs\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    /// Garante que o tap do PHP já esteja adicionado (para instalar versões antigas do PHP)\n    private func ensurePHPTapInstalled() async -> Bool {\n        if await isTapInstalled(phpTap) {\n            return true\n        }\n        return await addTap(phpTap)\n    }\n\n    /// Obtém o nome completo do pacote brew para a versão do PHP\n    private func getFullPHPBrewName(_ packageId: String) -> String {\n        if legacyPHPVersions.contains(packageId) {\n            return ";
"检查 \\(sites.count) 个站点" = "Verificando \\(sites.count) sites";
"字符数: \\(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(";
"点击\\" = "Clique \"";
"\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    // Obter a letra do ícone do serviço e a cor de fundo (solução alternativa)\n    private var serviceIconInfo: (letter: String, color: Color) {\n        let name = service.name.lowercased()\n        if name.contains(";
"[VhostScan] 日志文件已创建: \\(logPath)" = "[VhostScan] Arquivo de log criado: \\(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        // Analisar o carimbo de data/hora do 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    /// Localizar o primeiro caminho existente do cliente mysql\n    static func findMysql() -> String? {\n        return mysql.first { FileManager.default.fileExists(atPath: $0) }\n    }\n\n    /// Localizar o primeiro caminho existente do cliente mysqldump\n    static func findMysqldump() -> String? {\n        return mysqldump.first { FileManager.default.fileExists(atPath: $0) }\n    }\n}\n\n// MARK: - Recomendações de compatibilidade com clientes (versão simplificada)\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] = []  // Lista real de bancos de dados no MySQL local\n    @Published var isLoading = false\n    @Published var errorMessage: String?\n\n    // MARK: - Gerenciamento de estado das abas (manter o estado ao alternar entre telas)\n    @Published var openedDatabaseTabs: [OpenedDatabaseTab] = []\n    @Published var activeTabId: String? = nil  // ID da aba ativa; nil indica exibição de toda a lista\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    // Menu da barra lateral\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    // Conteúdo das configurações\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    // Configurações gerais\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: - Métodos auxiliares\n    private func startScan() {\n        isScanning = true\n\n        Task {\n            do {\n                // Usar SecurityAnalysisService para realizar a verificação real\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                // Usar uma prioridade mais baixa (100+) para garantir que fique após as versões predefinidas\n                additionalVersions.append((displayName, packageName, 9000, 100 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// Detectar dinamicamente todas as versões do MySQL instaladas (não listadas nas versões predefinidas)\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            // Correspondência com formato mysql ou 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            // Alternador para exibir informações sobre a origem do IP\n            Toggle(isOn: $showIPInfo) {\n                Text(LocalizedStringKey(";
"\n        }\n\n        // 安装 Apache\n        let success = await runBrewCommandWithProgress(\n            brewPath, arguments: [" = "\n        }\n\n        // Instalar o Apache\n        let success = await runBrewCommandWithProgress(\n            brewPath, arguments: [";
"无法执行配置检测: \\(error.localizedDescription)" = "Não foi possível executar a detecção de configuração: \\(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: - Migração de configurações\n\n    /// Informações sobre migração de configurações\n    struct ConfigMigrationInfo {\n        let serviceName: String\n        let sourceFile: String\n        let targetFile: String\n        let targetDirectory: String\n    }\n\n    /// Verificar se é necessário migrar arquivos de configuração\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        // O caminho do workspace não existe, mas o caminho do Homebrew existe → necessita migração\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    /// Obter pares de caminhos de arquivos de configuração (caminho do workspace, caminho do Homebrew)\n    private func getConfigPaths(serviceName: String, version: String?, config: WorkspaceConfig) -> (String, String) {\n        switch serviceName.lowercased() {\n        case ";
"✅ 已删除配置文件: \\(artifact.path)" = "✅ Arquivo de configuração excluído: \\(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: - Caminhos das ferramentas embutidas\n\n    /// Obter o caminho embutido do mkcert\n    func getBundledMkcertPath() -> String? {\n        guard let bundlePath = Bundle.main.resourcePath else {\n            return nil\n        }\n\n        // Selecionar o binário apropriado com base na arquitetura\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    /// Sincronizar um único arquivo de configuração\n    private func syncFile(\n        path: String,\n        patterns: [PortReferencePattern],\n        newPort: Int\n    ) async -> (success: Bool, error: String?) {\n        do {\n            // Ler o conteúdo do arquivo\n            guard let content = try? String(contentsOfFile: path, encoding: .utf8) else {\n                return (false, ";
"配置解析错误: \\(message)" = "Erro ao analisar configuração: \\(message)";
")\n        }\n    }\n\n    // PHP 端口配置 (key: PHP版本如 " = ")\n        }\n    }\n\n    // Configuração de porta PHP (chave: versão do PHP como ";
"\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    /// Obter o diretório de configurações dos sites do 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            // Detectar 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    // Coluna de backup\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ão \"Próxima Página\" V2\n                MacButton(icon: ";
"✗ 私钥文件不存在: \\(keyPath)" = "✗ Arquivo de chave privada não encontrado: \\(keyPath)";
"保存失败: \\(error.localizedDescription)" = "Falha ao salvar: \\(error.localizedDescription)";
"\n\n        // 根据数据库模式添加配置\n        if databaseMode == .mysql {\n            config += " = "\n\n        // Adicionar configuração com base no modo do banco de dados\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    /// Ler a porta do arquivo www.conf do 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        // Procurar pela diretiva \"listen\"\n        for line in lines {\n            let trimmed = line.trimmingCharacters(in: .whitespaces)\n            if trimmed.hasPrefix(";
"无法\\(operation)站点：\\(prefix) 未安装 \\(missingServices.joined(separator: " = "Não foi possível \\(operation) o site: \\(prefix) não tem os seguintes serviços instalados: \\(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    /// Adicionar um 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                // Status do servidor web\n                webServerStatusRow\n\n                // Botões de ação\n                defaultSiteActions\n\n                // Mensagem de status\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    // Visualização do status 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        // Indica se é um diretório (requer navegação)\n\n    /// Obter o caminho real\n    func getActualPath(workspaceRoot: String?, homebrewPrefix: String) -> String? {\n        // Verificar se há uma configuração de workspace\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 o serviço 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    /// Obter lista de caminhos compatíveis para o cliente mysqldump (priorizando versões 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 dos principais IPs\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                    // Estender o fundo para cobrir a borda inferior do Content Card\n                    Rectangle()\n                        .fill(Color.white)\n                        .frame(height: 4)\n                        .offset(y: 2) // Deslocamento em relação à parte inferior da guia\n                }\n            },\n            alignment: .bottom\n        )\n        .contentShape(Rectangle())\n        .onTapGesture { onSelect() }\n        .onHover { hovering in isHovered = hovering }\n    }\n}\n\n// Forma retangular arredondada superior (sem borda inferior, usada para contorno)\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        // Começar no canto inferior esquerdo\n        path.move(to: CGPoint(x: 0, y: h))\n        // Linha vertical esquerda para cima\n        path.addLine(to: CGPoint(x: 0, y: r))\n        // Arco superior esquerdo\n        path.addArc(center: CGPoint(x: r, y: r), radius: r, startAngle: Angle(degrees: 180), endAngle: Angle(degrees: 270), clockwise: false)\n        // Linha horizontal superior\n        path.addLine(to: CGPoint(x: w - r, y: 0))\n        // Arco superior direito\n        path.addArc(center: CGPoint(x: w - r, y: r), radius: r, startAngle: Angle(degrees: 270), endAngle: Angle(degrees: 0), clockwise: false)\n        // Linha vertical direita para baixo\n        path.addLine(to: CGPoint(x: w, y: h))\n        // Não fechar o caminho, sem borda inferior\n        \n        return path\n    }\n}\n\n// Retângulo arredondado superior (caminho fechado, usado para preenchimento de fundo)\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: - Visualização de detalhes da conexão com o banco de dados\nstruct ConnectionDatabasesView: View {\n    let tab: OpenedDatabaseTab\n    let password: String  // Senha de conexão (pode estar vazia localmente, mas é obrigatória remotamente)\n    @ObservedObject var databaseManager: DatabaseManager\n    var hideSystemDatabases: Bool = true  // Ocultar bancos de dados do sistema?\n    @State private var databases: [String] = []\n    @State private var remoteDatabases: [MySQLDatabaseInfo] = []  // Informações detalhadas sobre bancos de dados remotos\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                // Obter endereços IP de todas as interfaces de rede\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                    // Tratar apenas endereços IPv4\n                    if family == UInt8(AF_INET) {\n                        let name = String(cString: interface.ifa_name)\n\n                        // Ignorar interface de loopback (já adicionamos manualmente 127.0.0.1)\n                        if name != ";
")\n    }\n\n    /// 打开目录\n    private func openDirectory(_ directoryPath: String) {\n        guard directoryPath != " = ")\n    }\n\n    /// Abrir diretório\n    private func openDirectory(_ directoryPath: String) {\n        guard directoryPath != ";
"AI 分析" = "Análise por IA";
"检测到 \\(service.name) 配置文件位于原始路径：\\n\\(migration.sourceFile)\\n\\n是否将其迁移到工作空间？\\n目标路径：\\(migration.targetFile)" = "Foi detectado o arquivo de configuração do \\(service.name) no caminho original:\\n\\(migration.sourceFile)\\n\\nDeseja migrá-lo para o workspace?\\nCaminho de destino: \\(migration.targetFile)";
"重载 \\(service.displayName) 失败：\\(message)" = "Falha ao recarregar o \\(service.displayName): \\(message)";
"]),\n\n        // ===== 连接配置 =====\n        " = "]),\n\n        // ===== Configuração de conexão =====\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                // Aguardar a inicialização do modo seguro do MySQL\n                Thread.sleep(forTimeInterval: 3)\n\n                // Etapa 3: Alterar a senha\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 se o arquivo de origem existe\n        guard fileManager.fileExists(atPath: errorPageIndexFile) else {\n            print(";
"端口 \\(newPort) 已被占用，请选择其他端口" = "A porta \\(newPort) já está em uso. Por favor, escolha outra porta";
", 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                                // Converter vírgulas chinesas ou de largura total para vírgulas em inglês\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            // Limpar o arquivo de verificação\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 o caminho embutido do 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            // Exibição do status de verificação\n            verificationStatusView\n\n            // Botão de verificação\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    /// Obter o diretório de logs do 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        // Verificar primeiro o caminho do workspace\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        // Voltar para o caminho do 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            // Garantir que o diretório \"servers\" exista\n            try FileManager.default.createDirectory(atPath: serversDir, withIntermediateDirectories: true)\n\n            // Gravar a configuração\n            try config.write(toFile: configPath, atomically: true, encoding: .utf8)\n\n            // Recarregar o 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            // Se um caminho personalizado for definido, exibir o botão de redefinição\n            if customLogPath != nil {\n                Button(";
"✓ 版本匹配: \\(site.phpVersion)" = "✓ Versão compatível: \\(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                    // Seletor de modelo\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 itens selecionados (\\(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        // Tentar carregar a imagem do Catálogo de Ativos\n        if let image = NSImage(named: assetName) {\n            return image\n        }\n        \n        // Lógica de fallback: tentar carregar do diretório Documents do usuário (se suporte futuro para papéis de parede personalizados for implementado)\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            // Falha na concessão de permissões; desfazer alterações\n            _ = await executeSQL(";
"))\n\n        menu.addItem(NSMenuItem.separator())\n\n        // 完全退出\n        menu.addItem(NSMenuItem(title: " = "))\n\n        menu.addItem(NSMenuItem.separator())\n\n        // Sair completamente\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 (clicável para aplicar)\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        【Informações Básicas】\n        Nome do serviço: \\(port.service)\n        Número da porta: \\(port.port)\n        Status: \\(port.status)\n\n        【Informações do Processo】\n        PID do processo: \\(port.pid)\n        Nome do processo: \\(port.processName)\n        PID do processo pai: \\(port.parentPid)\n        Usuário em execução: \\(port.user)\n        Hora de início: \\(port.startTime)\n\n        【Informações de Caminho】\n        Arquivo executável: \\(port.executablePath)\n        Diretório de trabalho: \\(port.workingDirectory)\n\n        【Linha de Comando】\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ão de tráfego do Douyin\n                    Button(action: {\n                        if let url = URL(string: ";
"⏭️  跳过非配置文件: \\(file)" = "⏭️  Ignorar arquivo não relacionado ao perfil: \\(file)";
"# \\(service.name) 配置文件\\n" = "# Perfil do \\(service.name)\\n";
"修改 \\(softwareName) \\(version) 的端口配置" = "Modificar a configuração de porta do \\(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    /// Detectar a porta em que o Nginx está escutando\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    // Extrair os números das versões principal e secundária\n    private func extractMajorMinorVersion(_ version: String) -> String {\n        let cleanVersion = version.split(separator: ";
"共 \\(ports.count) 个端口" = "Total de \\(ports.count) portas";
"剩余\\(days)天" = "Restam \\(days) dias";
", 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    /// Obter a lista de serviços em execução\n    private nonisolated func fetchRunningServices(brewPath: String) async -> Set<String> {\n        await withCheckedContinuation { continuation in\n            // Executar toda a operação em uma thread em segundo plano para evitar bloqueios\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: - Detecção de primeiro lançamento\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: - Detecção de dependências\n\n    func checkAllDependencies() async {\n        await checkMkcert()\n        await checkHomebrew()\n    }\n\n    func checkMkcert() async {\n        mkcertStatus = .checking\n\n        // Primeiro, verificar o mkcert embutido\n        if let bundledMkcert = getBundledMkcertPath() {\n            NSLog(";
"确定要删除数据库 \\(database.name) 吗？此操作不可恢复。" = "Tem certeza de que deseja excluir o banco de dados \\(database.name)? Esta ação não pode ser desfeita.";
")\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    // Visualização detalhada do progresso\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                // Observações\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    /// Diagnosticar a causa da falha ao iniciar e retornar um erro detalhado\n    private func diagnoseStartFailure(software: SoftwareVersion) async -> SoftwareError {\n        // Obter as portas que o software pode usar\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        // Se não for um problema de porta, retornar um erro genérico (informações específicas sobre a verificação de configuração serão inseridas por quem chama na variável 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                            // Usar a primeira linha não vazia como número da versão\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    // Testar conexão 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                    ";
"彻删" = "Excluir permanentemente";
",\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                // Se a adição da entrada no arquivo hosts falhar, registrar o erro, mas não interromper o fluxo de criação do site\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            .padding(.top, 16)\n\n            Spacer()\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .background(Color(NSColor.controlBackgroundColor))\n    }\n\n    // Área da lista de bancos de dados MySQL locais\n    private var localMySQLDatabasesSection: some View {\n        VStack(alignment: .leading, spacing: 0) {\n            // Título da seção\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    // Copiar informações de conexão para a área de transferência (formato 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    // Carregar ícone do software a partir do 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: - Caminho do site\n\n    /// Obter o diretório raiz do site\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 o openssl para gerar uma senha criptografada\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: - Analisar saída do 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                // Abas de bancos de dados já abertas\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ão permitir fechar quando restar apenas uma aba\n                        onClose: databaseManager.openedDatabaseTabs.count > 1 ? { closeDatabaseTab(tab) } : nil\n                    )\n                }\n            }\n            .padding(.horizontal, 24)\n            // Deixar espaço suficiente na parte inferior para a extensão da máscara da aba ativa, evitando que seja cortada pelo ScrollView\n            .padding(.bottom, 10)\n            .padding(.top, 10)\n        }\n        // Altura fixa para garantir estabilidade no layout\n        // Altura estimada: texto (16) + padding vertical (20) + padding inferior (10) + padding superior (10) ≈ 56\n        .frame(height: 60)\n        // Margem negativa para cima, para mover para cima o Card de conteúdo abaixo, criando sobreposição com a aba\n        // Esse valor deve ser ajustado em conjunto com o padding(.bottom, 10) acima\n        // Queremos que a borda superior do Card fique exatamente acima da borda inferior da aba\n        // Se houver um espaço em branco de 10 px na parte inferior do conteúdo do ScrollView, precisamos deslocar para cima cerca de 12 px\n        .padding(.bottom, BuildInfo.uiVersion == ";
") {\n                // 提取查询字符串参数\n                if replacement.contains(" = ") {\n                // Extrair parâmetros da string 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    /// Carregar dados da porta do 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 configuração do workspace\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 se o domínio é local\n    func isLocalDomain(_ domain: String) -> Bool {\n        let localPatterns = [\n            ";
"\\(webServerDisplayName(webServer)) 未安装，无法校验配置。" = "\\(webServerDisplayName(webServer)) não está instalado; impossível validar a configuração.";
"\n\n        // 生成 auth_basic location 块\n        let backendUrl = site.sslMode == .nginxTermination ? " = "\n\n        // Gerar bloco '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    /// Gerar linha única de configuração\n    /// - Parâmetros:\n    ///   - item: item de configuração\n    ///   - commentPrefix: prefixo de comentário\n    /// - Retorna: conteúdo da linha de configuração\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            // Endereço IP\n            Text(entry.ip)\n                .font(.system(size: 12, design: .monospaced))\n                .frame(width: 120, alignment: .leading)\n\n            // Nome de domínio\n            Text(entry.hostname)\n                .font(.system(size: 12))\n                .frame(minWidth: 200, alignment: .leading)\n\n            // Observação\n            Text(entry.comment ?? ";
"已启用 \\(Self.allCases.count) / \\(Self.maxLanguageCapacity)" = "Ativado \\(Self.allCases.count) / \\(Self.maxLanguageCapacity)";
")\n        }\n        \n        // 检查是否有写权限\n        guard fileManager.isWritableFile(atPath: path) else {\n            return (true, false, " = ")\n        }\n        \n        // Verificar permissão de gravação\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            // Capacidade\n            Text(databaseSize)\n                .font(.system(size: 12))\n                .foregroundColor(.secondary)\n                .frame(width: 80, alignment: .leading)\n\n            // Backup\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                    // Extrair horário de inicialização (formato: Mon Jan 1 12:00:00 2024)\n                    let remainingText = String(components[2...].joined(separator: ";
"迁移失败: \\(error.localizedDescription)" = "Falha na migração: \\(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                // Fim do bloco http\n                if httpBraceCount == 0 {\n                    break\n                }\n            }\n        }\n\n        // Gravar de volta no arquivo\n        let newContent = lines.joined(separator: ";
", isDefault: false),\n        // 国外大模型\n        AIModelProvider(name: " = ", isDefault: false),\n        // Modelos de IA internacionais\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            // Nome do banco de dados\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    // Obter lista de bancos de dados do servidor\n    private func fetchDatabasesFromServer() async -> (databases: [String]?, error: String?) {\n        // Capturar os valores necessários na thread principal\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 encontrar o cliente disponível\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ão de alteração de senha\n            Button(";
"过期时间: \\(formatDate(certificate.validUntil))" = "Data de expiração: \\(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 fluxos\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        // Aguardar estabelecimento da conexão\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ão de conversão e avisos\n            VStack(spacing: 16) {\n                MacButton(icon: ";
"]),\n        // 性能配置\n        " = "]),\n        // Configuração de desempenho\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    // Cabeçalho da tabela de bancos de dados\n    private var databasesTableHeader: some View {\n        HStack(spacing: 8) {\n            // Espaço reservado para caixa de seleção\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 configurações\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    // Verificar o status do servidor web e exibir uma notificação\n    private func checkWebServerStatus(webServer: WebServerType) async {\n        // Localizar o serviço correspondente\n        let serviceName: String\n        switch webServer {\n        case .nginx:\n            serviceName = ";
"安装 acme.sh 失败：\\(output)" = "Falha ao instalar o 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 os serviços (usando a lista em cache de pacotes 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                // Detecção dinâmica de versões adicionais do PHP (não listadas previamente)\n                let predefinedBrewNames = Set(serviceDefinitions.map { $0.brewName })\n                let additionalPHPVersions = self.detectAdditionalPHPVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // Detectar versões adicionais do 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                // Detecção dinâmica de versões adicionais do MySQL (não listadas previamente)\n                let additionalMySQLVersions = self.detectAdditionalMySQLVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // Detectar versões adicionais do 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                // Detecção dinâmica de versões adicionais do MariaDB (não listadas previamente)\n                let additionalMariaDBVersions = self.detectAdditionalMariaDBVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // Detectar versões adicionais do 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                // Detecção dinâmica de versões adicionais do PostgreSQL (não listadas previamente)\n                let additionalPostgreSQLVersions = self.detectAdditionalPostgreSQLVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // Detectar versões adicionais do 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                // Detecção dinâmica de versões adicionais do Redis (não listadas previamente)\n                let additionalRedisVersions = self.detectAdditionalRedisVersions(\n                    predefinedBrewNames: predefinedBrewNames\n                )\n\n                // Detectar versões adicionais do 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 prioridade\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        // Salvar no cache\n        saveServicesToCache()\n    }\n\n    /// Detectar dinamicamente todas as versões do PHP instaladas (não listadas previamente)\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            // Correspondência com formato php ou php@x.x\n            if packageName == ";
"发版版暂未开放自动创建" = "A versão atual ainda não oferece suporte à criação 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 caminho do 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    /// Gerar 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)" = "Falha ao configurar a porta do 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        // Gerar configurações de proxy diferentes com base na configuração SSL\n        if site.sslEnabled && !certPath.isEmpty {\n            if site.forceHttps {\n                // Forçar HTTPS: redirecionamento 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 usuários\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    // Linha de usuário\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 Nginx\n        } else if name.contains(";
")\n        }\n        \n        // 检查是否是目录\n        guard isDirectory.boolValue else {\n            return (false, false, " = ")\n        }\n        \n        // Verificar se é um diretório\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        // Ler saída em tempo real\n        var outputData = Data()\n        let outputHandle = pipe.fileHandleForReading\n\n        // Iniciar processo\n        try process.run()\n\n        // Ler saída em uma thread em 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        // Exclui endereços de link 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    /// Verifica se um único serviço está em execução\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        // Cria o site\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    // Botões inferiores da versão 2\n    private var bottomButtonsV2: some View {\n        HStack(spacing: 12) {\n            Spacer()\n            MacButton(icon: ";
") 配置语法错误" = ") Erro de sintaxe na configuração";
" }\n\n    // MARK: - MySQL 路径\n\n    /// MySQL 配置目录 (按版本)\n    func mysqlConfPath(version: String) -> String {\n        " = " }\n\n    // MARK: - Caminho do MySQL\n\n    /// Diretório de configuração do MySQL (por versão)\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        // Separa a área gerenciada da não gerenciada\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                // Ignora o conteúdo antigo dentro da área gerenciada\n                continue\n            } else if foundStart {\n                afterManaged.append(line)\n            } else {\n                beforeManaged.append(line)\n            }\n        }\n        \n        // Constrói o novo conteúdo do arquivo hosts\n        var newContent = beforeManaged.joined(separator: ";
"数据库 \\(database.name) 已成功备份" = "O banco de dados \\(database.name) foi feito backup com sucesso";
", 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            // Atualiza apenas o estado de execução do software específico, sem recarregar toda a lista\n            updateSoftwareRunningState(software.id, isRunning: true)\n        } else {\n            // Falha ao iniciar; verifica ocupação de porta e fornece sugestões detalhadas\n            let error = await diagnoseStartFailure(software: software)\n            // Coleta informações detalhadas de diagnóstico\n            let configTestOutput = await getConfigTestError(software: software)\n            var detailParts: [String] = []\n\n            // Adiciona informações sobre o caminho do arquivo de configuração\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 status\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            // Verifica os arquivos de configuração no diretório de vhosts do workspace\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: - Gerenciamento de tokens\n\n    /// Gera um novo token de autenticação\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    /// Gera um token aleatório 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    /// Obtém o diretório de logs do 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 comuns de ferramentas de varredura\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            // Nome do banco de dados\n            Text(database.name)\n                .font(.system(size: 12))\n                .frame(width: 120, alignment: .leading)\n\n            // Nome de usuário\n            Text(database.username.isEmpty ? database.name : database.username)\n                .font(.system(size: 12))\n                .frame(width: 100, alignment: .leading)\n\n            // Senha\n            passwordColumn\n                .frame(width: 100, alignment: .leading)\n\n            // Backup\n            backupColumn\n                .frame(width: 120, alignment: .leading)\n\n            // Permissões (clicável para editar)\n            permissionColumn\n                .frame(width: 90, alignment: .leading)\n\n            // Observação\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: - Análise de 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 erro\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: - Visualização de configurações 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    /// Obtém detalhes do processo\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: - Folha de lista de backups de configuração\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            // Informações de paginação\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 a porta de escuta no arquivo de configuração do Apache (modificando todos os arquivos de configuração existentes)\n    private func configureApachePort(port: Int) async throws {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        var modified = false\n\n        // Modifica a configuração do caminho do workspace\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: - Caminho padrão do site\n\n    /// Obtém o caminho do site padrão ativo do servidor web atual\n    func getActiveDefaultSitePath() -> String? {\n        let settings = AppSettings.shared\n        let preferred = settings.preferredWebServer.lowercased()\n\n        // Decide com base no servidor preferido e no estado de execução\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                # 🔄 Configuração de proxy reverso do Nginx (backend Apache)\n                # Esta configuração encaminha as solicitações para o servidor backend Apache\n                # Porta frontal: 80/443 (Nginx)\n                # Porta de backend: \\(backendHttpPort)/\\(backendHttpsPort) (Apache)\n                # ============================================\n\n                # Bloco do servidor HTTP - redireciona para HTTPS\n                server {\n                    listen 80;\n                    server_name \\(serverNames);\n                    return 301 https://$server_name$request_uri;\n                }\n\n                # Bloco do servidor HTTPS - proxy reverso para o Apache\n                server {\n                    listen 443 ssl;\n                    server_name \\(serverNames);\n\n                    # Configuração do 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                    # Configuração do proxy reverso\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 // Quando não é possível executar, não é considerado um erro de configuração\n        }\n    }\n\n    /// Verifica a sintaxe da configuração do Apache (nonisolated, pode ser chamada em threads em 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            // Mensagem\n            Text(message)\n                .font(.system(size: 15))\n                .foregroundColor(.secondary)\n\n            Divider()\n\n            // Botões de ação\n            HStack(spacing: 16) {\n                Button(action: onViewLog) {\n                    Label(";
" }\n\n    /// SSL 证书目录\n    var sslPath: String { " = " }\n\n    /// Diretório dos 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            // Detecta o bloco location\n            if trimmed.hasPrefix(";
"确定要删除远程数据库连接「\\(db.name)」吗？此操作不会删除远程服务器上的数据。" = "Tem certeza de que deseja excluir a conexão com o banco de dados remoto \"\\(db.name)\"? Esta operação não excluirá os dados no 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    // Obtém o valor da configuração com unidade\n    private func getValueWithUnit(_ item: ConfigItem) -> String {\n        guard let unit = item.unit else { return item.value }\n\n        // Conversão de unidades para configuração do MySQL\n        switch unit {\n        case ";
",\n                configLocations: [\n                    // Nginx 配置目录\n                    ConfigFileLocation(\n                        workspacePath: " = ",\n                configLocations: [\n                    // Diretório de configuração do Nginx\n                    ConfigFileLocation(\n                        workspacePath: ";
"数据库 \\(name) 已删除" = "Banco de dados \\(name) excluído";
"  // 数据库关联的用户名\n    @State private var showPermissionsSheet = false\n    @State private var showChangePasswordSheet = false\n    @State private var showImportSheet = false\n\n    private let systemDatabases = [" = "  // Nome de usuário associado ao banco de dados\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            // Detecta o fim do bloco\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. Verifica se o brew services relata uma saída com erro\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. Verifica o status do brew services\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. Link da versão-alvo\n        let targetBrewName = software.brewName ?? software.id\n        // Usa --overwrite --force para garantir que o link seja bem-sucedido\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                    // Todos bem-sucedidos, fecha o pop-up\n                    dismiss()\n                } else {\n                    // Houve falhas, exibe a mensagem de erro\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 ferramentas\n    private var toolbar: some View {\n        HStack(spacing: 12) {\n            Button(";
"保存 Nginx 配置失败: \\(error)" = "Falha ao salvar a configuração do 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                // Se o serviço não for encontrado, verifique installedWebServers\n                let isInstalled = siteManager.installedWebServers.contains(webServer)\n                return (installed: isInstalled, running: false)\n            }\n\n            // Inicie o cálculo periódico do tamanho dos sites (a cada 5 minutos)\n            siteManager.startPeriodicSizeCalculation(interval: 300)\n        }\n        .onDisappear {\n            // Pare o 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    // Cabeçalho da tabela\n    private var tableHeader: some View {\n        HStack(alignment: .center, spacing: 8) {\n            // Caixa de seleção \"Selecionar Tudo\"\n            Button(action: {\n                if !filteredSites.isEmpty && selectedSites.count == filteredSites.count {\n                    // Desmarcar todos\n                    selectedSites.removeAll()\n                } else {\n                    // Selecionar todos\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        // Atualizar documentRoot\n        documentRoot = fullPath\n        \n        // Obter o caminho do diretório pai\n        let parentDir = (siteDir as NSString).deletingLastPathComponent\n        \n        // Primeiro, criar todos os diretórios necessários\n        // 1. Criar o diretório pai (se não existir)\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                    // Rótulo do 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                    // Rótulo 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ão para alterar o caminho do log\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            // Ler o conteúdo do arquivo (limitando às últimas 1000 linhas)\n            let content = try String(contentsOfFile: path, encoding: .utf8)\n            let lines = content.components(separatedBy: .newlines)\n\n            // Manter apenas as últimas 1000 linhas\n            let maxLines = 1000\n            if lines.count > maxLines {\n                let lastLines = lines.suffix(maxLines)\n                return ";
"未知的 caching_sha2_password 状态: \\(status)" = "Status desconhecido do 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 se o nome do site já 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: - Gerenciamento de conexões\n\n    /// Configurar informações de conexão\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    /// Executar SQL usando o 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            // Converter o resultado em formato de texto separado por tabulações\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    // Atualizar memória estimada\n    private func updateEstimatedMemory() {\n        estimatedMemory = PerformancePresets.calculateEstimatedMemory(configs: configValues)\n    }\n\n    // Obter o caminho do arquivo de configuração do MySQL\n    private func getMySQLConfigPath() -> String? {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        let paths = [\n            ";
"智谱" = "Zhipu";
") {\n                                // 解析端口号\n                                let pattern = #" = ") {\n                                // Analisar o número da porta\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 da porta)\n    @Published var phpPorts: [String: Int] {\n        didSet {\n            if let data = try? JSONEncoder().encode(phpPorts) {\n                UserDefaults.standard.set(data, forKey: ";
"版本 \\(software.version)" = "Versão \\(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        // Analisar o carimbo de data/hora\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                // Nome de usuário\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 prioridade mais baixa (130+ )\n                additionalVersions.append((displayName, packageName, 5432, 130 + additionalVersions.count))\n            }\n        }\n\n        return additionalVersions\n    }\n\n    /// Detectar dinamicamente todas as versões do Redis instaladas (não listadas previamente)\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            // Correspondência com formato redis ou 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                // Nome do banco de dados\n                HStack(spacing: 16) {\n                    Text(";
"\n                )\n            } catch {\n                // 备份失败不阻止保存，只记录警告\n                DispatchQueue.main.async {\n                    self.updateLastStatusMessage(" = "\n                )\n            } catch {\n                // Falha no backup não impede a gravação; apenas registra um aviso\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        // Se o diretório do site estiver vazio ou não contiver o domínio completo, gere-o automaticamente\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 erro\n            }\n        }\n\n        return (executablePath, commandLine, user, parentPid, startTime, workingDirectory)\n    }\n\n    /// Verificar se uma determinada porta está ocupada\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    /// Salvar a lista de serviços no 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: - Análise de logs de erro\n    private func parseErrorLog(path: String, limit: Int) throws -> [SiteErrorLogEntry] {\n        // Usar o comando tail para ler as últimas N linhas\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            // Barra de progresso ou mensagem de erro\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()  // Conector remoto\n    @State private var showAddDatabaseSheet = false  // Folha de adição de banco de dados\n    @State private var isExporting = false  // Estado de exportação\n    @State private var exportingDatabase: String? = nil  // Banco de dados sendo exportado\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    // Visualização simplificada de conexão remota\n    private var remoteConnectionView: some View {\n        VStack(spacing: 24) {\n            Spacer()\n\n            // Ícone de conexão\n            Image(systemName: ";
"复制默认首页失败: \\(error.localizedDescription)" = "Falha ao copiar a página inicial padrão: \\(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            // Detectar início de outras seções\n            if trimmed.hasPrefix(";
"有 \\(missingPorts.count) 个端口未监听" = "Há \\(missingPorts.count) porta(s) não escutando";
")\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        // Atualizar status de backup\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    // Executar backup com 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 = [";
"  // 通过 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 = " = "  // Versão do MySQL detectada via SQL\n    @State private var showRemoteAddDatabaseSheet = false  // Folha de criação de banco de dados remoto\n    @StateObject private var remoteConnector = MySQLConnector()  // Conector de banco de dados remoto\n    @State private var hideSystemDatabases = true  // Ocultar bancos de dados do sistema? Padrão: ocultar\n    @State private var showMySQLConfigSheet = false  // Folha de arquivo de configuração do MySQL\n    @State private var showMySQLPerformanceSheet = false  // Folha de otimização de desempenho do MySQL\n\n    // Relacionado a dicas flutuantes\n    @State private var showPasswordTip = false  // Exibir dica de falha na verificação da senha\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: - Gerenciamento de configuração do Nginx\n\n    /// Diretório de instalação do phpMyAdmin\n    private var phpMyAdminPath: String {\n        let homebrewPrefix = ConfigPathProvider.shared.homebrewPrefix\n        // Verificar primeiro o caminho de instalação do Homebrew\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    // Converter domínio em nome de usuário (substituir pontos por sublinhados)\n    private func domainToUsername(_ domain: String) -> String {\n        // Remover número da porta\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            // Barra de título\n            HStack {\n                Text(";
"有备份(\\(database.backupCount))" = "Com backup (\\(database.backupCount))";
"✅ 已恢复配置: \\(backup.originalPath)" = "✅ Configuração restaurada com sucesso: \\(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    // Cabeçalho das informações de conexão\n    private var connectionHeader: some View {\n        HStack(spacing: 16) {\n            // Ícone de conexão\n            Image(systemName: tab.isLocal ? ";
"✅ Nginx 配置文件已生成: \\(configFilePath)" = "✅ Arquivo de configuração do Nginx gerado com sucesso: \\(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            // Excluir arquivo de teste\n            try? fileManager.removeItem(atPath: testFile)\n            return (false, true, nil, normalizedPath)\n        } else {\n            // Se a criação do arquivo de teste falhar, verificar isWritableFile\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: - Visualização de linha de conexão com banco de dados\nstruct ConnectionDatabaseRow: View {\n    let name: String\n    let host: String\n    let port: Int\n    let isLocal: Bool\n    let password: String  // Senha de conexão\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                // Validade\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            // Barra de título\n            headerBar\n\n            Divider()\n\n            // Conteúdo do formulário\n            formContent\n\n            Divider()\n\n            // Botões inferiores\n            bottomButtons\n        }\n        .frame(width: 500, height: 420)\n        .onAppear {\n            generateRandomPassword()\n        }\n    }\n\n    // Barra de título\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: ";