diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 62a4fc37..b4dbba57 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -7,6 +7,7 @@ services: - ../..:/workspaces:cached - ./go-path:/root/go - ./data/nginx:/etc/nginx + - /var/run/docker.sock:/var/run/docker.sock command: sleep infinity environment: - NGINX_UI_CERT_CA_DIR=https://pebble:14000/dir @@ -25,6 +26,28 @@ services: - nginx-ui networks: nginxui: + nginx-ui-3: + image: nginx-ui-dev + container_name: nginx-ui-3 + volumes: + - ../..:/workspaces:cached + - ./data/nginx-ui-3/nginx:/etc/nginx + - ./data/nginx-ui-3/nginx-ui:/etc/nginx-ui + - /var/run/docker.sock:/var/run/docker.sock + working_dir: /workspaces/nginx-ui + command: ./.devcontainer/node-supervisor.sh + depends_on: + - nginx-ui + networks: + nginxui: + nginx: + image: nginx-ui-dev + container_name: nginx + volumes: + - ./data/nginx-ui-3/nginx:/etc/nginx + command: sleep infinity + networks: + nginxui: pebble: image: ghcr.io/letsencrypt/pebble:latest volumes: diff --git a/.devcontainer/init-nginx.sh b/.devcontainer/init-nginx.sh index 38ce5206..361f8831 100755 --- a/.devcontainer/init-nginx.sh +++ b/.devcontainer/init-nginx.sh @@ -6,4 +6,4 @@ if [ "$(ls -A /etc/nginx)" = "" ]; then fi # start nginx -nginx -g "daemon off;" +nginx diff --git a/.devcontainer/start.sh b/.devcontainer/start.sh index 6ca743f6..fd788472 100755 --- a/.devcontainer/start.sh +++ b/.devcontainer/start.sh @@ -3,7 +3,7 @@ # install air go install github.com/air-verse/air@latest -# install zsh-autosuggestions +install zsh-autosuggestions git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions if ! grep -q "zsh-autosuggestions" ~/.zshrc; then diff --git a/api/config/add.go b/api/config/add.go index 37be3895..36591ffa 100644 --- a/api/config/add.go +++ b/api/config/add.go @@ -74,7 +74,12 @@ func AddConfig(c *gin.Context) { return } - output := nginx.Reload() + output, err := nginx.Reload() + if err != nil { + cosy.ErrHandler(c, err) + return + } + if nginx.GetLogLevel(output) >= nginx.Warn { cosy.ErrHandler(c, cosy.WrapErrorWithParams(config.ErrNginxReloadFailed, output)) return diff --git a/api/nginx/control.go b/api/nginx/control.go index c2038f5e..6426584d 100644 --- a/api/nginx/control.go +++ b/api/nginx/control.go @@ -5,24 +5,36 @@ import ( "github.com/0xJacky/Nginx-UI/internal/nginx" "github.com/gin-gonic/gin" + "github.com/uozi-tech/cosy" ) +// Reload reloads the nginx func Reload(c *gin.Context) { - output := nginx.Reload() + output, err := nginx.Reload() + if err != nil { + cosy.ErrHandler(c, err) + return + } c.JSON(http.StatusOK, gin.H{ "message": output, "level": nginx.GetLogLevel(output), }) } -func Test(c *gin.Context) { - output := nginx.TestConf() +// TestConfig tests the nginx config +func TestConfig(c *gin.Context) { + output, err := nginx.TestConfig() + if err != nil { + cosy.ErrHandler(c, err) + return + } c.JSON(http.StatusOK, gin.H{ "message": output, "level": nginx.GetLogLevel(output), }) } +// Restart restarts the nginx func Restart(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "message": "ok", @@ -30,8 +42,13 @@ func Restart(c *gin.Context) { go nginx.Restart() } +// Status returns the status of the nginx func Status(c *gin.Context) { - lastOutput := nginx.GetLastOutput() + lastOutput, err := nginx.GetLastOutput() + if err != nil { + cosy.ErrHandler(c, err) + return + } running := nginx.IsNginxRunning() diff --git a/api/nginx/router.go b/api/nginx/router.go index 4d67f68f..9ac59c95 100644 --- a/api/nginx/router.go +++ b/api/nginx/router.go @@ -11,7 +11,7 @@ func InitRouter(r *gin.RouterGroup) { r.POST("ngx/format_code", FormatNginxConfig) r.POST("nginx/reload", Reload) r.POST("nginx/restart", Restart) - r.POST("nginx/test", Test) + r.POST("nginx/test", TestConfig) r.GET("nginx/status", Status) // Get detailed Nginx status information, including connection count, process information, etc. (Issue #850) r.GET("nginx/detail_status", GetDetailStatus) diff --git a/api/nginx/status.go b/api/nginx/status.go index d7ea0a37..64d3e302 100644 --- a/api/nginx/status.go +++ b/api/nginx/status.go @@ -4,7 +4,6 @@ package nginx import ( - "errors" "net/http" "strings" "time" @@ -119,10 +118,14 @@ func ToggleStubStatus(c *gin.Context) { } // Reload Nginx configuration - reloadOutput := nginx.Reload() + reloadOutput, err := nginx.Reload() + if err != nil { + cosy.ErrHandler(c, err) + return + } if len(reloadOutput) > 0 && (strings.Contains(strings.ToLower(reloadOutput), "error") || strings.Contains(strings.ToLower(reloadOutput), "failed")) { - cosy.ErrHandler(c, errors.New("Reload Nginx failed")) + cosy.ErrHandler(c, cosy.WrapErrorWithParams(nginx.ErrReloadFailed, reloadOutput)) return } diff --git a/api/settings/settings.go b/api/settings/settings.go index a50fe36c..7dfcb692 100644 --- a/api/settings/settings.go +++ b/api/settings/settings.go @@ -25,6 +25,7 @@ func GetSettings(c *gin.Context) { settings.NginxSettings.ErrorLogPath = nginx.GetErrorLogPath() settings.NginxSettings.ConfigDir = nginx.GetConfPath() settings.NginxSettings.PIDPath = nginx.GetPIDPath() + settings.NginxSettings.StubStatusPort = settings.NginxSettings.GetStubStatusPort() if settings.NginxSettings.ReloadCmd == "" { settings.NginxSettings.ReloadCmd = "nginx -s reload" diff --git a/app/src/api/settings.ts b/app/src/api/settings.ts index 90d19288..d97e0b4f 100644 --- a/app/src/api/settings.ts +++ b/app/src/api/settings.ts @@ -64,6 +64,7 @@ export interface NginxSettings { reload_cmd: string restart_cmd: string stub_status_port: number + container_name: string } export interface NodeSettings { diff --git a/app/src/components/Breadcrumb/index.ts b/app/src/components/Breadcrumb/index.ts new file mode 100644 index 00000000..4211b0d7 --- /dev/null +++ b/app/src/components/Breadcrumb/index.ts @@ -0,0 +1,3 @@ +import Breadcrumb from './Breadcrumb.vue' + +export default Breadcrumb diff --git a/app/src/components/CodeEditor/CodeCompletion.ts b/app/src/components/CodeEditor/CodeCompletion.ts index 9b4fa896..7f8cbe89 100644 --- a/app/src/components/CodeEditor/CodeCompletion.ts +++ b/app/src/components/CodeEditor/CodeCompletion.ts @@ -12,12 +12,39 @@ function debug(...args: any[]) { } } +// Config file patterns and extensions +const CONFIG_FILE_EXTENSIONS = ['.conf', '.config'] +const SENSITIVE_CONTENT_PATTERNS = [ + /-----BEGIN [A-Z ]+ PRIVATE KEY-----/, + /-----BEGIN CERTIFICATE-----/, + /apiKey\s*[:=]\s*["'][a-zA-Z0-9]+["']/, + /password\s*[:=]\s*["'][^"']+["']/, + /secret\s*[:=]\s*["'][^"']+["']/, +] + function useCodeCompletion() { const editorRef = ref() const currentGhostText = ref('') + const isConfigFile = ref(false) const ws = openai.code_completion() + // Check if the current file is a configuration file + function checkIfConfigFile(filename: string, content: string): boolean { + // Check file extension + const hasConfigExtension = CONFIG_FILE_EXTENSIONS.some(ext => filename.toLowerCase().endsWith(ext)) + + // Check if it's an Nginx configuration file based on common patterns + const hasNginxPatterns = /server\s*\{|location\s*\/|http\s*\{|upstream\s*[\w-]+\s*\{/.test(content) + + return hasConfigExtension || hasNginxPatterns + } + + // Check if content contains sensitive information that shouldn't be sent + function containsSensitiveContent(content: string): boolean { + return SENSITIVE_CONTENT_PATTERNS.some(pattern => pattern.test(content)) + } + function getAISuggestions(code: string, context: string, position: Point, callback: (suggestion: string) => void, language: string = 'nginx', suffix: string = '', requestId: string) { if (!ws || ws.readyState !== WebSocket.OPEN) { debug('WebSocket is not open') @@ -29,6 +56,17 @@ function useCodeCompletion() { return } + // Skip if not a config file or contains sensitive content + if (!isConfigFile.value) { + debug('Skipping AI suggestions for non-config file') + return + } + + if (containsSensitiveContent(context)) { + debug('Skipping AI suggestions due to sensitive content') + return + } + const message = { context, code, @@ -57,8 +95,20 @@ function useCodeCompletion() { return } + if (!isConfigFile.value) { + debug('Skipping ghost text for non-config file') + return + } + try { const currentText = editorRef.value.getValue() + + // Skip if content contains sensitive information + if (containsSensitiveContent(currentText)) { + debug('Skipping ghost text due to sensitive content') + return + } + const cursorPosition = editorRef.value.getCursorPosition() // Get all text before the current cursor position as the code part for the request @@ -175,7 +225,7 @@ function useCodeCompletion() { debug('Editor initialized') - async function init(editor: Editor) { + async function init(editor: Editor, filename: string = '') { const { enabled } = await openai.get_code_completion_enabled_status() if (!enabled) { debug('Code completion is not enabled') @@ -184,6 +234,11 @@ function useCodeCompletion() { editorRef.value = editor + // Determine if the current file is a configuration file + const content = editor.getValue() + isConfigFile.value = checkIfConfigFile(filename, content) + debug(`File type check: isConfigFile=${isConfigFile.value}, filename=${filename}`) + // Set up Tab key handler setupTabHandler(editor) @@ -195,7 +250,9 @@ function useCodeCompletion() { if (e.action === 'insert' || e.action === 'remove') { // Clear current ghost text - debouncedApplyGhostText() + if (isConfigFile.value) { + debouncedApplyGhostText() + } } }) @@ -203,7 +260,9 @@ function useCodeCompletion() { editor.selection.on('changeCursor', () => { debug('Cursor changed') clearGhostText() - debouncedApplyGhostText() + if (isConfigFile.value) { + debouncedApplyGhostText() + } }) }, 2000) } diff --git a/app/src/components/EnvGroupTabs/index.ts b/app/src/components/EnvGroupTabs/index.ts new file mode 100644 index 00000000..d38d49cb --- /dev/null +++ b/app/src/components/EnvGroupTabs/index.ts @@ -0,0 +1,3 @@ +import EnvGroupTabs from './EnvGroupTabs.vue' + +export default EnvGroupTabs diff --git a/app/src/components/EnvIndicator/index.ts b/app/src/components/EnvIndicator/index.ts new file mode 100644 index 00000000..92a202b6 --- /dev/null +++ b/app/src/components/EnvIndicator/index.ts @@ -0,0 +1,3 @@ +import EnvIndicator from './EnvIndicator.vue' + +export default EnvIndicator diff --git a/app/src/components/ICP/index.ts b/app/src/components/ICP/index.ts new file mode 100644 index 00000000..af5fccc8 --- /dev/null +++ b/app/src/components/ICP/index.ts @@ -0,0 +1,3 @@ +import ICP from './ICP.vue' + +export default ICP diff --git a/app/src/components/Logo/index.ts b/app/src/components/Logo/index.ts new file mode 100644 index 00000000..372f4623 --- /dev/null +++ b/app/src/components/Logo/index.ts @@ -0,0 +1,3 @@ +import Logo from './Logo.vue' + +export default Logo diff --git a/app/src/components/NginxControl/index.ts b/app/src/components/NginxControl/index.ts new file mode 100644 index 00000000..c8116bf6 --- /dev/null +++ b/app/src/components/NginxControl/index.ts @@ -0,0 +1,3 @@ +import NginxControl from './NginxControl.vue' + +export default NginxControl diff --git a/app/src/components/NodeSelector/index.ts b/app/src/components/NodeSelector/index.ts new file mode 100644 index 00000000..a47f1e99 --- /dev/null +++ b/app/src/components/NodeSelector/index.ts @@ -0,0 +1,3 @@ +import NodeSelector from './NodeSelector.vue' + +export default NodeSelector diff --git a/app/src/components/Notification/index.ts b/app/src/components/Notification/index.ts new file mode 100644 index 00000000..0caca02d --- /dev/null +++ b/app/src/components/Notification/index.ts @@ -0,0 +1,3 @@ +import Notification from './Notification.vue' + +export default Notification diff --git a/app/src/components/OTPInput/index.ts b/app/src/components/OTPInput/index.ts new file mode 100644 index 00000000..77378f29 --- /dev/null +++ b/app/src/components/OTPInput/index.ts @@ -0,0 +1,3 @@ +import OTPInput from './OTPInput.vue' + +export default OTPInput diff --git a/app/src/components/PageHeader/index.ts b/app/src/components/PageHeader/index.ts new file mode 100644 index 00000000..e30aad17 --- /dev/null +++ b/app/src/components/PageHeader/index.ts @@ -0,0 +1,3 @@ +import PageHeader from './PageHeader.vue' + +export default PageHeader diff --git a/app/src/components/ReactiveFromNow/index.ts b/app/src/components/ReactiveFromNow/index.ts new file mode 100644 index 00000000..6c637061 --- /dev/null +++ b/app/src/components/ReactiveFromNow/index.ts @@ -0,0 +1,3 @@ +import ReactiveFromNow from './ReactiveFromNow.vue' + +export default ReactiveFromNow diff --git a/app/src/components/SensitiveString/index.ts b/app/src/components/SensitiveString/index.ts new file mode 100644 index 00000000..17e9a8ec --- /dev/null +++ b/app/src/components/SensitiveString/index.ts @@ -0,0 +1,3 @@ +import SensitiveString from './SensitiveString.vue' + +export default SensitiveString diff --git a/app/src/components/SetLanguage/index.ts b/app/src/components/SetLanguage/index.ts new file mode 100644 index 00000000..f02f7b97 --- /dev/null +++ b/app/src/components/SetLanguage/index.ts @@ -0,0 +1,3 @@ +import SetLanguage from './SetLanguage.vue' + +export default SetLanguage diff --git a/app/src/components/SwitchAppearance/SwitchAppearance.vue b/app/src/components/SwitchAppearance/SwitchAppearance.vue index ff2baaba..e34aa1c0 100644 --- a/app/src/components/SwitchAppearance/SwitchAppearance.vue +++ b/app/src/components/SwitchAppearance/SwitchAppearance.vue @@ -1,6 +1,6 @@