diff --git a/.air.toml b/.air.toml index 19f54c2ac..ac2a1ea23 100644 --- a/.air.toml +++ b/.air.toml @@ -7,7 +7,7 @@ tmp_dir = "tmp" [build] # Just plain old shell command. You could use `make` as well. -cmd = "CGO_ENABLED=1 go build -tags=jsoniter,unembed -ldflags=\"-X 'github.com/0xJacky/Nginx-UI/settings.buildTime=$(date +%s)'\" -v -o ./tmp/main ." +cmd = "CGO_ENABLED=1 go build -tags=jsoniter,unembed,dev -ldflags=\"-X 'github.com/0xJacky/Nginx-UI/settings.buildTime=$(date +%s)'\" -v -o ./tmp/main ." # Binary file yields from `cmd`. bin = "tmp/main" # Customize binary. @@ -15,7 +15,7 @@ full_bin = "APP_ENV=dev APP_USER=air ./tmp/main" # Watch these filename extensions. include_ext = ["go", "tpl", "tmpl", "html", "toml", "po", "conf"] # Ignore these filename extensions or directories. -exclude_dir = ["assets", "tmp", "vendor", "app/node_modules", "upload", "docs", "resources", ".idea"] +exclude_dir = ["assets", "tmp", "vendor", "app/node_modules", "upload", "docs", "resources", ".idea", "cmd", ".go", "log-index"] # Watch these directories if you specified. include_dir = [] # Exclude files. @@ -33,7 +33,7 @@ delay = 1000 # ms # Stop running old binary when build errors occur. stop_on_error = true # Send Interrupt signal before killing process (windows does not support this feature) -send_interrupt = false +send_interrupt = true # Delay after sending Interrupt signal kill_delay = 500 # ms diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 000000000..b978e1e45 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,28 @@ +{ + "permissions": { + "allow": [ + "Bash(pnpm typecheck:*)", + "Bash(pnpm lint:*)", + "Bash(npm run build:*)", + "Bash(go build:*)", + "Bash(npm run typecheck:*)", + "Bash(mkdir:*)", + "Bash(mv:*)", + "Bash(grep:*)", + "Bash(go test:*)", + "mcp__context7__resolve-library-id", + "mcp__context7__get-library-docs", + "Bash(find:*)", + "Bash(sed:*)", + "Bash(cp:*)", + "mcp__eslint__lint-files", + "Bash(go generate:*)", + "Bash(pnpm eslint:*)", + "Read(//workspaces/cosy/settings/**)", + "Bash(go doc:*)", + "Bash(pnpm exec eslint:*)", + "Bash(go:*)" + ], + "deny": [] + } +} \ No newline at end of file diff --git a/.cursor/mcp.json b/.cursor/mcp.json new file mode 100644 index 000000000..846943055 --- /dev/null +++ b/.cursor/mcp.json @@ -0,0 +1,13 @@ +{ + "mcpServers": { + "eslint": { + "command": "npx", + "args": ["@eslint/mcp@latest"], + "env": {} + }, + "context7": { + "command": "npx", + "args": ["-y", "@upstash/context7-mcp"] + } + } +} \ No newline at end of file diff --git a/.cursor/rules/backend.mdc b/.cursor/rules/backend.mdc new file mode 100644 index 000000000..27a215ad5 --- /dev/null +++ b/.cursor/rules/backend.mdc @@ -0,0 +1,56 @@ +--- +description: +globs: **/**/*.go +alwaysApply: false +--- +# Cursor Rules +You are an expert in Go, Gin, Gorm, Gen, Cosy (https://cosy.uozi.org/) with a deep understanding of best practices and performance optimization techniques in these technologies. + +## 1. Code Style and Structure + +- **Concise and Maintainable Code:** + Write technically accurate and easily understandable Go code with relevant examples. + +- **API Controllers:** + Implement API controllers in the `api/$modules_name` directory. + +- **Database Models:** + Define database table models in the `model/` folder. + +- **Query Simplification:** + Use [Gen](mdc:https:/cosy.uozi.org) to streamline query operations, reducing boilerplate code. + +- **Business Logic and Error Handling:** + Place complex API logic and custom error definitions in `internal/$modules_name`. Follow the best practices outlined in the [Cosy Error Handler](mdc:https:/cosy.uozi.org/error-handler). + +- **Routing:** + Register all application routes in the `router/` directory. + +- **Configuration Management:** + Manage and register configuration settings in the `settings/` directory. + +## 2. CRUD Operations + +- **Standardized Operations:** + Utilize [Cosy](mdc:https:/cosy.uozi.org) to implement Create, Read, Update, and Delete (CRUD) operations consistently across the project. + +## 3. Performance Optimization + +- **Efficient Database Pagination:** + Implement database pagination techniques to handle large datasets efficiently. + +- **Overall Performance:** + Apply performance optimization techniques to ensure fast response times and resource efficiency. + +## 4. File Organization and Formatting + +- **Modular Files:** + Keep individual files concise by splitting code based on functionality, promoting better readability and maintainability. + +- **Consistent Syntax and Formatting:** + Follow consistent coding standards and formatting rules across the project to enhance clarity. + +## 5. Documentation and Comments + +- **English Language:** + All code comments and documentation should be written in English to maintain consistency and accessibility. \ No newline at end of file diff --git a/.cursor/rules/frontend.mdc b/.cursor/rules/frontend.mdc new file mode 100644 index 000000000..43f215ee4 --- /dev/null +++ b/.cursor/rules/frontend.mdc @@ -0,0 +1,47 @@ +--- +description: +globs: app/**/*.tsx,app/**/*.vue,app/**/*.ts,app/**/*.js,app/**/*.json +alwaysApply: false +--- +You are an expert in TypeScript, Node.js, Vite, Vue.js, Vue Router, Pinia, VueUse, Ant Design Vue, and UnoCSS, with a deep understanding of best practices and performance optimization techniques in these technologies. + +Package manager: pnpm +- pnpm typecheck + + Code Style and Structure + - Write concise, maintainable, and technically accurate TypeScript code with relevant examples. + - Use functional and declarative programming patterns; avoid classes. + - Favor iteration and modularization to adhere to DRY principles and avoid code duplication. + - Use descriptive variable names with auxiliary verbs (e.g., isLoading, hasError). + - Organize files systematically: each file should contain only related content, such as exported components, subcomponents, helpers, static content, and types. + - Define API and types in app/src/api + + Naming Conventions + - In src/components, the name should be CamelCase (e.g., app/src/components/ChatGPT/ChatGPT.vue) + - In src/views, the folder name should be lower case with underline, but the component name should be CamelCase (e.g., app/src/views/system/About.vue) + - Favor named exports for functions. + + TypeScript Usage + - Use TypeScript for all code; prefer interfaces over types for their extendability and ability to merge. + + Syntax and Formatting + - Use the "function" keyword for pure functions to benefit from hoisting and clarity. + - Always use the Vue Composition API script setup style. + - Use Vue3.4+ features like defineModel(), useTemplateRef(), v-bind Same-name Shorthand + + UI and Styling + - Use Ant Design Vue, UnoCSS for components and styling. + - Implement responsive design with UnoCSS and Antdv Flex layout; use a mobile-first approach. + + Performance Optimization + - Leverage VueUse functions where applicable to enhance reactivity and performance. + - Wrap asynchronous components in Suspense with a fallback UI. + - Use dynamic loading for non-critical components. + - Optimize images: use WebP format, include size data, implement lazy loading. + - Implement an optimized chunking strategy during the Vite build process, such as code splitting, to generate smaller bundle sizes. + + Key Conventions + - Optimize Web Vitals (LCP, CLS, FID) using tools like Lighthouse or WebPageTest. + + Comments + - Always response in English diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 7aca31303..c41a9d0f7 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,49 +1,38 @@ -FROM mcr.microsoft.com/devcontainers/base:jammy +FROM mcr.microsoft.com/devcontainers/base:noble # Combine installation steps for Nginx and Go to avoid repetitive update/cleanup commands RUN apt-get update && \ - apt-get install -y --no-install-recommends curl gnupg2 ca-certificates lsb-release ubuntu-keyring jq cloc && \ - \ - # Configure the Nginx repository - curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor > /usr/share/keyrings/nginx-archive-keyring.gpg && \ - echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] http://nginx.org/packages/mainline/ubuntu $(lsb_release -cs) nginx" \ - > /etc/apt/sources.list.d/nginx.list && \ - printf "Package: *\nPin: origin nginx.org\nPin: release o=nginx\nPin-Priority: 900\n" \ - > /etc/apt/preferences.d/99nginx && \ - \ - # Update package information and install Nginx - apt-get update && \ - apt-get install -y --no-install-recommends nginx inotify-tools file && \ - \ - # Automatically retrieve the latest stable Go version and install it, - # download the appropriate binary based on system architecture (amd64 or arm64) - GO_VERSION=$(curl -sSL "https://golang.org/dl/?mode=json" | \ - jq -r 'map(select(.stable)) | .[0].version' | sed 's/^go//') && \ - ARCH=$(dpkg --print-architecture) && \ - if [ "$ARCH" = "arm64" ]; then \ - GO_ARCH=linux-arm64; \ - else \ - GO_ARCH=linux-amd64; \ - fi && \ - echo "Installing Go version: ${GO_VERSION} for architecture: ${GO_ARCH}" && \ - curl -sSL "https://golang.org/dl/go${GO_VERSION}.${GO_ARCH}.tar.gz" -o go.tar.gz && \ - rm -rf /usr/local/go && \ - tar -C /usr/local -xzf go.tar.gz && \ - rm go.tar.gz && \ - \ - # Remove jq and clean up to reduce image size - apt-get remove -y jq && \ - apt-get autoremove -y && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* + apt-get install -y --no-install-recommends curl gnupg2 ca-certificates lsb-release ubuntu-keyring jq cloc software-properties-common && \ + \ + # Add PPA repository for nginx-extras + add-apt-repository -y ppa:ondrej/nginx && \ + \ + # Update package information and install Nginx-extras + apt-get update && \ + apt-get install -y --no-install-recommends nginx nginx-extras inotify-tools file && \ + \ + # Automatically retrieve the latest stable Go version and install it, + # download the appropriate binary based on system architecture (amd64 or arm64) + GO_VERSION=$(curl -sSL "https://golang.org/dl/?mode=json" | \ + jq -r 'map(select(.stable)) | .[0].version' | sed 's/^go//') && \ + ARCH=$(dpkg --print-architecture) && \ + if [ "$ARCH" = "arm64" ]; then \ + GO_ARCH=linux-arm64; \ + else \ + GO_ARCH=linux-amd64; \ + fi && \ + echo "Installing Go version: ${GO_VERSION} for architecture: ${GO_ARCH}" && \ + curl -sSL "https://golang.org/dl/go${GO_VERSION}.${GO_ARCH}.tar.gz" -o go.tar.gz && \ + rm -rf /usr/local/go && \ + tar -C /usr/local -xzf go.tar.gz && \ + rm go.tar.gz RUN cp -rp /etc/nginx /etc/nginx.orig # Set PATH to include Go installation and default go install binary location ENV PATH="/usr/local/go/bin:/root/go/bin:${PATH}" -# Install air with go install (requires Go 1.23 or higher) -RUN go install github.com/air-verse/air@latest +ENV NGINX_UI_WORKING_DIR=/var/run/ # set zsh as default shell RUN chsh -s $(which zsh) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f3690edce..96a8284d7 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -11,7 +11,7 @@ "ghcr.io/devcontainers/features/common-utils:2": { "installOhMyZsh": true }, - "ghcr.io/devcontainers/features/node:1.6.1": {} + "ghcr.io/devcontainers/features/node:1.6.3": {} }, // Use 'forwardPorts' to make a list of ports inside the container available locally. @@ -28,8 +28,11 @@ "antfu.unocss", "github.copilot", "golang.go", + "ms-azuretools.vscode-docker", + "akino.i18n-gettext", + "github.vscode-github-actions", "vue.volar", - "ms-azuretools.vscode-docker" + "eamodio.gitlens" ] } }, diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 09e492ea9..612254ffb 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -4,9 +4,12 @@ services: image: nginx-ui-dev container_name: nginx-ui volumes: + - ~/.ssh:/root/.ssh - ../..:/workspaces:cached - - ./go-path:/root/go + - go-modules:/root/go - ./data/nginx:/etc/nginx + - ./data/nginx-logs:/var/log/nginx + - /var/run/docker.sock:/var/run/docker.sock command: sleep infinity environment: - NGINX_UI_CERT_CA_DIR=https://pebble:14000/dir @@ -25,15 +28,38 @@ services: - nginx-ui networks: nginxui: - + nginx-ui-3: + image: nginx-ui-dev + container_name: nginx-ui-3 + environment: + - NGINX_UI_OFFICIAL_DOCKER=true + 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: - ./pebble-test:/test command: -config /test/config/pebble-config.json -strict -dnsserver challtestsrv:8053 ports: - - 14000:14000 # HTTPS ACME API - - 15000:15000 # HTTPS Management API + - 14000:14000 # HTTPS ACME API + - 15000:15000 # HTTPS Management API environment: - PEBBLE_VA_NOSLEEP=1 - PEBBLE_VA_ALWAYS_VALID=1 @@ -56,3 +82,6 @@ services: networks: nginxui: + +volumes: + go-modules: diff --git a/.devcontainer/init-nginx.sh b/.devcontainer/init-nginx.sh index 870807799..65f0aae05 100755 --- a/.devcontainer/init-nginx.sh +++ b/.devcontainer/init-nginx.sh @@ -1,6 +1,56 @@ +#!/bin/bash # init nginx config dir if [ "$(ls -A /etc/nginx)" = "" ]; then echo "Initialing Nginx config dir" cp -rp /etc/nginx.orig/* /etc/nginx/ echo "Initialed Nginx config dir" -fi \ No newline at end of file +fi + + +src_dir="/usr/share/nginx/modules-available" +dest_dir="/etc/nginx/modules-enabled" + +create_symlink() { + local module_name=$1 + local weight=$2 + + local target="$dest_dir/$weight-$module_name" + local source="$src_dir/$module_name" + + ln -sf "$source" "$target" + echo "Created symlink: $target -> $source" +} + +modules=( + "mod-http-ndk.conf 10" + "mod-http-auth-pam.conf 50" + "mod-http-cache-purge.conf 50" + "mod-http-dav-ext.conf 50" + "mod-http-echo.conf 50" + "mod-http-fancyindex.conf 50" + "mod-http-geoip.conf 50" + "mod-http-geoip2.conf 50" + "mod-http-headers-more-filter.conf 50" + "mod-http-image-filter.conf 50" + "mod-http-lua.conf 50" + "mod-http-perl.conf 50" + "mod-http-subs-filter.conf 50" + "mod-http-uploadprogress.conf 50" + "mod-http-upstream-fair.conf 50" + "mod-http-xslt-filter.conf 50" + "mod-mail.conf 50" + "mod-nchan.conf 50" + "mod-stream.conf 50" + "mod-stream-geoip.conf 70" + "mod-stream-geoip2.conf 70" +) + +for module in "${modules[@]}"; do + module_name=$(echo $module | awk '{print $1}') + weight=$(echo $module | awk '{print $2}') + + create_symlink "$module_name" "$weight" +done + +# start nginx +nginx diff --git a/.devcontainer/pebble-test/config/pebble-config.json b/.devcontainer/pebble-test/config/pebble-config.json index be398b8c7..a7603753c 100644 --- a/.devcontainer/pebble-test/config/pebble-config.json +++ b/.devcontainer/pebble-test/config/pebble-config.json @@ -20,7 +20,7 @@ }, "shortlived": { "description": "A short-lived cert profile, without actual enforcement", - "validityPeriod": 518400 + "validityPeriod": 7776000 } } } diff --git a/.devcontainer/start.sh b/.devcontainer/start.sh index 184b6a1ed..520c14fa4 100755 --- a/.devcontainer/start.sh +++ b/.devcontainer/start.sh @@ -1,6 +1,14 @@ #!/bin/bash -# install zsh-autosuggestions +# setup git for claude +git config --global gpg.format ssh +git config --global user.signingkey ~/.ssh/id_ed25519.pub +git config --global commit.gpgsign true + +# install air +go install github.com/air-verse/air@latest + +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/.github/build/build_info.json b/.github/build/build_info.json index 07f13d17f..8befffd83 100644 --- a/.github/build/build_info.json +++ b/.github/build/build_info.json @@ -16,5 +16,10 @@ "darwin": { "amd64": {"arch": "o64", "name": "macos-64"}, "arm64": {"arch": "oa64", "name": "macos-arm64-v8a"} + }, + "windows": { + "386": {"arch": "i686", "name": "windows-32"}, + "amd64": {"arch": "x86_64", "name": "windows-64"}, + "arm64": {"arch": "aarch64", "name": "windows-arm64-v8a"} } } diff --git a/.github/renovate.json b/.github/renovate.json index 801fc2d96..03a48b3a4 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -27,11 +27,7 @@ "schedule": [ "after 2am and before 3am" ] - }, - { - "matchPackageNames": ["vue-tsc"], - "allowedVersions": "!/^2\\.2\\.0$/" } ], - "ignoreDeps": ["vue3-apexcharts"] + "ignoreDeps": ["vue3-apexcharts", "gorm.io/gorm", "gorm.io/plugin/dbresolver"] } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index dd517852d..82936d7e2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -6,6 +6,7 @@ on: - 'weblate' paths: - "app/**/*.js" + - "app/**/*.ts" - "app/**/*.vue" - "app/src/language/**/*.po" - "app/i18n.json" @@ -40,13 +41,13 @@ on: jobs: build_app: - runs-on: macos-14 + runs-on: macos-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up nodejs - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: node-version: current @@ -55,29 +56,44 @@ jobs: corepack enable corepack prepare pnpm@latest --activate pnpm install - working-directory: app - name: Check frontend code style run: | - pnpm run lint - working-directory: app + pnpm lint - name: Check frontend types run: | - pnpm run typecheck - working-directory: app + pnpm typecheck - name: Build run: | - npx browserslist@latest --update-db + npx update-browserslist-db@latest pnpm build - working-directory: app + + - name: Compress frontend with xz + run: | + # Create compressed archive of the entire dist directory + tar -C app -cf - dist | xz -9 -c > app/dist.tar.xz + + # Verify compression worked and show savings (macOS compatible) + ORIGINAL_SIZE=$(find app/dist -type f -exec stat -f%z {} \; | awk '{total += $1} END {print total}') + COMPRESSED_SIZE=$(stat -f%z app/dist.tar.xz) + + if [[ $ORIGINAL_SIZE -gt 0 ]]; then + SAVINGS=$((100 - COMPRESSED_SIZE * 100 / ORIGINAL_SIZE)) + echo "Original size: ${ORIGINAL_SIZE} bytes" + echo "Compressed size: ${COMPRESSED_SIZE} bytes" + echo "Compression savings: ${SAVINGS}%" + else + echo "Compressed size: ${COMPRESSED_SIZE} bytes" + fi - name: Archive app artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: app-dist - path: app/dist + path: | + app/dist.tar.xz - name: Prepare publish if: github.event_name == 'release' @@ -96,12 +112,12 @@ jobs: needs: build_app strategy: matrix: - goos: [ linux, darwin ] + goos: [ linux, windows ] goarch: [ amd64, 386, arm64 ] - exclude: - # Exclude i386 on darwin. - - goarch: 386 - goos: darwin + # exclude: + # Exclude i386 on windows (if needed) + # - goarch: 386 + # goos: windows include: # BEGIN Linux ARM 5 6 7 - goos: linux @@ -135,12 +151,12 @@ jobs: GOARM: ${{ matrix.goarm }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Go - uses: actions/setup-go@v5 + uses: actions/setup-go@v6 with: - go-version: ^1.24.1 + go-version: ^1.25.3 cache: false - name: Setup environment @@ -150,12 +166,14 @@ jobs: export _ARCH=$(jq ".$GOOS[\"$GOARCH$GOARM\"].arch" -r < .github/build/build_info.json) export _ABI=$(jq ".$GOOS[\"$GOARCH$GOARM\"].abi // \"\"" -r < .github/build/build_info.json) export _ARTIFACT=nginx-ui-$GOOS-$GOARCH$(if [[ "$GOARM" ]]; then echo "v$GOARM"; fi) - echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, ABI: $_ABI, RELEASE_NAME: $_NAME, ARTIFACT_NAME: $_ARTIFACT" + export _BINARY=nginx-ui$(if [[ "$GOOS" == "windows" ]]; then echo ".exe"; fi) + echo "GOOS: $GOOS, GOARCH: $GOARCH, GOARM: $GOARM, ABI: $_ABI, RELEASE_NAME: $_NAME, ARTIFACT_NAME: $_ARTIFACT, BINARY_NAME: $_BINARY" echo "CACHE_NAME=$_NAME" >> $GITHUB_ENV echo "ARCH_NAME=$_ARCH" >> $GITHUB_ENV echo "ABI=$_ABI" >> $GITHUB_ENV echo "DIST=nginx-ui-$_NAME" >> $GITHUB_ENV echo "ARTIFACT=$_ARTIFACT" >> $GITHUB_ENV + echo "BINARY_NAME=$_BINARY" >> $GITHUB_ENV - name: Setup Go modules cache uses: actions/cache@v4 @@ -176,16 +194,16 @@ jobs: go-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_NAME }}- - name: Download app artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v6 with: name: app-dist - path: app/dist + path: app/dist.tar.xz - name: Generate files env: GOOS: linux GOARCH: amd64 - run: go generate + run: go generate cmd/version/generate.go - name: Install musl cross compiler if: env.GOOS == 'linux' @@ -203,35 +221,65 @@ jobs: echo "CXX=${{ env.ARCH_NAME }}-linux-musl${{ env.ABI }}-g++" >> $GITHUB_ENV echo "LD_FLAGS=-w --extldflags '-static'" >> $GITHUB_ENV - - name: Install darwin cross compiler - if: env.GOOS == 'darwin' + - name: Setup for Windows + if: env.GOOS == 'windows' run: | - curl -L https://github.com/Hintay/crossosx/releases/latest/download/crossosx.tar.zst -o crossosx.tar.zst - tar xvaf crossosx.tar.zst - echo "LD_LIBRARY_PATH=$(pwd)/crossosx/lib/" >> $GITHUB_ENV - echo "PATH=$(pwd)/crossosx/bin/:$PATH" >> $GITHUB_ENV - echo "CC=${{ env.ARCH_NAME }}-clang" >> $GITHUB_ENV - echo "CXX=${{ env.ARCH_NAME }}-clang++" >> $GITHUB_ENV echo "LD_FLAGS=-w" >> $GITHUB_ENV + echo "CGO_ENABLED=1" >> $GITHUB_ENV + + # Install cross compilers based on architecture + sudo apt-get update + sudo apt-get install -y zip + if [[ "$GOARCH" == "amd64" ]]; then + echo "Installing x86_64 Windows cross compiler" + sudo apt-get install -y gcc-mingw-w64-x86-64 g++-mingw-w64-x86-64 + echo "CC=x86_64-w64-mingw32-gcc" >> $GITHUB_ENV + echo "CXX=x86_64-w64-mingw32-g++" >> $GITHUB_ENV + elif [[ "$GOARCH" == "386" ]]; then + echo "Installing i686 Windows cross compiler" + sudo apt-get install -y gcc-mingw-w64-i686 g++-mingw-w64-i686 + echo "CC=i686-w64-mingw32-gcc" >> $GITHUB_ENV + echo "CXX=i686-w64-mingw32-g++" >> $GITHUB_ENV + elif [[ "$GOARCH" == "arm64" ]]; then + echo "Installing ARM64 Windows cross compiler" + # Ubuntu's apt repositories don't have mingw for ARM64 + # Use llvm-mingw project instead + mkdir -p $HOME/llvm-mingw + wget -q https://github.com/mstorsjo/llvm-mingw/releases/download/20231128/llvm-mingw-20231128-ucrt-ubuntu-20.04-x86_64.tar.xz + tar xf llvm-mingw-20231128-ucrt-ubuntu-20.04-x86_64.tar.xz -C $HOME/llvm-mingw --strip-components=1 + echo "PATH=$HOME/llvm-mingw/bin:$PATH" >> $GITHUB_ENV + echo "CC=aarch64-w64-mingw32-clang" >> $GITHUB_ENV + echo "CXX=aarch64-w64-mingw32-clang++" >> $GITHUB_ENV + else + echo "Unsupported Windows architecture: $GOARCH" + exit 1 + fi - name: Build run: | mkdir -p dist - go build -tags=jsoniter -ldflags "$LD_FLAGS -X 'github.com/0xJacky/Nginx-UI/settings.buildTime=$(date +%s)'" -o dist/nginx-ui -v main.go + go build -trimpath -tags=jsoniter -ldflags "$LD_FLAGS -X 'github.com/0xJacky/Nginx-UI/settings.buildTime=$(date +%s)'" -o dist/$BINARY_NAME -v main.go - name: Archive backend artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: ${{ env.ARTIFACT }} - path: dist/nginx-ui + path: dist/${{ env.BINARY_NAME }} - name: Prepare publish - if: github.event_name == 'release' run: | cp README*.md ./dist find dist -printf '%P\n' | tar -C dist --no-recursion -zcvf ${{ env.DIST }}.tar.gz -T - openssl dgst -sha512 ${{ env.DIST }}.tar.gz | sed 's/([^)]*)//g' | awk '{print $NF}' >> ${{ env.DIST }}.tar.gz.digest + # Create zip for Windows builds (for winget compatibility) + if [[ "$GOOS" == "windows" ]]; then + cd dist + zip -r ../${{ env.DIST }}.zip . + cd .. + openssl dgst -sha512 ${{ env.DIST }}.zip | sed 's/([^)]*)//g' | awk '{print $NF}' >> ${{ env.DIST }}.zip.digest + fi + - name: Publish uses: softprops/action-gh-release@v2 if: github.event_name == 'release' @@ -239,16 +287,125 @@ jobs: files: | ${{ env.DIST }}.tar.gz ${{ env.DIST }}.tar.gz.digest + ${{ env.GOOS == 'windows' && format('{0}.zip', env.DIST) || '' }} + ${{ env.GOOS == 'windows' && format('{0}.zip.digest', env.DIST) || '' }} + + - name: Upload to R2 using S3 API + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + AWS_REGION: us-east-1 + run: | + echo "Uploading ${{ env.DIST }}.tar.gz to R2..." + aws s3 cp ./${{ env.DIST }}.tar.gz s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }} + + echo "Uploading ${{ env.DIST }}.tar.gz.digest to R2..." + aws s3 cp ./${{ env.DIST }}.tar.gz.digest s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz.digest --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }} + + echo "Upload completed successfully" + + build_macos_native: + runs-on: macos-latest + needs: build_app + strategy: + matrix: + goarch: [amd64, arm64] + env: + CGO_ENABLED: 1 + GOOS: darwin + GOARCH: ${{ matrix.goarch }} + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Set up Go + uses: actions/setup-go@v6 + with: + go-version: ^1.25.3 + cache: false + + - name: Setup environment + id: info + run: | + export _NAME=$(jq ".darwin[\"$GOARCH\"].name" -r < .github/build/build_info.json) + export _ARTIFACT=nginx-ui-darwin-$GOARCH + export _BINARY=nginx-ui + echo "GOOS: darwin, GOARCH: $GOARCH, RELEASE_NAME: $_NAME, ARTIFACT_NAME: $_ARTIFACT, BINARY_NAME: $_BINARY" + echo "CACHE_NAME=$_NAME" >> $GITHUB_ENV + echo "DIST=nginx-ui-$_NAME" >> $GITHUB_ENV + echo "ARTIFACT=$_ARTIFACT" >> $GITHUB_ENV + echo "BINARY_NAME=$_BINARY" >> $GITHUB_ENV + + - name: Setup Go build cache + uses: actions/cache@v4 + with: + path: | + ~/go/pkg/mod + ~/.cache/go-build + key: go-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_NAME }}-${{ hashFiles('go.mod') }} + restore-keys: | + go-${{ runner.os }}-${{ runner.arch }}-${{ env.CACHE_NAME }}- + + - name: Download app artifacts + uses: actions/download-artifact@v6 + with: + name: app-dist + path: app/dist.tar.xz + + - name: Generate files + run: go generate cmd/version/generate.go + + - name: Build with native CGO + run: | + mkdir -p dist + go build -trimpath -tags=jsoniter -ldflags "-w -X 'github.com/0xJacky/Nginx-UI/settings.buildTime=$(date +%s)'" -o dist/$BINARY_NAME -v main.go + + - name: Archive backend artifacts + uses: actions/upload-artifact@v5 + with: + name: ${{ env.ARTIFACT }} + path: dist/${{ env.BINARY_NAME }} + + - name: Prepare publish + run: | + cp README*.md ./dist + cd dist && tar -zcvf ../${{ env.DIST }}.tar.gz . + cd .. + openssl dgst -sha512 ${{ env.DIST }}.tar.gz | sed 's/([^)]*)//g' | awk '{print $NF}' >> ${{ env.DIST }}.tar.gz.digest + + - name: Publish + uses: softprops/action-gh-release@v2 + if: github.event_name == 'release' + with: + files: | + ${{ env.DIST }}.tar.gz + ${{ env.DIST }}.tar.gz.digest + + - name: Upload to R2 using S3 API + if: github.event_name != 'pull_request' && github.ref == 'refs/heads/dev' + env: + AWS_ACCESS_KEY_ID: ${{ secrets.R2_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }} + AWS_REGION: us-east-1 + run: | + echo "Uploading ${{ env.DIST }}.tar.gz to R2..." + aws s3 cp ./${{ env.DIST }}.tar.gz s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }} + + echo "Uploading ${{ env.DIST }}.tar.gz.digest to R2..." + aws s3 cp ./${{ env.DIST }}.tar.gz.digest s3://nginx-ui-dev-build/${{ env.DIST }}.tar.gz.digest --endpoint-url=${{ secrets.R2_S3_API_ENDPOINT }} + + echo "Upload completed successfully" docker-build: if: github.event_name != 'pull_request' runs-on: ubuntu-latest - needs: build + needs: [build, build_macos_native] env: PLATFORMS: linux/amd64,linux/arm64,linux/arm/v7,linux/arm/v6,linux/arm/v5 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Docker meta id: meta @@ -260,16 +417,17 @@ jobs: type=schedule type=ref,event=branch type=semver,pattern={{version}} + type=semver,pattern={{raw}} type=sha type=raw,value=latest,enable=${{ github.event_name == 'release' && !github.event.release.prerelease }} - name: Download artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v6 with: path: ./dist - name: Prepare Artifacts - run: chmod +x ./dist/nginx-ui-*/nginx-ui + run: chmod +x ./dist/nginx-ui-*/nginx-ui* - name: Set up Docker Buildx id: buildx @@ -311,3 +469,199 @@ jobs: push: 'true' tags: | uozi/nginx-ui-demo:latest + + update-homebrew: + runs-on: ubuntu-latest + needs: [build, build_macos_native] + if: github.event_name == 'release' + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Get release info + id: release + run: | + echo "tag_name=${{ github.event.release.tag_name }}" >> $GITHUB_OUTPUT + echo "version=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT + + - name: Download release assets and calculate SHA256 checksums + id: checksums + run: | + VERSION="${{ steps.release.outputs.version }}" + TAG_NAME="${{ steps.release.outputs.tag_name }}" + + # Download binary files from releases and calculate SHA256 + mkdir -p downloads + + # macOS Intel + wget -O downloads/nginx-ui-macos-64.tar.gz "https://github.com/${{ github.repository }}/releases/download/$TAG_NAME/nginx-ui-macos-64.tar.gz" + MACOS_INTEL_SHA256=$(sha256sum downloads/nginx-ui-macos-64.tar.gz | cut -d' ' -f1) + + # macOS ARM + wget -O downloads/nginx-ui-macos-arm64-v8a.tar.gz "https://github.com/${{ github.repository }}/releases/download/$TAG_NAME/nginx-ui-macos-arm64-v8a.tar.gz" + MACOS_ARM_SHA256=$(sha256sum downloads/nginx-ui-macos-arm64-v8a.tar.gz | cut -d' ' -f1) + + # Linux Intel + wget -O downloads/nginx-ui-linux-64.tar.gz "https://github.com/${{ github.repository }}/releases/download/$TAG_NAME/nginx-ui-linux-64.tar.gz" + LINUX_INTEL_SHA256=$(sha256sum downloads/nginx-ui-linux-64.tar.gz | cut -d' ' -f1) + + # Linux ARM + wget -O downloads/nginx-ui-linux-arm64-v8a.tar.gz "https://github.com/${{ github.repository }}/releases/download/$TAG_NAME/nginx-ui-linux-arm64-v8a.tar.gz" + LINUX_ARM_SHA256=$(sha256sum downloads/nginx-ui-linux-arm64-v8a.tar.gz | cut -d' ' -f1) + + echo "macos_intel_sha256=$MACOS_INTEL_SHA256" >> $GITHUB_OUTPUT + echo "macos_arm_sha256=$MACOS_ARM_SHA256" >> $GITHUB_OUTPUT + echo "linux_intel_sha256=$LINUX_INTEL_SHA256" >> $GITHUB_OUTPUT + echo "linux_arm_sha256=$LINUX_ARM_SHA256" >> $GITHUB_OUTPUT + + - name: Generate Homebrew Formula + id: formula + run: | + VERSION="${{ steps.release.outputs.version }}" + + cat > nginx-ui.rb << 'EOF' + class NginxUi < Formula + desc "Yet another Nginx Web UI" + homepage "https://github.com/0xJacky/nginx-ui" + version "${{ steps.release.outputs.version }}" + license "AGPL-3.0" + + on_macos do + on_intel do + url "https://github.com/0xJacky/nginx-ui/releases/download/v#{version}/nginx-ui-macos-64.tar.gz" + sha256 "${{ steps.checksums.outputs.macos_intel_sha256 }}" + end + on_arm do + url "https://github.com/0xJacky/nginx-ui/releases/download/v#{version}/nginx-ui-macos-arm64-v8a.tar.gz" + sha256 "${{ steps.checksums.outputs.macos_arm_sha256 }}" + end + end + + on_linux do + on_intel do + url "https://github.com/0xJacky/nginx-ui/releases/download/v#{version}/nginx-ui-linux-64.tar.gz" + sha256 "${{ steps.checksums.outputs.linux_intel_sha256 }}" + end + on_arm do + url "https://github.com/0xJacky/nginx-ui/releases/download/v#{version}/nginx-ui-linux-arm64-v8a.tar.gz" + sha256 "${{ steps.checksums.outputs.linux_arm_sha256 }}" + end + end + + def install + bin.install "nginx-ui" + + # Create configuration directory + (etc/"nginx-ui").mkpath + + # Create default configuration file if it doesn't exist + config_file = etc/"nginx-ui/app.ini" + unless config_file.exist? + config_file.write <<~EOS + [app] + PageSize = 10 + + [server] + Host = 0.0.0.0 + Port = 9000 + RunMode = release + + [cert] + HTTPChallengePort = 9180 + + [terminal] + StartCmd = login + EOS + end + + # Create data directory + (var/"nginx-ui").mkpath + end + + def post_install + # Ensure correct permissions + (var/"nginx-ui").chmod 0755 + end + + service do + run [opt_bin/"nginx-ui", "serve", "--config", etc/"nginx-ui/app.ini"] + keep_alive true + working_dir var/"nginx-ui" + log_path var/"log/nginx-ui.log" + error_log_path var/"log/nginx-ui.err.log" + end + + test do + assert_match version.to_s, shell_output("#{bin}/nginx-ui --version") + end + end + EOF + + echo "Generated Homebrew Formula:" + cat nginx-ui.rb + + - name: Checkout homebrew-tools repository + uses: actions/checkout@v5 + with: + repository: 0xJacky/homebrew-tools + path: homebrew-tools + token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }} + + - name: Update Formula file + run: | + # Copy the generated formula to the correct location + mkdir -p homebrew-tools/Formula/ + cp nginx-ui.rb homebrew-tools/Formula/nginx-ui.rb + + - name: Verify Formula + run: | + cd homebrew-tools + # Basic syntax check + ruby -c Formula/nginx-ui.rb + echo "Formula syntax is valid" + + - name: Create Pull Request to homebrew-tools + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }} + path: homebrew-tools + branch: update-nginx-ui-${{ steps.release.outputs.version }} + delete-branch: true + title: 'nginx-ui ${{ steps.release.outputs.version }}' + body: | + Update nginx-ui to version ${{ steps.release.outputs.version }} + + **Release Notes:** + - Version: ${{ steps.release.outputs.version }} + - Release URL: https://github.com/${{ github.repository }}/releases/tag/${{ steps.release.outputs.tag_name }} + + **Checksums (SHA256):** + - macOS Intel: ${{ steps.checksums.outputs.macos_intel_sha256 }} + - macOS ARM: ${{ steps.checksums.outputs.macos_arm_sha256 }} + - Linux Intel: ${{ steps.checksums.outputs.linux_intel_sha256 }} + - Linux ARM: ${{ steps.checksums.outputs.linux_arm_sha256 }} + + --- + + This PR was automatically generated by GitHub Actions. + commit-message: 'nginx-ui ${{ steps.release.outputs.version }}' + committer: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> + add-paths: | + Formula/nginx-ui.rb + + publish-winget: + runs-on: windows-latest + needs: [build, build_macos_native] + if: github.event_name == 'release' + steps: + - name: Checkout + uses: actions/checkout@v5 + + - name: Publish to WinGet + uses: vedantmgoyal9/winget-releaser@v2 + with: + identifier: 0xJacky.nginx-ui + max-versions-to-keep: 5 + token: ${{ secrets.HOMEBREW_GITHUB_TOKEN }} + installers-regex: 'nginx-ui-windows.*\.zip$' diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0f6069a4f..057d9df5d 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -62,11 +62,11 @@ jobs: # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} build-mode: ${{ matrix.build-mode }} @@ -94,6 +94,6 @@ jobs: exit 1 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/documents.yml b/.github/workflows/documents.yml index 5ba557ee2..3be9f32ca 100644 --- a/.github/workflows/documents.yml +++ b/.github/workflows/documents.yml @@ -1,6 +1,7 @@ name: Build Documents on: + workflow_dispatch: push: branches: - '*' @@ -22,18 +23,24 @@ on: - "docs/.env*" - "docs/**/*.md" - ".github/workflows/doc*.yml" + release: + types: [published] + workflow_run: + workflows: ["Sync branch"] + types: + - completed jobs: build: - runs-on: macos-14 + runs-on: macos-15 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up nodejs - uses: actions/setup-node@v4 + uses: actions/setup-node@v6 with: - node-version: 21.x + node-version: 23.x - name: Install dependencies run: | @@ -47,13 +54,13 @@ jobs: working-directory: docs - name: Archive artifacts - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: dist path: docs/.vitepress/dist - - name: Deploy to server - if: github.event_name != 'pull_request' + - name: Deploy + if: github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.event_name == 'release' || (github.event_name == 'workflow_run' && github.event.workflow_run.conclusion == 'success')) uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CF_API_TOKEN }} diff --git a/.github/workflows/sync-main-on-release.yml b/.github/workflows/sync-main-on-release.yml index 9da99d53b..7f3b73fe8 100644 --- a/.github/workflows/sync-main-on-release.yml +++ b/.github/workflows/sync-main-on-release.yml @@ -11,11 +11,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 ref: dev clean: false + token: ${{ secrets.PAT_TOKEN }} - name: Configure Git run: | diff --git a/.github/workflows/weblate-pull.yml b/.github/workflows/weblate-pull.yml index 3d3d66fb0..b3144dc17 100644 --- a/.github/workflows/weblate-pull.yml +++ b/.github/workflows/weblate-pull.yml @@ -16,12 +16,12 @@ jobs: if: github.event.pull_request.merged == true || github.event.action == 'published' || github.event_name == 'workflow_dispatch' steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: '3.13.2' + python-version: '3.14.0' - name: Install wlc run: pip install wlc @@ -29,4 +29,4 @@ jobs: - name: Update Repository env: WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} - run: wlc --key $WEBLATE_TOKEN pull + run: wlc --key $WEBLATE_TOKEN reset nginx-ui diff --git a/.github/workflows/weblate-sync.yml b/.github/workflows/weblate-sync.yml index 0cd094aad..5072e298b 100644 --- a/.github/workflows/weblate-sync.yml +++ b/.github/workflows/weblate-sync.yml @@ -18,7 +18,7 @@ jobs: ahead: ${{ steps.check.outputs.ahead }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ env.DEVELOP_BRANCH }} clean: false @@ -46,12 +46,12 @@ jobs: WEBLATE_TOKEN: ${{ secrets.WEBLATE_TOKEN }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Setup python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: - python-version: '3.13.2' + python-version: '3.14.0' - name: Install wlc run: pip install wlc @@ -69,7 +69,7 @@ jobs: if: ${{ needs.check.outputs.ahead > 0 || needs.check.outputs.behind > 0 && always() }} steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: ${{ env.DEVELOP_BRANCH }} fetch-depth: 0 diff --git a/.gitignore b/.gitignore index 0c38fd5ef..378edcfac 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ node_modules app.ini app.*.ini dist +dist.tar.xz *.exe *.po~ nginx-ui @@ -18,3 +19,10 @@ internal/**/*.gen.go .devcontainer/go-path .devcontainer/data .devcontainer/casdoor.pem +.vscode/.i18n-gettext.secret +.go/ +/.cunzhi-memory +log-index/ +*.prof +*.test +GeoLite2-City.mmdb diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..1040baec7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,38 @@ +{ + "i18n-gettext.localesConfig": { + "root": "app", + "type": "nested", + "basePath": "src/language", + "pattern": "${locale}/${domain}.po", + "defaultDomain": "app", + "sourceLanguage": "en" + }, + "i18n-gettext.translatorConfig": { + "onlyTranslateUntranslatedAndFuzzy": true, + "batch": { + "pageSize": 100 + } + }, + "search.exclude": { + "**/node_modules": true, + "**/bower_components": true, + "**/*.code-search": true, + "**/vendor": true, + "**/dist": true, + "**/build": true, + "**/out": true, + "**/tmp": true, + "**/.git": true, + "**/.DS_Store": true, + "**/database.db": true, + "**/.pnpm-store": true, + "**/nginx-ui": true + }, + "files.watcherExclude": { + "**/.git/objects/**": true, + "**/.git/subtree-cache/**": true, + "**/node_modules/*/**": true, + "**/tmp/**": true, + "**/.pnpm-store/**": true + } +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json index d44e62864..67cf98641 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -2,7 +2,7 @@ "version": "2.0.0", "tasks": [ { - "label": "Start Backend", + "label": "[Go] Start Backend", "type": "shell", "command": "air", "isBackground": true, @@ -12,18 +12,19 @@ "problemMatcher": [] }, { - "label": "Start Frontend", + "label": "[App] Start Frontend", "type": "shell", - "command": "cd app && pnpm dev", + "command": "pnpm dev", "isBackground": true, "presentation": { "panel": "new" - } + }, + "problemMatcher": [] }, { - "label": "Start Documentation", + "label": "[Docs] Start Documentation", "type": "shell", - "command": "cd docs && pnpm docs:dev", + "command": "pnpm docs:dev", "isBackground": true, "presentation": { "panel": "new" @@ -31,12 +32,12 @@ "problemMatcher": [] }, { - "label": "Start All Services", + "label": "[All] Start All Services", "dependsOrder": "parallel", "dependsOn": [ - "Start Backend", - "Start Frontend", - "Start Documentation" + "[Go] Start Backend", + "[App] Start Frontend", + "[Docs] Start Documentation" ], "group": { "kind": "build", @@ -47,7 +48,7 @@ { "label": "[App] Gettext Extract", "type": "shell", - "command": "cd app && pnpm gettext:extract", + "command": "pnpm gettext:extract", "isBackground": true, "presentation": { "panel": "new" @@ -57,7 +58,7 @@ { "label": "[App] ESLint Fix", "type": "shell", - "command": "cd app && pnpm lint --fix", + "command": "pnpm lint --fix", "presentation": { "panel": "new" }, @@ -66,7 +67,7 @@ { "label": "[App] Typecheck", "type": "shell", - "command": "cd app && pnpm typecheck", + "command": "pnpm typecheck", "presentation": { "panel": "new" }, @@ -75,23 +76,23 @@ { "label": "[App] Build", "type": "shell", - "command": "cd app && pnpm build", + "command": "pnpm build", "presentation": { "panel": "new" }, "problemMatcher": [] }, { - "label": "Go Generate", + "label": "[Go] Generate", "type": "shell", - "command": "./gen.sh", + "command": "go generate", "presentation": { "panel": "new" }, "problemMatcher": [] }, { - "label": "Bump Version", + "label": "[All] Bump Version", "type": "shell", "command": "./version.sh", "presentation": { diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 000000000..d3fe818e8 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,82 @@ +# NGINX UI - Claude Code Guidelines + +This project is a web-based NGINX management interface built with Go backend and Vue.js frontend. + +## Package Manager +- **Use pnpm exclusively** for all frontend package management operations +- Commands: `pnpm install`, `pnpm run dev`, `pnpm typecheck` + +## Backend (Go) Development + +### Technologies & Frameworks +- **Go** with Gin web framework +- **GORM** for database operations +- **Gen** for query simplification +- **Cosy** framework (https://cosy.uozi.org/) + +### Code Organization +- **API Controllers**: Implement in `api/$module_name/` directory +- **Database Models**: Define in `model/` folder +- **Business Logic**: Place complex logic and error handling in `internal/$module_name/` +- **Routing**: Register routes in `router/` directory +- **Configuration**: Manage settings in `settings/` directory + +### Development Guidelines +- Write concise, maintainable Go code with clear examples +- Use Gen to streamline database queries and reduce boilerplate +- Follow Cosy Error Handler best practices for error management +- Implement standardized CRUD operations using Cosy framework +- Apply efficient database pagination for large datasets +- Keep files modular and well-organized by functionality +- **All comments and documentation must be in English** + +## Frontend (Vue.js) Development + +### Technology Stack +- **TypeScript** for all code +- **Vue 3** with Composition API +- **Vite** build tool +- **Vue Router** for routing +- **Pinia** for state management +- **VueUse** for utilities +- **Ant Design Vue** for UI components +- **UnoCSS** for styling + +### Code Standards +- Use functional and declarative programming patterns (avoid classes) +- Prefer interfaces over types for better extendability +- Use descriptive variable names with auxiliary verbs (e.g., `isLoading`, `hasError`) +- Always use Vue Composition API with ` +
+