├── .github ├── FUNDING.yml ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .husky ├── .gitignore └── pre-commit ├── .lintstagedrc ├── .npmignore ├── .prettierignore ├── .prettierrc ├── .vercel └── ignore-gh-pages.sh ├── LICENSE ├── README.md ├── core ├── README.md ├── package.json ├── src │ ├── conf.ts │ ├── directives.ts │ ├── index.tsx │ └── suggestions.ts └── tsconfig.json ├── lerna.json ├── package.json ├── renovate.json ├── sandbox.config.json ├── tsconfig.json └── website ├── .kktrc.ts ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.css ├── App.tsx ├── Header.module.less ├── Header.tsx ├── LoadFile.module.less ├── LoadFile.tsx ├── github.svg ├── index.tsx ├── logo.svg ├── nginx.conf.ts ├── nginx.svg └── react-app-env.d.ts └── tsconfig.json /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | ko_fi: jaywcjlove 2 | buy_me_a_coffee: jaywcjlove 3 | custom: ["https://www.paypal.me/kennyiseeyou", "https://jaywcjlove.github.io/#/sponsor"] 4 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: / 5 | schedule: 6 | interval: daily 7 | 8 | # https://docs.github.com/en/free-pro-team@latest/github/administering-a-repository/configuration-options-for-dependency-updates#about-the-dependabotyml-file 9 | # Enable version updates for npm 10 | - package-ecosystem: 'npm' 11 | # Look for `package.json` and `lock` files in the `root` directory 12 | directory: '/' 13 | # Check the npm registry for updates every day (weekdays) 14 | schedule: 15 | interval: 'daily' -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | 7 | jobs: 8 | build-deploy: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: write 12 | id-token: write 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: actions/setup-node@v4 16 | with: 17 | node-version: 20 18 | registry-url: 'https://registry.npmjs.org' 19 | 20 | - name: Setup Yarn 21 | uses: threeal/setup-yarn-action@v2.0.0 22 | with: 23 | cache: false 24 | version: 1.22.21 25 | 26 | - run: yarn install 27 | - run: npm run build 28 | - run: npm run doc 29 | 30 | - name: Generate Contributors Images 31 | uses: jaywcjlove/github-action-contributors@main 32 | with: 33 | filter-author: (renovate\[bot\]|renovate-bot|dependabot\[bot\]) 34 | output: website/build/CONTRIBUTORS.svg 35 | avatarSize: 42 36 | 37 | - name: Create Tag 38 | id: create_tag 39 | uses: jaywcjlove/create-tag-action@main 40 | with: 41 | token: ${{ secrets.GITHUB_TOKEN }} 42 | package-path: ./core/package.json 43 | 44 | - name: get tag version 45 | id: tag_version 46 | uses: jaywcjlove/changelog-generator@main 47 | 48 | - name: Deploy 49 | uses: peaceiris/actions-gh-pages@v4 50 | with: 51 | commit_message: ${{steps.tag_version.outputs.tag}} ${{ github.event.head_commit.message }} 52 | github_token: ${{ secrets.GITHUB_TOKEN }} 53 | publish_dir: ./website/build 54 | 55 | - name: Generate Changelog 56 | id: changelog 57 | uses: jaywcjlove/changelog-generator@main 58 | with: 59 | head-ref: ${{steps.create_tag.outputs.version}} 60 | filter: '[R|r]elease[d]\s+[v|V]\d(\.\d+){0,2}' 61 | 62 | - name: Create Release 63 | uses: jaywcjlove/create-tag-action@main 64 | if: steps.create_tag.outputs.successful 65 | with: 66 | package-path: ./package.json 67 | version: ${{steps.create_tag.outputs.version}} 68 | release: true 69 | prerelease: false 70 | draft: false 71 | body: | 72 | [![Buy me a coffee](https://img.shields.io/badge/Buy%20me%20a%20coffee-048754?logo=buymeacoffee)](https://jaywcjlove.github.io/#/sponsor) [![](https://img.shields.io/badge/Open%20in-unpkg-blue)](https://uiwjs.github.io/npm-unpkg/#/pkg/monaco-editor-nginx@${{steps.create_tag.outputs.versionNumber}}/file/README.md) 73 | 74 | Documentation ${{ steps.changelog.outputs.tag }}: https://raw.githack.com/jaywcjlove/nginx-editor/${{ steps.changelog.outputs.gh-pages-short-hash }}/index.html 75 | Comparing Changes: ${{ steps.changelog.outputs.compareurl }} 76 | 77 | ```bash 78 | npm i monaco-editor-nginx@${{steps.create_tag.outputs.versionNumber}} 79 | ``` 80 | 81 | ${{ steps.changelog.outputs.changelog }} 82 | 83 | - run: npm publish --access public --provenance 84 | name: 📦 monaco-editor-nginx publish to NPM 85 | continue-on-error: true 86 | working-directory: core 87 | env: 88 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 89 | 90 | # - run: npm run get:nginx 91 | 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | cjs 2 | esm 3 | build 4 | node_modules 5 | npm-debug.log* 6 | package-lock.json 7 | yarn.lock 8 | 9 | .eslintcache 10 | .DS_Store 11 | .cache 12 | .vscode 13 | 14 | *.bak 15 | *.tem 16 | *.temp 17 | #.swp 18 | *.*~ 19 | ~*.* 20 | -------------------------------------------------------------------------------- /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | ./node_modules/.bin/lint-staged --config .lintstagedrc -------------------------------------------------------------------------------- /.lintstagedrc: -------------------------------------------------------------------------------- 1 | { 2 | "*.{js,jsx,tsx,ts,less,md,json}": [ 3 | "pretty-quick --staged" 4 | ] 5 | } -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | tsconfig.json 3 | react-app-env.d.ts -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | **/*.md 2 | **/*.svg 3 | **/*.ejs 4 | **/*.yml 5 | package.json 6 | node_modules 7 | dist 8 | build 9 | lib 10 | test 11 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "printWidth": 120, 5 | "overrides": [ 6 | { 7 | "files": ".prettierrc", 8 | "options": { "parser": "json" } 9 | }, 10 | { 11 | "files": "*.{js,jsx}", 12 | "options": { "parser": "babel" } 13 | }, 14 | { 15 | "files": "*.{ts,tsx}", 16 | "options": { "parser": "babel-ts" } 17 | }, 18 | { 19 | "files": "*.{ts,tsx}", 20 | "options": { "parser": "typescript" } 21 | }, 22 | { 23 | "files": "*.{less,css}", 24 | "options": { "parser": "css" } 25 | } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /.vercel/ignore-gh-pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Don't try to build the gh-pages branch. 4 | 5 | if [[ "$BRANCH" != "gh-pages" ]] ; then 6 | # Proceed with the build 7 | exit 1; 8 | else 9 | # Don't build 10 | exit 0; 11 | fi -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 小弟调调™ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | core/README.md -------------------------------------------------------------------------------- /core/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | nginx-editor 4 | 5 |

6 | 7 |

8 | 9 | Buy me a coffee 10 | 11 | 12 | Build & Deploy 13 | 14 | 15 | Issues 16 | 17 | 18 | Forks 19 | 20 | 21 | Stars 22 | 23 | 24 | Open in unpkg 25 | 26 | 27 | npm version 28 | 29 |

30 | 31 | Nginx language plugin for the [Monaco Editor](https://github.com/microsoft/monaco-editor). It provides the following features when editing [Nginx](https://nginx.org/) config files: 32 | 33 | - Syntax highlighting 34 | 35 | ## Quick Start 36 | 37 | ```bash 38 | npm install monaco-editor-nginx 39 | ``` 40 | 41 | [![Open in CodeSandbox](https://img.shields.io/badge/Open%20in-CodeSandbox-blue?logo=codesandbox)](https://codesandbox.io/s/github/jaywcjlove/nginx-editor) 42 | [![Open in Github gh-pages](https://img.shields.io/badge/Open%20In-Github%20gh--pages-blue?logo=github)](https://jaywcjlove.github.io/nginx-editor/) 43 | 44 | ```jsx 45 | import MonacoEditor from '@uiw/react-monacoeditor'; 46 | import 'monaco-editor-nginx'; 47 | 48 | { 52 | setContentDownload(value); 53 | }} 54 | language="nginx" 55 | value={content} 56 | height="calc(100vh - 36px)" 57 | /> 58 | ``` 59 | 60 | or, Integrating the ESM version of the Monaco Editor 61 | 62 | ```js 63 | import * as monaco from 'monaco-editor'; 64 | import 'monaco-editor-nginx'; 65 | 66 | monaco.editor.create(document.getElementById("container"), { 67 | theme: 'nginx-theme', 68 | value: 'nginx code.....', 69 | language: 'nginx' 70 | }); 71 | ``` 72 | 73 | ## Development 74 | 75 | > [!WARNING] 76 | > 77 | > Using `npm` to install now cannot preview the example effect, but using `yarn@1` works. Currently, we are not sure what the reason is. If anyone knows, please let me know. Thanks a lot 🙏 78 | 79 | ```bash 80 | yarn install # ⚠️ 81 | ``` 82 | 83 | Runs the project in development mode. 84 | 85 | ```bash 86 | # Step 1, run first, listen to the component compile and output the .js file 87 | # listen for compilation output type .d.ts file 88 | npm run watch 89 | # Step 2, development mode, listen to compile preview website instance 90 | npm run start 91 | ``` 92 | 93 | Builds the app for production to the build folder. 94 | 95 | ```bash 96 | npm run build 97 | ``` 98 | 99 | The build is minified and the filenames include the hashes. 100 | Your app is ready to be deployed! 101 | 102 | 103 | ### Related 104 | 105 | - [@uiw/react-monacoeditor](https://github.com/jaywcjlove/react-monacoeditor): Monaco Editor component for React. 106 | - [@uiw/react-codemirror](https://github.com/uiwjs/react-codemirror): CodeMirror component for React. @codemirror 107 | - [@uiw/react-markdown-editor](https://github.com/uiwjs/react-markdown-editor): A markdown editor with preview, implemented with React.js and TypeScript. 108 | - [@uiw/react-md-editor](https://github.com/uiwjs/react-md-editor): A simple markdown editor with preview, implemented with React.js and TypeScript. 109 | - [@uiw/react-markdown-preview](https://github.com/jaywcjlove/react-monacoeditor): React component preview markdown text in web browser. 110 | 111 | ## Contributors 112 | 113 | As always, thanks to our amazing contributors! 114 | 115 | 116 | 117 | 118 | 119 | Made with [github-action-contributors](https://github.com/jaywcjlove/github-action-contributors). 120 | ## License 121 | 122 | Licensed under the MIT License. 123 | -------------------------------------------------------------------------------- /core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "monaco-editor-nginx", 3 | "version": "2.0.2", 4 | "description": "Nginx language for Monaco Editor.", 5 | "main": "cjs/index.js", 6 | "module": "esm/index.js", 7 | "homepage": "https://jaywcjlove.github.io/nginx-editor", 8 | "funding": "https://jaywcjlove.github.io/#/sponsor", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/jaywcjlove/nginx-editor.git" 12 | }, 13 | "author": "", 14 | "license": "MIT", 15 | "peerDependencies": { 16 | "@babel/runtime": ">=7.10.0", 17 | "@nginx/reference-lib": ">=1.1.0", 18 | "monaco-editor": ">=0.22.3", 19 | "react": ">=16.9.0", 20 | "react-dom": ">=16.9.0" 21 | }, 22 | "dependencies": { 23 | "@babel/runtime": "^7.18.6", 24 | "@nginx/reference-lib": "^1.1.0" 25 | }, 26 | "devDependencies": { 27 | "monaco-editor": "^0.44.0" 28 | }, 29 | "files": [ 30 | "cjs", 31 | "esm", 32 | "src" 33 | ], 34 | "keywords": [ 35 | "monaco-editor", 36 | "monaco-nginx", 37 | "nginx", 38 | "monaco", 39 | "editor", 40 | "editor-nginx", 41 | "monaco-editor-nginx" 42 | ] 43 | } 44 | -------------------------------------------------------------------------------- /core/src/conf.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor'; 2 | 3 | export const tokenConf: monaco.languages.IMonarchLanguage = { 4 | defaultToken: 'source', 5 | ignoreCase: true, 6 | brackets: [{ open: '{', close: '}', token: 'delimiter.bracket' }], 7 | 8 | tokenizer: { 9 | root: [ 10 | [/(")/, 'delimiter.bracket'], 11 | // [/[{}()[\]]/, "@brackets"], 12 | [/[;,.]/, 'delimiter'], 13 | [/\\.* |~|~\*|!~|!~\*/, 'string.regexp'], 14 | [/\b\d+\w+\b/, 'number'], 15 | [/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}(:\d{1,5})?\b/, 'number'], 16 | [/\b(ip_hash|upstream|server)\b/, 'http.upstream'], 17 | [/\b(add_header|expires|server_tokens)\b/, 'http.headers'], 18 | [/\b(map|map_hash_max_size|map_hash_bucket_size)\b/, 'module.http'], 19 | [/\b(http)\b/, 'module.http'], 20 | [ 21 | /\b(gzip|gzip_buffers|gzip_comp_level|gzip_disable|gzip_http.version|gzip_min_length|gzip_proxied|gzip_types|gzip_vary)\b/, 22 | 'module.http', 23 | ], 24 | [/\s(on|off)\b/, 'module.main'], 25 | [/\b(access_log|log_format)\s/, 'module.log'], 26 | [ 27 | /\b(proxy_buffer_size|proxy_buffering|proxy_buffers|proxy_busy_buffers_size|proxy_cache|proxy_cache_background_update|proxy_cache_bypass|proxy_cache_convert_head|proxy_cache_key|proxy_cache_lock|proxy_cache_lock_age|proxy_cache_lock_timeout|proxy_cache_max_range_offset|proxy_cache_methods|proxy_cache_min_uses|proxy_cache_path|proxy_cache_purge|proxy_cache_revalidate|proxy_cache_use_stale|proxy_cache_valid|proxy_connect_timeout|proxy_headers_hash_bucket_size|proxy_headers_hash_max_size|proxy_hide_header|proxy_http_version|proxy_ignore_client_abort|proxy_intercept_errors|proxy_max_temp_file_size|proxy_method|proxy_next_upstream|proxy_next_upstream_tries|proxy_next_upstream_timeout|proxy_pass|proxy_pass_header|proxy_pass_request_body|proxy_pass_request_headers|proxy_read_timeout|proxy_redirect|proxy_redirect_errors|proxy_send_lowat|proxy_send_timeout|proxy_set_body|proxy_set_header|proxy_store|proxy_store_access|proxy_temp_file_write_size|proxy_t|emp_pathproxy_upstream_fail_timeout|proxy_upstream_max_fails)\b/, 28 | 'http.proxy', 29 | ], 30 | [ 31 | /\b(ssl|ssl_buffer_size|ssl_certificate|ssl_certificate_key|ssl_ciphers|ssl_client_certificate|ssl_crl|ssl_dhparam|ssl_ecdh_curve|ssl_password_file|ssl_prefer_server_ciphers|ssl_protocols|ssl_session_cache|ssl_session_ticket_key|ssl_session_tickets|ssl_session_timeout|ssl_stapling|ssl_stapling_file|ssl_stapling_responder|ssl_stapling_verify|ssl_trusted_certificate|ssl_verify_client|ssl_verify_depth)\b/, 32 | 'module.http', 33 | ], 34 | [ 35 | /\b(daemon|env|debug_points|error_log|log_not_found|include|lock_file|master_process|pid|ssl_engine|timer_resolution|types_hash_max_size|user|worker_cpu_affinity|worker_priority|worker_processes|worker_rlimit_core|worker_rlimit_nofile|worker_rlimit_sigpending|working_directory|try_files)\b/, 36 | 'module.main', 37 | ], 38 | [ 39 | /\b(index|alias|chunked_transfer_encoding|client_body_in_file_only|client_body_buffer_size|client_body_temp_path|client_body_timeout|client_header_buffer_size|client_header_timeout|client_max_body_size|default_type|error_page|index |internal|keepalive_timeout|keepalive_requests|large_client_header_buffers|limit_except|limit_rate|listen|location|msie_padding|msie_refresh|optimize_server_names|port_in_redirect|recursive_error_pages|reset_timedout_connection|resolver|resolver_timeout|root|satisfy_any|send_timeout|sendfile|server|server_name|server_names_hash_max_size|server_names_hash_bucket_size|tcp_nodelay|tcp_nopush|types |try_files)\s/, 40 | 'module.http', 41 | ], 42 | [ 43 | /\b(accept_mutex|accept_mutex_delay|debug_connection|devpoll_changes|devpoll_events|epoll_events|kqueue_changes|kqueue_events|multi_accept|rtsig_signo|rtsig_overflow_events|rtsig_overflow_test|rtsig_overflow_threshold|use|worker_connections)\b/, 44 | 'module.events', 45 | ], 46 | [/\b(add_before_body|add_after_body|addition_types)\b/, 'module.http.addition'], 47 | [/\b(events)\b/, 'module.events'], 48 | [ 49 | /\b(fastcgi_index|fastcgi_hide_header|fastcgi_ignore_client_abort|fastcgi_intercept_errors|fastcgi_param|fastcgi_pass|fastcgi_pass_header|fastcgi_read_timeout|fastcgi_redirect_errors|fa|stcgi_storefastcgi_store_access|fastcgi_buffers|fastcgi_buffers_size|fastcgi_temp_path|fastcgi_buffer_size|fastcgi_connect_timeout|fastcgi_send_timeout|fastcgi_split_path_info)\b/, 50 | 'module.http', 51 | ], 52 | [/\$\w+/, 'variable'], 53 | [/#.*$/, 'comment'], 54 | // { include: "@numbers" }, 55 | ], 56 | comment: [[/#.*$/, 'comment']], 57 | // urldeclaration: [ 58 | // ['[^)\r\n]+', 'string'], 59 | // ['\\)', { token: 'delimiter.parenthesis', next: '@pop' }] 60 | // ], 61 | numbers: [ 62 | ['-?(\\d*\\.)?\\d+([eE][\\-+]?\\d+)?', { token: 'attribute.value.number', next: '@units' }], 63 | ['#[0-9a-fA-F_]+(?!\\w)', 'attribute.value.hex'], 64 | ], 65 | units: [['(M)?', 'attribute.value.unit', '@pop']], 66 | }, 67 | }; 68 | 69 | export const themeConfig: monaco.editor.IStandaloneThemeData = { 70 | colors: { 71 | // 'attribute.value.unit': '#68217a' 72 | }, 73 | // base: 'vs-dark', 74 | base: 'vs', 75 | inherit: true, 76 | rules: [ 77 | { 78 | token: 'module.http', 79 | foreground: '#007c7c', 80 | }, 81 | { 82 | token: 'module.events', 83 | foreground: '#007c7c', 84 | }, 85 | { 86 | token: 'http.headers', 87 | foreground: '#5400d7', 88 | }, 89 | { 90 | token: 'http.proxy', 91 | foreground: '#00a039', 92 | }, 93 | { 94 | token: 'module.main', 95 | foreground: '#c408ff', 96 | fontStyle: 'bold', 97 | }, 98 | { 99 | token: 'module.log', 100 | foreground: '#4d6aab', 101 | }, 102 | { 103 | token: 'module.http.addition', 104 | foreground: '#c408ff', 105 | }, 106 | { 107 | token: 'keywords', 108 | foreground: '#9effff', 109 | fontStyle: 'bold', 110 | }, 111 | { 112 | token: 'http.upstream', 113 | foreground: '#0078d0', 114 | fontStyle: 'bold', 115 | }, 116 | { 117 | token: 'identifier', 118 | foreground: '#8e44ad', 119 | }, 120 | { 121 | token: 'delimiter.bracket', 122 | foreground: '#737373', 123 | }, 124 | { 125 | token: 'delimiter', 126 | foreground: '#737373', 127 | }, 128 | { 129 | token: 'comment', 130 | foreground: '#b6b6b6', 131 | }, 132 | ], 133 | }; 134 | 135 | export const themeDarkConfig: monaco.editor.IStandaloneThemeData = { 136 | colors: { 137 | // 'attribute.value.unit': '#68217a' 138 | }, 139 | // base: 'vs-dark', 140 | base: 'vs-dark', 141 | inherit: true, 142 | rules: [ 143 | { 144 | token: 'module.http', 145 | foreground: '#00bbbb', 146 | }, 147 | { 148 | token: 'module.events', 149 | foreground: '#00bbbb', 150 | }, 151 | { 152 | token: 'http.headers', 153 | foreground: '#00bbbb', 154 | }, 155 | { 156 | token: 'http.proxy', 157 | foreground: '#58f18e', 158 | }, 159 | { 160 | token: 'module.main', 161 | foreground: '#c152e4', 162 | fontStyle: 'bold', 163 | }, 164 | { 165 | token: 'module.log', 166 | foreground: '#4d6aab', 167 | }, 168 | { 169 | token: 'module.http.addition', 170 | foreground: '#c152e4', 171 | }, 172 | { 173 | token: 'keywords', 174 | foreground: '#9effff', 175 | fontStyle: 'bold', 176 | }, 177 | { 178 | token: 'http.upstream', 179 | foreground: '#0078d0', 180 | fontStyle: 'bold', 181 | }, 182 | { 183 | token: 'identifier', 184 | foreground: '#8e44ad', 185 | }, 186 | { 187 | token: 'delimiter.bracket', 188 | foreground: '#737373', 189 | }, 190 | { 191 | token: 'delimiter', 192 | foreground: '#737373', 193 | }, 194 | ], 195 | }; 196 | -------------------------------------------------------------------------------- /core/src/directives.ts: -------------------------------------------------------------------------------- 1 | import { getDirectives, Format, getVariables, Directive } from '@nginx/reference-lib'; 2 | 3 | export type Autocomplete = { 4 | /** name of the NGINX module */ 5 | m: string; 6 | /** name */ 7 | n: string; 8 | /** markdown-formatted description */ 9 | d: string; 10 | /** default value, as an unformatted string as if a human typed it into an 11 | * nginx config */ 12 | v?: string; 13 | /** markdown CSV for valid contexts */ 14 | c?: string; 15 | /** markdown-formatted syntax specifications, including directive name. 16 | * Multiple syntaxes are seperated by newlines */ 17 | s?: string; 18 | }; 19 | 20 | function toAutocomplete(d: Directive): Autocomplete { 21 | const ret: Autocomplete = { 22 | m: d.module, 23 | n: d.name, 24 | d: d.description, 25 | c: d.contexts.map((c: string) => '`' + c + '`').join(', '), 26 | s: d.syntax.map((s: string) => `**${d.name}** ${s};`).join('\n'), 27 | }; 28 | 29 | if (d.default) { 30 | ret.v = `${d.name} ${d.default};`; 31 | } 32 | 33 | return ret; 34 | } 35 | 36 | const variables = getVariables(Format.Markdown).map((v) => ({ 37 | m: v.module, 38 | n: v.name, 39 | d: v.description, 40 | })); 41 | 42 | export const directives = getDirectives(Format.Markdown).map(toAutocomplete).concat(variables); 43 | -------------------------------------------------------------------------------- /core/src/index.tsx: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor'; 2 | import { themeConfig, themeDarkConfig, tokenConf } from './conf'; 3 | import suggestions from './suggestions'; 4 | import { directives } from './directives'; 5 | 6 | // Register a new language 7 | monaco.languages.register({ id: 'nginx' }); 8 | // monaco.languages.setLanguageConfiguration('nginx', { 9 | // autoClosingPairs: [ 10 | // { open: '{', close: '}' }, 11 | // { open: '"', close: '"' }, 12 | // ], 13 | // }); 14 | monaco.languages.setMonarchTokensProvider('nginx', tokenConf); 15 | monaco.editor.defineTheme('nginx-theme', themeConfig); 16 | monaco.editor.defineTheme('nginx-theme-dark', themeDarkConfig); 17 | 18 | monaco.languages.registerCompletionItemProvider('nginx', { 19 | provideCompletionItems: (model: monaco.editor.ITextModel, position: monaco.Position) => { 20 | const word = model.getWordUntilPosition(position); 21 | const range = { 22 | startLineNumber: position.lineNumber, 23 | endLineNumber: position.lineNumber, 24 | startColumn: word.startColumn, 25 | endColumn: word.endColumn, 26 | }; 27 | return { suggestions: suggestions(range) }; 28 | }, 29 | }); 30 | 31 | monaco.languages.registerHoverProvider('nginx', { 32 | provideHover: (model: monaco.editor.ITextModel, position: monaco.Position, token: monaco.CancellationToken) => { 33 | const word = model.getWordAtPosition(position); 34 | if (!word) return; 35 | const data = directives.find((item) => item.n === word.word || item.n === `$${word.word}`); 36 | if (!data) return; 37 | const range = { 38 | startLineNumber: position.lineNumber, 39 | endLineNumber: position.lineNumber, 40 | startColumn: word.startColumn, 41 | endColumn: word.endColumn, 42 | }; 43 | const contents = [{ value: `**\`${data.n}\`** | ${data.m} | ${data.c || ''}` }]; 44 | if (data.s) { 45 | contents.push({ value: `**syntax:** ${data.s || ''}` }); 46 | } 47 | if (data.v) { 48 | contents.push({ value: `**default:** ${data.v || ''}` }); 49 | } 50 | if (data.d) { 51 | contents.push({ value: `${data.d}` }); 52 | } 53 | return { 54 | contents: [...contents], 55 | range: range, 56 | }; 57 | }, 58 | }); 59 | -------------------------------------------------------------------------------- /core/src/suggestions.ts: -------------------------------------------------------------------------------- 1 | import * as monaco from 'monaco-editor'; 2 | import { directives } from './directives'; 3 | 4 | function getDirectives(range: monaco.IRange) { 5 | return directives.map((item) => ({ 6 | label: item.n, 7 | kind: monaco.languages.CompletionItemKind.Keyword, 8 | insertText: item.n, 9 | documentation: item.d, 10 | range, 11 | })); 12 | } 13 | 14 | export default function suggestions(range: monaco.IRange): monaco.languages.CompletionList['suggestions'] { 15 | return [ 16 | ...getDirectives(range), 17 | { 18 | label: 'upstream', 19 | kind: monaco.languages.CompletionItemKind.Snippet, 20 | insertText: [ 21 | // eslint-disable-next-line no-template-curly-in-string 22 | 'upstream ${1:upstream_name} {', 23 | // eslint-disable-next-line no-template-curly-in-string 24 | '\tserver ${0:127.0.0.1:3110};', 25 | '}', 26 | ].join('\n'), 27 | insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, 28 | detail: 'Upstream Example', 29 | range, 30 | }, 31 | { 32 | label: 'proxy_pass', 33 | kind: monaco.languages.CompletionItemKind.Snippet, 34 | insertText: [ 35 | // eslint-disable-next-line no-template-curly-in-string 36 | 'proxy_pass ${1:http}://${0192.168.188.222:32001};', 37 | ].join('\n'), 38 | insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, 39 | detail: 'proxy_pass Example', 40 | range, 41 | }, 42 | { 43 | label: 'location', 44 | kind: monaco.languages.CompletionItemKind.Snippet, 45 | insertText: [ 46 | // eslint-disable-next-line no-template-curly-in-string 47 | 'location ${1:/} {\n\t${0}\n}', 48 | ].join('\n'), 49 | insertTextRules: monaco.languages.CompletionItemInsertTextRule.InsertAsSnippet, 50 | detail: 'proxy_pass Example', 51 | range, 52 | }, 53 | ]; 54 | } 55 | -------------------------------------------------------------------------------- /core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "compilerOptions": { 4 | "outDir": "../cjs", 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.2", 3 | "packages": ["website", "core"] 4 | } 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "scripts": { 4 | "prepare": "npm run prettier", 5 | "⬇️⬇️⬇️⬇️⬇️ package ⬇️⬇️⬇️⬇️⬇️": "▼▼▼▼▼ package ▼▼▼▼▼", 6 | "build": "lerna exec --scope monaco-editor-nginx -- tsbb build src/*.tsx --use-babel --cjs cjs --bail", 7 | "watch": "lerna exec \"tsbb watch src/*.{tsx,ts} --use-babel --cjs cjs\" --scope monaco-editor-nginx", 8 | "⬆️⬆️⬆️⬆️⬆️ package ⬆️⬆️⬆️⬆️⬆️": "▲▲▲▲▲ package ▲▲▲▲▲", 9 | "start": "lerna exec --scope website -- npm run start", 10 | "doc": "lerna exec --scope website -- npm run build", 11 | "version": "lerna version --exact --force-publish --no-push --no-git-tag-version", 12 | "prettier": "prettier --write \"**/*.{js,jsx,tsx,ts,less,md,json}\"", 13 | "map": "source-map-explorer website/build/static/js/*.js --html website/build/website-result.html" 14 | }, 15 | "devDependencies": { 16 | "@types/turndown": "~5.0.1", 17 | "cheerio": "~1.0.0-rc.10", 18 | "compile-less-cli": "~1.9.0", 19 | "husky": "^9.0.0", 20 | "lerna": "^8.0.0", 21 | "lint-staged": "^15.2.5", 22 | "prettier": "^3.3.1", 23 | "pretty-quick": "^4.0.0", 24 | "react": "~18.2.0", 25 | "react-dom": "~18.2.0", 26 | "source-map-explorer": "^2.5.3", 27 | "ts-node": "^10.9.1", 28 | "tsbb": "^4.1.11", 29 | "turndown": "^7.1.2", 30 | "typescript": "^5.1.3" 31 | }, 32 | "workspaces": [ 33 | "website", 34 | "core" 35 | ], 36 | "overrides": { 37 | "typescript": "^5.1.3" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": ["config:base"], 3 | "packageRules": [ 4 | { 5 | "matchPackagePatterns": ["*"], 6 | "rangeStrategy": "replace" 7 | } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /sandbox.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "template": "node" 3 | } 4 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "lib": ["dom", "dom.iterable", "es6"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "esModuleInterop": true, 8 | "allowSyntheticDefaultImports": true, 9 | "strict": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "declaration": true, 16 | "baseUrl": ".", 17 | "jsx": "react-jsx", 18 | "noFallthroughCasesInSwitch": true 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /website/.kktrc.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import { LoaderConfOptions, WebpackConfiguration } from 'kkt'; 4 | import lessModules from '@kkt/less-modules'; 5 | import rawModules from '@kkt/raw-modules'; 6 | import scopePluginOptions from '@kkt/scope-plugin-options'; 7 | import MonacoWebpackPlugin from 'monaco-editor-webpack-plugin'; 8 | import pkg from 'monaco-editor-nginx/package.json'; 9 | 10 | export default (conf: WebpackConfiguration, env: 'production' | 'development', options: LoaderConfOptions) => { 11 | conf = rawModules(conf, env, { ...options }); 12 | conf = scopePluginOptions(conf, env, { 13 | ...options, 14 | allowedFiles: [path.resolve(process.cwd(), 'README.md')], 15 | }); 16 | conf = lessModules(conf, env, options); 17 | // Get the project version. 18 | conf.plugins!.push( 19 | new webpack.DefinePlugin({ 20 | VERSION: JSON.stringify(pkg.version), 21 | }), 22 | ); 23 | conf.plugins!.push( 24 | new MonacoWebpackPlugin({ 25 | languages: ['json'], 26 | }), 27 | ); 28 | if (env === 'production') { 29 | conf.output = { ...conf.output, publicPath: './' }; 30 | conf.optimization = { 31 | ...conf.optimization, 32 | splitChunks: { 33 | automaticNameDelimiter: '.', 34 | maxSize: 500000, 35 | minSize: 100000, 36 | cacheGroups: { 37 | reactvendor: { 38 | test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/, 39 | name: 'react-vendor', 40 | reuseExistingChunk: true, 41 | chunks: 'all', 42 | priority: -10, 43 | }, 44 | monacoeditor: { 45 | test: /[\\/]node_modules[\\/](monaco-editor)[\\/]/, 46 | name: 'monaco-editor-vendor', 47 | chunks: 'all', 48 | }, 49 | }, 50 | }, 51 | }; 52 | } 53 | return conf; 54 | }; 55 | -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "2.0.2", 4 | "private": true, 5 | "scripts": { 6 | "start": "kkt start", 7 | "build": "kkt build" 8 | }, 9 | "license": "MIT", 10 | "dependencies": { 11 | "@wcj/dark-mode": "^1.0.13", 12 | "monaco-editor-nginx": "2.0.2", 13 | "react": "~18.2.0", 14 | "react-dom": "~18.2.0" 15 | }, 16 | "devDependencies": { 17 | "@kkt/less-modules": "^7.5.4", 18 | "@kkt/raw-modules": "^7.5.4", 19 | "@kkt/scope-plugin-options": "^7.5.4", 20 | "@types/react": "^18.0.31", 21 | "@types/react-dom": "^18.0.11", 22 | "@uiw/react-monacoeditor": "^3.5.8", 23 | "kkt": "^7.5.4", 24 | "monaco-editor-webpack-plugin": "~7.1.0" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /website/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jaywcjlove/nginx-editor/5ebc190a974cedb603526009e2be28f2f2995bcc/website/public/favicon.ico -------------------------------------------------------------------------------- /website/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 20 | nginx online editor 21 | 22 | 23 | 24 | 25 |
26 | 36 | 37 | -------------------------------------------------------------------------------- /website/src/App.css: -------------------------------------------------------------------------------- 1 | [data-color-mode*='dark'], 2 | [data-color-mode*='dark'] body { 3 | --color-theme-hover-text: #ffffff33; 4 | } 5 | [data-color-mode*='light'], 6 | [data-color-mode*='light'] body { 7 | --color-theme-hover-text: #c9d1d9; 8 | } 9 | 10 | body { 11 | margin: 0; 12 | padding: 0; 13 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 14 | 'Droid Sans', 'Helvetica Neue', sans-serif; 15 | -webkit-font-smoothing: antialiased; 16 | -moz-osx-font-smoothing: grayscale; 17 | } 18 | 19 | html, 20 | body { 21 | background-color: var(--color-theme-bg); 22 | color: var(--color-theme-text); 23 | } 24 | 25 | .App { 26 | overflow: hidden; 27 | } 28 | -------------------------------------------------------------------------------- /website/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef, useState } from 'react'; 2 | import MonacoEditor, { RefEditorInstance } from '@uiw/react-monacoeditor'; 3 | import { nginxStr } from './nginx.conf'; 4 | import Header from './Header'; 5 | import './App.css'; 6 | 7 | import 'monaco-editor-nginx'; 8 | 9 | const App: React.FC = () => { 10 | const [theme, setTheme] = useState( 11 | document.documentElement.dataset.colorMode === 'dark' || !document.documentElement.dataset.colorMode 12 | ? 'vs-dark' 13 | : 'vs', 14 | ); 15 | const [content, setContent] = useState(nginxStr || ''); 16 | const [contentDownload, setContentDownload] = useState(content || nginxStr || ''); 17 | const editor = useRef(null); 18 | function resizeHandle(evn: UIEvent) { 19 | const { target } = evn; 20 | const width = (target as Window).innerWidth; 21 | const height = (target as Window).innerHeight; 22 | if (editor.current && editor.current.editor) { 23 | editor.current.editor.layout({ width, height: height - 36 }); 24 | } 25 | } 26 | useEffect(() => { 27 | if (editor.current && window) { 28 | window.addEventListener('resize', resizeHandle, false); 29 | } 30 | setTheme(document.documentElement.dataset.colorMode === 'dark' ? 'vs-dark' : 'vs'); 31 | document.addEventListener('colorschemechange', (e) => { 32 | setTheme(e.detail.colorScheme === 'dark' ? 'vs-dark' : 'vs'); 33 | }); 34 | return () => { 35 | window && window.removeEventListener('resize', resizeHandle, false); 36 | }; 37 | }, []); 38 | return ( 39 |
40 |
{ 43 | setContent(text); 44 | }} 45 | /> 46 | { 50 | setContentDownload(value); 51 | }} 52 | language="nginx" 53 | value={content} 54 | height="calc(100vh - 36px)" 55 | /> 56 |
57 | ); 58 | }; 59 | 60 | export default App; 61 | -------------------------------------------------------------------------------- /website/src/Header.module.less: -------------------------------------------------------------------------------- 1 | .header { 2 | height: 32px; 3 | display: flex; 4 | padding: 0 10px; 5 | align-items: center; 6 | user-select: none; 7 | a { 8 | color: var(--color-theme-text); 9 | display: flex; 10 | align-items: center; 11 | } 12 | } 13 | 14 | .title { 15 | padding-left: 6px; 16 | } 17 | 18 | .filename { 19 | flex: 1; 20 | text-align: center; 21 | font-size: 14px; 22 | } 23 | -------------------------------------------------------------------------------- /website/src/Header.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import '@wcj/dark-mode'; 3 | import styles from 'Header.module.less'; 4 | import LoadFile, { LoadFileProps } from './LoadFile'; 5 | // @ts-ignore 6 | import { ReactComponent } from './github.svg'; 7 | // @ts-ignore 8 | import { ReactComponent as NginxLogo } from './nginx.svg'; 9 | 10 | type HeaderProps = LoadFileProps & {}; 11 | 12 | export default function Header(props: HeaderProps) { 13 | const { onLoadContent } = props; 14 | const [filename, setFilename] = useState('nginx.example.conf'); 15 | return ( 16 |
17 | 18 |
nginx editor
19 |
{filename}
20 | { 24 | setFilename(file!.name || ''); 25 | onLoadContent && onLoadContent(text, evn); 26 | }} 27 | /> 28 | 29 | 30 | 31 | 32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /website/src/LoadFile.module.less: -------------------------------------------------------------------------------- 1 | .button { 2 | border: 0; 3 | background-color: transparent !important; 4 | color: var(--color-theme-text); 5 | padding: 2px 5px; 6 | transition: background-color 0.3s; 7 | cursor: pointer; 8 | &:focus { 9 | box-shadow: inset 0 0 0 #000; 10 | outline: 0; 11 | } 12 | &:hover { 13 | background-color: var(--color-theme-hover-text) !important; 14 | } 15 | + .button { 16 | margin-right: 3px; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /website/src/LoadFile.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useEffect, Fragment } from 'react'; 2 | import styles from './LoadFile.module.less'; 3 | 4 | export type LoadFileProps = { 5 | content?: string; 6 | filename?: string; 7 | onLoadContent?: (text: string, evn: ProgressEvent, file?: File) => void; 8 | }; 9 | 10 | export default function LoadFile(props: LoadFileProps) { 11 | const { onLoadContent } = props; 12 | const inputRef = useRef(null); 13 | const fileReader = useRef(new FileReader()); 14 | const fileState = useRef(); 15 | useEffect(() => { 16 | const inp = inputRef.current; 17 | if (inp) { 18 | fileReader.current.onload = (e) => { 19 | if (e.target && e.target.result) { 20 | onLoadContent && onLoadContent(e.target.result.toString(), e, fileState.current); 21 | } 22 | }; 23 | inp.addEventListener('change', handleUpload, false); 24 | } 25 | return () => { 26 | if (inp) { 27 | inp.removeEventListener('change', handleUpload, false); 28 | } 29 | }; 30 | // eslint-disable-next-line 31 | }, []); 32 | 33 | function handleUpload(eve: Event) { 34 | const { target } = eve as unknown as React.ChangeEvent; 35 | if (target.files && target.files[0]) { 36 | fileState.current = target.files[0]; 37 | fileReader.current.readAsText(target.files[0]); 38 | } 39 | } 40 | 41 | function handleClick() { 42 | inputRef.current!.click(); 43 | } 44 | 45 | function handleSaveAs() { 46 | let aTag = document.createElement('a'); 47 | if ('download' in aTag) { 48 | aTag.setAttribute('download', props.filename || 'nginx.conf'); 49 | let blob = new Blob([props.content || ''], { type: '' }); 50 | aTag.setAttribute('href', URL.createObjectURL(blob)); 51 | document.body.appendChild(aTag); 52 | const eventMouse = document.createEvent('MouseEvents'); 53 | eventMouse.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); 54 | aTag.dispatchEvent(eventMouse); 55 | document.body.removeChild(aTag); 56 | } 57 | } 58 | 59 | return ( 60 | 61 | {props.content && ( 62 | 65 | )} 66 | 70 | 71 | ); 72 | } 73 | -------------------------------------------------------------------------------- /website/src/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /website/src/index.tsx: -------------------------------------------------------------------------------- 1 | import App from './App'; 2 | import { createRoot } from 'react-dom/client'; 3 | 4 | const container = document.getElementById('root'); 5 | const root = createRoot(container!); 6 | root.render(); 7 | -------------------------------------------------------------------------------- /website/src/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /website/src/nginx.conf.ts: -------------------------------------------------------------------------------- 1 | export const nginxStr = `user www www; ## Default: nobody 2 | worker_processes 5; ## Default: 1 3 | error_log logs/error.log; 4 | pid logs/nginx.pid; 5 | worker_rlimit_nofile 8192; 6 | 7 | events { 8 | worker_connections 4096; ## Default: 1024 9 | } 10 | 11 | http { 12 | include conf/mime.types; 13 | include /etc/nginx/proxy.conf; 14 | include /etc/nginx/fastcgi.conf; 15 | index index.html index.htm index.php; 16 | 17 | default_type application/octet-stream; 18 | log_format main '$remote_addr - $remote_user [$time_local] $status ' 19 | '"$request" $body_bytes_sent "$http_referer" ' 20 | '"$http_user_agent" "$http_x_forwarded_for"'; 21 | access_log logs/access.log main; 22 | sendfile on; 23 | tcp_nopush on; 24 | server_names_hash_bucket_size 128; # this seems to be required for some vhosts 25 | 26 | server { # php/fastcgi 27 | listen 80; 28 | server_name domain1.com www.domain1.com; 29 | access_log logs/domain1.access.log main; 30 | root html; 31 | 32 | location ~ \\.php$ { 33 | fastcgi_pass 127.0.0.1:1025; 34 | } 35 | } 36 | 37 | server { # simple reverse-proxy 38 | listen 80; 39 | server_name domain2.com www.domain2.com; 40 | access_log logs/domain2.access.log main; 41 | 42 | # serve static files 43 | location ~ ^/(images|javascript|js|css|flash|media|static)/ { 44 | root /var/www/virtual/big.server.com/htdocs; 45 | expires 30d; 46 | } 47 | 48 | # pass requests for dynamic content to rails/turbogears/zope, et al 49 | location / { 50 | proxy_pass http://127.0.0.1:8080; 51 | } 52 | } 53 | 54 | upstream big_server_com { 55 | server 127.0.0.3:8000 weight=5; 56 | server 127.0.0.3:8001 weight=5; 57 | server 192.168.0.1:8000; 58 | server 192.168.0.1:8001; 59 | } 60 | 61 | server { # simple load balancing 62 | listen 80; 63 | server_name big.server.com; 64 | access_log logs/big.server.access.log main; 65 | 66 | location / { 67 | proxy_pass http://big_server_com; 68 | } 69 | } 70 | }`; 71 | -------------------------------------------------------------------------------- /website/src/nginx.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /website/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | ////// 2 | /// 3 | 4 | declare module '*.module.less' { 5 | const classes: { readonly [key: string]: string }; 6 | export default classes; 7 | } 8 | 9 | declare module '*.md' { 10 | const src: string; 11 | export default src; 12 | } 13 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig", 3 | "include": [".kktrc.ts", "src"], 4 | "compilerOptions": { 5 | "jsx": "react-jsx", 6 | "baseUrl": "./src", 7 | "noEmit": true 8 | } 9 | } 10 | --------------------------------------------------------------------------------