✨ 现在支持 Web Admin 面板 #4
							
								
								
									
										59
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,59 @@ | ||||
| <component name="ProjectCodeStyleConfiguration"> | ||||
|   <code_scheme name="Project" version="173"> | ||||
|     <HTMLCodeStyleSettings> | ||||
|       <option name="HTML_SPACE_INSIDE_EMPTY_TAG" value="true" /> | ||||
|     </HTMLCodeStyleSettings> | ||||
|     <JSCodeStyleSettings version="0"> | ||||
|       <option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" /> | ||||
|       <option name="FORCE_SEMICOLON_STYLE" value="true" /> | ||||
|       <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" /> | ||||
|       <option name="FORCE_QUOTE_STYlE" value="true" /> | ||||
|       <option name="ENFORCE_TRAILING_COMMA" value="Remove" /> | ||||
|       <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" /> | ||||
|       <option name="SPACES_WITHIN_IMPORTS" value="true" /> | ||||
|     </JSCodeStyleSettings> | ||||
|     <TypeScriptCodeStyleSettings version="0"> | ||||
|       <option name="USE_SEMICOLON_AFTER_STATEMENT" value="false" /> | ||||
|       <option name="FORCE_SEMICOLON_STYLE" value="true" /> | ||||
|       <option name="SPACE_BEFORE_FUNCTION_LEFT_PARENTH" value="false" /> | ||||
|       <option name="FORCE_QUOTE_STYlE" value="true" /> | ||||
|       <option name="ENFORCE_TRAILING_COMMA" value="Remove" /> | ||||
|       <option name="SPACES_WITHIN_OBJECT_LITERAL_BRACES" value="true" /> | ||||
|       <option name="SPACES_WITHIN_IMPORTS" value="true" /> | ||||
|     </TypeScriptCodeStyleSettings> | ||||
|     <VueCodeStyleSettings> | ||||
|       <option name="INTERPOLATION_NEW_LINE_AFTER_START_DELIMITER" value="false" /> | ||||
|       <option name="INTERPOLATION_NEW_LINE_BEFORE_END_DELIMITER" value="false" /> | ||||
|     </VueCodeStyleSettings> | ||||
|     <codeStyleSettings language="HTML"> | ||||
|       <option name="SOFT_MARGINS" value="120" /> | ||||
|       <indentOptions> | ||||
|         <option name="INDENT_SIZE" value="2" /> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="2" /> | ||||
|         <option name="TAB_SIZE" value="2" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="JavaScript"> | ||||
|       <option name="SOFT_MARGINS" value="120" /> | ||||
|       <indentOptions> | ||||
|         <option name="INDENT_SIZE" value="2" /> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="2" /> | ||||
|         <option name="TAB_SIZE" value="2" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="TypeScript"> | ||||
|       <option name="SOFT_MARGINS" value="120" /> | ||||
|       <indentOptions> | ||||
|         <option name="INDENT_SIZE" value="2" /> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="2" /> | ||||
|         <option name="TAB_SIZE" value="2" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|     <codeStyleSettings language="Vue"> | ||||
|       <option name="SOFT_MARGINS" value="120" /> | ||||
|       <indentOptions> | ||||
|         <option name="CONTINUATION_INDENT_SIZE" value="2" /> | ||||
|       </indentOptions> | ||||
|     </codeStyleSettings> | ||||
|   </code_scheme> | ||||
| </component> | ||||
							
								
								
									
										5
									
								
								.idea/codeStyles/codeStyleConfig.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								.idea/codeStyles/codeStyleConfig.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| <component name="ProjectCodeStyleConfiguration"> | ||||
|   <state> | ||||
|     <option name="USE_PER_PROJECT_SETTINGS" value="true" /> | ||||
|   </state> | ||||
| </component> | ||||
| @@ -1,8 +1,14 @@ | ||||
| # Building Backend | ||||
| FROM golang:alpine as roadsign-server | ||||
|  | ||||
| RUN apk add nodejs npm | ||||
|  | ||||
| WORKDIR /source | ||||
| COPY . . | ||||
| WORKDIR /source/pkg/sideload/view | ||||
| RUN npm install | ||||
| RUN npm run build | ||||
| WORKDIR /source | ||||
| RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -buildvcs -o /dist ./pkg/cmd/server/main.go | ||||
|  | ||||
| # Runtime | ||||
|   | ||||
							
								
								
									
										29
									
								
								pkg/sideload/processes.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								pkg/sideload/processes.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| package sideload | ||||
|  | ||||
| import ( | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sign" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| func getProcesses(c *fiber.Ctx) error { | ||||
| 	processes := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance { | ||||
| 		return item.Processes | ||||
| 	}) | ||||
|  | ||||
| 	return c.JSON(processes) | ||||
| } | ||||
|  | ||||
| func getProcessLog(c *fiber.Ctx) error { | ||||
| 	processes := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance { | ||||
| 		return item.Processes | ||||
| 	}) | ||||
|  | ||||
| 	if target, ok := lo.Find(processes, func(item *sign.ProcessInstance) bool { | ||||
| 		return item.ID == c.Params("id") | ||||
| 	}); !ok { | ||||
| 		return fiber.NewError(fiber.StatusNotFound) | ||||
| 	} else { | ||||
| 		return c.SendString(target.GetLogs()) | ||||
| 	} | ||||
| } | ||||
| @@ -15,8 +15,8 @@ import ( | ||||
| func doPublish(c *fiber.Ctx) error { | ||||
| 	var workdir string | ||||
| 	var site *sign.SiteConfig | ||||
| 	var upstream *sign.UpstreamConfig | ||||
| 	var process *sign.ProcessConfig | ||||
| 	var upstream *sign.UpstreamInstance | ||||
| 	var process *sign.ProcessInstance | ||||
| 	for _, item := range sign.App.Sites { | ||||
| 		if item.ID == c.Params("site") { | ||||
| 			site = item | ||||
|   | ||||
| @@ -1,8 +1,11 @@ | ||||
| package sideload | ||||
|  | ||||
| import ( | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sideload/view" | ||||
| 	"fmt" | ||||
| 	"github.com/gofiber/fiber/v2/middleware/filesystem" | ||||
| 	jsoniter "github.com/json-iterator/go" | ||||
| 	"net/http" | ||||
|  | ||||
| 	roadsign "code.smartsheep.studio/goatworks/roadsign/pkg" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| @@ -39,9 +42,21 @@ func InitSideload() *fiber.App { | ||||
| 		}, | ||||
| 	})) | ||||
|  | ||||
| 	app.Use("/", filesystem.New(filesystem.Config{ | ||||
| 		Root:         http.FS(view.FS), | ||||
| 		PathPrefix:   "dist", | ||||
| 		Index:        "index.html", | ||||
| 		NotFoundFile: "index.html", | ||||
| 	})) | ||||
|  | ||||
| 	cgi := app.Group("/cgi").Name("CGI") | ||||
| 	{ | ||||
| 		cgi.All("/connectivity", responseConnectivity) | ||||
| 		cgi.Get("/statistics", getStatistics) | ||||
| 		cgi.Get("/sites", getSites) | ||||
| 		cgi.Get("/sites/cfg/:id", getSiteConfig) | ||||
| 		cgi.Get("/processes", getProcesses) | ||||
| 		cgi.Get("/processes/logs/:id", getProcessLog) | ||||
| 	} | ||||
|  | ||||
| 	webhooks := app.Group("/webhooks").Name("WebHooks") | ||||
|   | ||||
| @@ -12,6 +12,24 @@ import ( | ||||
| 	"gopkg.in/yaml.v2" | ||||
| ) | ||||
|  | ||||
| func getSites(c *fiber.Ctx) error { | ||||
| 	return c.JSON(sign.App.Sites) | ||||
| } | ||||
|  | ||||
| func getSiteConfig(c *fiber.Ctx) error { | ||||
| 	fp := filepath.Join(viper.GetString("paths.configs"), c.Params("id")) | ||||
|  | ||||
| 	var err error | ||||
| 	var data []byte | ||||
| 	if data, err = os.ReadFile(fp + ".yml"); err != nil { | ||||
| 		if data, err = os.ReadFile(fp + ".yaml"); err != nil { | ||||
| 			return fiber.NewError(fiber.StatusNotFound, err.Error()) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return c.Type("yaml").SendString(string(data)) | ||||
| } | ||||
|  | ||||
| func doSyncSite(c *fiber.Ctx) error { | ||||
| 	var req sign.SiteConfig | ||||
|  | ||||
|   | ||||
							
								
								
									
										28
									
								
								pkg/sideload/statistics.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								pkg/sideload/statistics.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,28 @@ | ||||
| package sideload | ||||
|  | ||||
| import ( | ||||
| 	"code.smartsheep.studio/goatworks/roadsign/pkg/sign" | ||||
| 	"github.com/gofiber/fiber/v2" | ||||
| 	"github.com/samber/lo" | ||||
| ) | ||||
|  | ||||
| func getStatistics(c *fiber.Ctx) error { | ||||
| 	upstreams := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.UpstreamInstance { | ||||
| 		return item.Upstreams | ||||
| 	}) | ||||
| 	processes := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance { | ||||
| 		return item.Processes | ||||
| 	}) | ||||
| 	unhealthy := lo.FlatMap(sign.App.Sites, func(item *sign.SiteConfig, idx int) []*sign.ProcessInstance { | ||||
| 		return lo.Filter(item.Processes, func(item *sign.ProcessInstance, idx int) bool { | ||||
| 			return item.Status != sign.ProcessStarted | ||||
| 		}) | ||||
| 	}) | ||||
|  | ||||
| 	return c.JSON(fiber.Map{ | ||||
| 		"sites":     len(sign.App.Sites), | ||||
| 		"upstreams": len(upstreams), | ||||
| 		"processes": len(processes), | ||||
| 		"status":    len(unhealthy) == 0, | ||||
| 	}) | ||||
| } | ||||
							
								
								
									
										2
									
								
								pkg/sideload/view/.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								pkg/sideload/view/.dockerignore
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| /dist | ||||
| /node_modules | ||||
							
								
								
									
										15
									
								
								pkg/sideload/view/.eslintrc.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								pkg/sideload/view/.eslintrc.cjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,15 @@ | ||||
| /* eslint-env node */ | ||||
| require('@rushstack/eslint-patch/modern-module-resolution') | ||||
|  | ||||
| module.exports = { | ||||
|   root: true, | ||||
|   'extends': [ | ||||
|     'plugin:vue/vue3-essential', | ||||
|     'eslint:recommended', | ||||
|     '@vue/eslint-config-typescript', | ||||
|     '@vue/eslint-config-prettier/skip-formatting' | ||||
|   ], | ||||
|   parserOptions: { | ||||
|     ecmaVersion: 'latest' | ||||
|   } | ||||
| } | ||||
							
								
								
									
										30
									
								
								pkg/sideload/view/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								pkg/sideload/view/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| # Logs | ||||
| logs | ||||
| *.log | ||||
| npm-debug.log* | ||||
| yarn-debug.log* | ||||
| yarn-error.log* | ||||
| pnpm-debug.log* | ||||
| lerna-debug.log* | ||||
|  | ||||
| node_modules | ||||
| .DS_Store | ||||
| dist | ||||
| dist-ssr | ||||
| coverage | ||||
| *.local | ||||
|  | ||||
| /cypress/videos/ | ||||
| /cypress/screenshots/ | ||||
|  | ||||
| # Editor directories and files | ||||
| .vscode/* | ||||
| !.vscode/extensions.json | ||||
| .idea | ||||
| *.suo | ||||
| *.ntvs* | ||||
| *.njsproj | ||||
| *.sln | ||||
| *.sw? | ||||
|  | ||||
| *.tsbuildinfo | ||||
							
								
								
									
										8
									
								
								pkg/sideload/view/.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								pkg/sideload/view/.prettierrc.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|   "$schema": "https://json.schemastore.org/prettierrc", | ||||
|   "semi": false, | ||||
|   "tabWidth": 2, | ||||
|   "singleQuote": false, | ||||
|   "printWidth": 120, | ||||
|   "trailingComma": "none" | ||||
| } | ||||
							
								
								
									
										8
									
								
								pkg/sideload/view/.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								pkg/sideload/view/.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|   "recommendations": [ | ||||
|     "Vue.volar", | ||||
|     "Vue.vscode-typescript-vue-plugin", | ||||
|     "dbaeumer.vscode-eslint", | ||||
|     "esbenp.prettier-vscode" | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										46
									
								
								pkg/sideload/view/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								pkg/sideload/view/README.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,46 @@ | ||||
| # @roadsign/sideload-ui | ||||
|  | ||||
| This template should help get you started developing with Vue 3 in Vite. | ||||
|  | ||||
| ## Recommended IDE Setup | ||||
|  | ||||
| [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin). | ||||
|  | ||||
| ## Type Support for `.vue` Imports in TS | ||||
|  | ||||
| TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin) to make the TypeScript language service aware of `.vue` types. | ||||
|  | ||||
| If the standalone TypeScript plugin doesn't feel fast enough to you, Volar has also implemented a [Take Over Mode](https://github.com/johnsoncodehk/volar/discussions/471#discussioncomment-1361669) that is more performant. You can enable it by the following steps: | ||||
|  | ||||
| 1. Disable the built-in TypeScript Extension | ||||
|     1) Run `Extensions: Show Built-in Extensions` from VSCode's command palette | ||||
|     2) Find `TypeScript and JavaScript Language Features`, right click and select `Disable (Workspace)` | ||||
| 2. Reload the VSCode window by running `Developer: Reload Window` from the command palette. | ||||
|  | ||||
| ## Customize configuration | ||||
|  | ||||
| See [Vite Configuration Reference](https://vitejs.dev/config/). | ||||
|  | ||||
| ## Project Setup | ||||
|  | ||||
| ```sh | ||||
| yarn | ||||
| ``` | ||||
|  | ||||
| ### Compile and Hot-Reload for Development | ||||
|  | ||||
| ```sh | ||||
| yarn dev | ||||
| ``` | ||||
|  | ||||
| ### Type-Check, Compile and Minify for Production | ||||
|  | ||||
| ```sh | ||||
| yarn build | ||||
| ``` | ||||
|  | ||||
| ### Lint with [ESLint](https://eslint.org/) | ||||
|  | ||||
| ```sh | ||||
| yarn lint | ||||
| ``` | ||||
							
								
								
									
										6
									
								
								pkg/sideload/view/embed.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								pkg/sideload/view/embed.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| package view | ||||
|  | ||||
| import "embed" | ||||
|  | ||||
| //go:embed all:dist | ||||
| var FS embed.FS | ||||
							
								
								
									
										1
									
								
								pkg/sideload/view/env.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								pkg/sideload/view/env.d.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1 @@ | ||||
| /// <reference types="vite/client" /> | ||||
							
								
								
									
										13
									
								
								pkg/sideload/view/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								pkg/sideload/view/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
|   <head> | ||||
|     <meta charset="UTF-8"> | ||||
|     <link rel="icon" href="/favicon.ico"> | ||||
|     <meta name="viewport" content="width=device-width, initial-scale=1.0"> | ||||
|     <title>RoadSign</title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <div id="app"></div> | ||||
|     <script type="module" src="/src/main.ts"></script> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										44
									
								
								pkg/sideload/view/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								pkg/sideload/view/package.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| { | ||||
|   "name": "@roadsign/sideload-ui", | ||||
|   "version": "0.0.0", | ||||
|   "private": true, | ||||
|   "type": "module", | ||||
|   "scripts": { | ||||
|     "dev": "vite", | ||||
|     "build": "run-p type-check \"build-only {@}\" --", | ||||
|     "preview": "vite preview", | ||||
|     "build-only": "vite build", | ||||
|     "type-check": "vue-tsc --build --force", | ||||
|     "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", | ||||
|     "format": "prettier --write src/" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@guolao/vue-monaco-editor": "^1.4.1", | ||||
|     "highlight.js": "^11.9.0", | ||||
|     "js-yaml": "^4.1.0", | ||||
|     "pinia": "^2.1.7", | ||||
|     "vue": "^3.3.11", | ||||
|     "vue-router": "^4.2.5" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@rushstack/eslint-patch": "^1.3.3", | ||||
|     "@tsconfig/node18": "^18.2.2", | ||||
|     "@types/js-yaml": "^4.0.9", | ||||
|     "@types/node": "^18.19.3", | ||||
|     "@vicons/carbon": "^0.12.0", | ||||
|     "@vitejs/plugin-vue": "^4.5.2", | ||||
|     "@vue/eslint-config-prettier": "^8.0.0", | ||||
|     "@vue/eslint-config-typescript": "^12.0.0", | ||||
|     "@vue/tsconfig": "^0.5.0", | ||||
|     "eslint": "^8.49.0", | ||||
|     "eslint-plugin-vue": "^9.17.0", | ||||
|     "naive-ui": "^2.36.0", | ||||
|     "npm-run-all2": "^6.1.1", | ||||
|     "prettier": "^3.0.3", | ||||
|     "typescript": "~5.3.0", | ||||
|     "unocss": "^0.58.2", | ||||
|     "vfonts": "^0.0.3", | ||||
|     "vite": "^5.0.10", | ||||
|     "vue-tsc": "^1.8.25" | ||||
|   } | ||||
| } | ||||
							
								
								
									
										6
									
								
								pkg/sideload/view/src/assets/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								pkg/sideload/view/src/assets/main.css
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | ||||
| @import "vfonts/IBMPlexSans.css"; | ||||
| @import "vfonts/IBMPlexMono.css"; | ||||
|  | ||||
| a { | ||||
|     color: #3f7ee8; | ||||
| } | ||||
							
								
								
									
										135
									
								
								pkg/sideload/view/src/components/data/sites-table-action.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										135
									
								
								pkg/sideload/view/src/components/data/sites-table-action.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,135 @@ | ||||
| <template> | ||||
|   <div class="flex gap-[4px]"> | ||||
|     <n-button size="small" @click="publishing = true"> | ||||
|       <template #icon> | ||||
|         <n-icon :component="CloudUpload" /> | ||||
|       </template> | ||||
|     </n-button> | ||||
|     <n-button size="small" @click="editConfig()"> | ||||
|       <template #icon> | ||||
|         <n-icon :component="Edit" /> | ||||
|       </template> | ||||
|     </n-button> | ||||
|  | ||||
|     <n-modal | ||||
|       v-model:show="publishing" | ||||
|       class="w-[720px]" | ||||
|       preset="card" | ||||
|       title="Publish Artifacts" | ||||
|       segmented | ||||
|       closable | ||||
|     > | ||||
|       We are sorry about this tool isn't completed yet. <br> | ||||
|       For now, you can use our <b>Wonderful Command Line Tool —— RDS</b> <br> | ||||
|       Learn more on our <a href="https://wiki.smartsheep.studio/roadsign/index.html" target="_blank">official wiki</a>. | ||||
|       <br> | ||||
|       <br> | ||||
|       Install it by this command below | ||||
|       <n-code code="go install code.smartsheep.studio/goatworks/roadsign/pkg/cmd/rds@latest" /> | ||||
|       <br> | ||||
|       Then connect your rds client to this server | ||||
|       <n-code :code="`rds connect <name> ${host} <credentials>`" /> | ||||
|       <br> | ||||
|       After that you can publish your stuff (You need to compress them to zip archive before publish) | ||||
|       <n-code :code="`rds deploy <name> ${props.id} <upstream id or process id>`" /> | ||||
|     </n-modal> | ||||
|  | ||||
|     <n-modal | ||||
|       v-model:show="editing" | ||||
|       class="w-[720px]" | ||||
|       content-style="padding: 0" | ||||
|       preset="card" | ||||
|       title="Edit Configuration" | ||||
|       segmented | ||||
|       closable | ||||
|     > | ||||
|       <div class="relative h-[540px]"> | ||||
|         <vue-monaco-editor | ||||
|           v-model:value="config" | ||||
|           :options="{ automaticLayout: true, minimap: { enabled: false } }" | ||||
|           language="yaml" | ||||
|         /> | ||||
|  | ||||
|         <div class="fab"> | ||||
|           <n-tooltip placement="left"> | ||||
|             <template #trigger> | ||||
|               <n-button | ||||
|                 circle | ||||
|                 type="primary" | ||||
|                 size="large" | ||||
|                 class="shadow-lg" | ||||
|                 :loading="submitting" | ||||
|                 @click="syncConfig()" | ||||
|               > | ||||
|                 <template #icon> | ||||
|                   <n-icon :component="Save" /> | ||||
|                 </template> | ||||
|               </n-button> | ||||
|             </template> | ||||
|             This operation will restart all processes related. Service may interrupted for some while. | ||||
|           </n-tooltip> | ||||
|         </div> | ||||
|       </div> | ||||
|     </n-modal> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { NButton, NCode, NIcon, NModal, NTooltip, useMessage } from "naive-ui" | ||||
| import { CloudUpload, Edit, Save } from "@vicons/carbon" | ||||
| import { ref } from "vue" | ||||
| import { VueMonacoEditor } from "@guolao/vue-monaco-editor" | ||||
| import * as yaml from "js-yaml" | ||||
|  | ||||
| const message = useMessage() | ||||
|  | ||||
| const props = defineProps<{ id: string, rules: any[], upstreams: any[], processes: any[] }>() | ||||
| const emits = defineEmits(["reload"]) | ||||
| const host = location.protocol + "//" + location.host | ||||
|  | ||||
| const submitting = ref(false) | ||||
|  | ||||
| const publishing = ref(false) | ||||
| const editing = ref(false) | ||||
|  | ||||
| const config = ref<string | undefined>(undefined) | ||||
|  | ||||
| async function editConfig() { | ||||
|   const resp = await fetch(`/cgi/sites/cfg/${props.id}`) | ||||
|   config.value = await resp.text() | ||||
|   editing.value = true | ||||
| } | ||||
|  | ||||
| async function syncConfig() { | ||||
|   if (config.value == null) return | ||||
|  | ||||
|   let content | ||||
|   try { | ||||
|     content = yaml.load(config.value) | ||||
|   } catch (e: any) { | ||||
|     message.warning(`Your configuration has some issue: ${e.message}`) | ||||
|     return | ||||
|   } | ||||
|  | ||||
|   submitting.value = true | ||||
|   const resp = await fetch(`/webhooks/sync/${props.id}`, { | ||||
|     method: "PUT", | ||||
|     headers: { "Content-Type": "application/json" }, | ||||
|     body: JSON.stringify(content) | ||||
|   }) | ||||
|   if (resp.status != 200) { | ||||
|     message.error(`Something went wrong... ${await resp.text()}`) | ||||
|   } else { | ||||
|     emits("reload") | ||||
|   } | ||||
|   submitting.value = false | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .fab { | ||||
|   position: absolute; | ||||
|   bottom: 16px; | ||||
|   right: 24px; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										110
									
								
								pkg/sideload/view/src/components/data/sites-table-add.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								pkg/sideload/view/src/components/data/sites-table-add.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <n-button circle size="small" type="primary" @click="creating = true"> | ||||
|       <template #icon> | ||||
|         <n-icon :component="Add" /> | ||||
|       </template> | ||||
|     </n-button> | ||||
|  | ||||
|     <n-modal | ||||
|       v-model:show="creating" | ||||
|       class="w-[720px]" | ||||
|       content-style="padding: 0" | ||||
|       preset="card" | ||||
|       title="Create Site" | ||||
|       segmented | ||||
|       closable | ||||
|     > | ||||
|       <div class="py-4 px-5 border border-solid border-b border-[#eee]"> | ||||
|         <n-input | ||||
|           v-model:value="data.id" | ||||
|           placeholder="Will be the file name of this file" | ||||
|         /> | ||||
|       </div> | ||||
|  | ||||
|       <div class="relative mt-[4px] h-[540px]"> | ||||
|         <vue-monaco-editor | ||||
|           v-model:value="data.content" | ||||
|           :options="{ automaticLayout: true, minimap: { enabled: false } }" | ||||
|           language="yaml" | ||||
|         /> | ||||
|  | ||||
|         <div class="fab"> | ||||
|           <n-tooltip placement="left"> | ||||
|             <template #trigger> | ||||
|               <n-button | ||||
|                 circle | ||||
|                 type="primary" | ||||
|                 size="large" | ||||
|                 class="shadow-lg" | ||||
|                 :loading="submitting" | ||||
|                 @click="submit()" | ||||
|               > | ||||
|                 <template #icon> | ||||
|                   <n-icon :component="Checkmark" /> | ||||
|                 </template> | ||||
|               </n-button> | ||||
|             </template> | ||||
|             This operation will publish this site right away. | ||||
|           </n-tooltip> | ||||
|         </div> | ||||
|       </div> | ||||
|     </n-modal> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { NButton, NIcon, NInput, NModal, NTooltip, useMessage } from "naive-ui" | ||||
| import { Add, Checkmark } from "@vicons/carbon" | ||||
| import { VueMonacoEditor } from "@guolao/vue-monaco-editor" | ||||
| import { ref } from "vue" | ||||
| import * as yaml from "js-yaml" | ||||
|  | ||||
| const message = useMessage() | ||||
|  | ||||
| const emits = defineEmits(["reload"]) | ||||
|  | ||||
| const submitting = ref(false) | ||||
| const creating = ref(false) | ||||
|  | ||||
| const data = ref<any>({}) | ||||
|  | ||||
| async function submit() { | ||||
|   let content | ||||
|   try { | ||||
|     content = yaml.load(data.value.content) | ||||
|   } catch (e: any) { | ||||
|     message.warning(`Your configuration has some issue: ${e.message}`) | ||||
|     return | ||||
|   } | ||||
|  | ||||
|   submitting.value = true | ||||
|   const resp = await fetch(`/webhooks/sync/${data.value.id}`, { | ||||
|     method: "PUT", | ||||
|     headers: { "Content-Type": "application/json" }, | ||||
|     body: JSON.stringify(content) | ||||
|   }) | ||||
|   if (resp.status != 200) { | ||||
|     message.error(`Something went wrong... ${await resp.text()}`) | ||||
|   } else { | ||||
|     reset() | ||||
|     emits("reload") | ||||
|     message.success("Your site has been created! 🎉") | ||||
|     creating.value = false | ||||
|   } | ||||
|   submitting.value = false | ||||
| } | ||||
|  | ||||
| function reset() { | ||||
|   data.value.id = "" | ||||
|   data.value.content = "" | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .fab { | ||||
|   position: absolute; | ||||
|   bottom: 16px; | ||||
|   right: 24px; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										36
									
								
								pkg/sideload/view/src/components/data/sites-table-expand.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								pkg/sideload/view/src/components/data/sites-table-expand.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,36 @@ | ||||
| <template> | ||||
|   <div class="flex flex-col gap-1"> | ||||
|  | ||||
|     <div> | ||||
|       <div class="font-bold">Rules</div> | ||||
|       <n-code :hljs="hljs" :code="parseData(props.rules)" language="json" /> | ||||
|     </div> | ||||
|  | ||||
|     <div> | ||||
|       <div class="font-bold">Upstreams</div> | ||||
|       <n-code :hljs="hljs" :code="parseData(props.upstreams)" language="json" /> | ||||
|     </div> | ||||
|  | ||||
|     <div> | ||||
|       <div class="font-bold">Processes</div> | ||||
|       <n-code :hljs="hljs" :code="parseData(props.processes)" language="json" /> | ||||
|     </div> | ||||
|  | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { NCode } from "naive-ui" | ||||
| import hljs from "highlight.js/lib/core" | ||||
| import json from "highlight.js/lib/languages/json" | ||||
|  | ||||
| hljs.registerLanguage("json", json) | ||||
|  | ||||
| const props = defineProps<{ rules: any[], upstreams: any[], processes: any[] }>() | ||||
|  | ||||
| function parseData(data: any): string { | ||||
|   return JSON.stringify(data, null, 1) | ||||
|     .replace(/  +/g, " ") | ||||
|     .replace(/\n/g, "") | ||||
| } | ||||
| </script> | ||||
							
								
								
									
										76
									
								
								pkg/sideload/view/src/components/data/sites-table.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								pkg/sideload/view/src/components/data/sites-table.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | ||||
| <template> | ||||
|   <div> | ||||
|     <n-card title="Sites"> | ||||
|       <template #header-extra> | ||||
|         <sites-table-add @reload="readSites()" /> | ||||
|       </template> | ||||
|  | ||||
|       <n-data-table | ||||
|         :columns="columns" | ||||
|         :data="data" | ||||
|         :row-key="(row: any) => row.id" | ||||
|       /> | ||||
|     </n-card> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { NCard, NDataTable, NTag } from "naive-ui" | ||||
| import { h, ref } from "vue" | ||||
| import SitesTableExpand from "@/components/data/sites-table-expand.vue" | ||||
| import SitesTableAction from "@/components/data/sites-table-action.vue" | ||||
| import SitesTableAdd from "@/components/data/sites-table-add.vue" | ||||
|  | ||||
| const columns: any[] = [ | ||||
|   { | ||||
|     type: "expand", | ||||
|     renderExpand(row: any) { | ||||
|       return h(SitesTableExpand, { ...row, class: "pl-[38px]" }) | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     title: "ID", | ||||
|     key: "id", | ||||
|     render(row: any) { | ||||
|       return h(NTag, { type: "info", bordered: false, size: "small" }, row?.id) | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     title: "Rules", | ||||
|     key: "rules", | ||||
|     render(row: any) { | ||||
|       return row?.rules?.length ?? 0 | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     title: "Upstreams", | ||||
|     key: "upstreams", | ||||
|     render(row: any) { | ||||
|       return row?.upstreams?.length ?? 0 | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     title: "Processes", | ||||
|     key: "processes", | ||||
|     render(row: any) { | ||||
|       return row?.processes?.length ?? 0 | ||||
|     } | ||||
|   }, | ||||
|   { | ||||
|     title: "Actions", | ||||
|     key: "actions", | ||||
|     render(row: any) { | ||||
|       return h(SitesTableAction, { ...row, onReload: () => readSites() }) | ||||
|     } | ||||
|   } | ||||
| ] | ||||
|  | ||||
| const data = ref<any[]>([]) | ||||
|  | ||||
| async function readSites() { | ||||
|   const resp = await fetch("/cgi/sites") | ||||
|   data.value = await resp.json() | ||||
| } | ||||
|  | ||||
| readSites() | ||||
| </script> | ||||
							
								
								
									
										60
									
								
								pkg/sideload/view/src/layouts/main.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								pkg/sideload/view/src/layouts/main.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| <template> | ||||
|   <n-layout> | ||||
|     <n-layout-header class="header py-[8px] px-[36px]" bordered> | ||||
|       <div class="flex items-center gap-2"> | ||||
|         <router-link class="link" to="/"> | ||||
|           RoadSign<i>!</i> | ||||
|         </router-link> | ||||
|       </div> | ||||
|  | ||||
|       <div class="nav-menu"> | ||||
|         <div class="h-full flex items-center header-nav"> | ||||
|           <n-menu v-model:value="key" :options="options" mode="horizontal" /> | ||||
|         </div> | ||||
|       </div> | ||||
|     </n-layout-header> | ||||
|     <n-layout-content class="h-[calc(100vh-70px)] container mx-auto" content-style="padding: 24px"> | ||||
|       <router-view /> | ||||
|     </n-layout-content> | ||||
|   </n-layout> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { type MenuOption, NIcon, NLayout, NLayoutContent, NLayoutHeader, NMenu } from "naive-ui" | ||||
| import { type Component, h, ref } from "vue" | ||||
| import { Dashboard } from "@vicons/carbon" | ||||
| import { RouterLink, useRoute, useRouter } from "vue-router" | ||||
|  | ||||
| const route = useRoute() | ||||
| const router = useRouter() | ||||
| const key = ref(route.name?.toString()) | ||||
|  | ||||
| router.afterEach((to) => { | ||||
|   key.value = to.name?.toString() ?? "index" | ||||
| }) | ||||
|  | ||||
| const options: MenuOption[] = [ | ||||
|   { | ||||
|     label: () => h(RouterLink, { to: { name: "dashboard" } }, "Dashboard"), | ||||
|     icon: renderIcon(Dashboard), | ||||
|     key: "dashboard" | ||||
|   } | ||||
| ] | ||||
|  | ||||
| function renderIcon(icon: Component) { | ||||
|   return () => h(NIcon, null, { default: () => h(icon) }) | ||||
| } | ||||
| </script> | ||||
|  | ||||
| <style scoped> | ||||
| .header { | ||||
|   display: grid; | ||||
|   grid-template-columns: 1fr auto 1fr; | ||||
|   gap: 40px; | ||||
| } | ||||
|  | ||||
| .link { | ||||
|   all: unset; | ||||
|   cursor: pointer; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										16
									
								
								pkg/sideload/view/src/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								pkg/sideload/view/src/main.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| import "./assets/main.css" | ||||
|  | ||||
| import "virtual:uno.css" | ||||
|  | ||||
| import { createApp } from "vue" | ||||
| import { createPinia } from "pinia" | ||||
|  | ||||
| import root from "./root.vue" | ||||
| import router from "./router" | ||||
|  | ||||
| const app = createApp(root) | ||||
|  | ||||
| app.use(createPinia()) | ||||
| app.use(router) | ||||
|  | ||||
| app.mount("#app") | ||||
							
								
								
									
										9
									
								
								pkg/sideload/view/src/root.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								pkg/sideload/view/src/root.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| <template> | ||||
|   <n-message-provider> | ||||
|     <router-view /> | ||||
|   </n-message-provider> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { NMessageProvider } from "naive-ui" | ||||
| </script> | ||||
							
								
								
									
										21
									
								
								pkg/sideload/view/src/router/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								pkg/sideload/view/src/router/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| import { createRouter, createWebHistory } from "vue-router" | ||||
|  | ||||
| const router = createRouter({ | ||||
|   history: createWebHistory(import.meta.env.BASE_URL), | ||||
|   routes: [ | ||||
|     { | ||||
|       path: "/", | ||||
|       name: "layouts.main", | ||||
|       component: () => import("@/layouts/main.vue"), | ||||
|       children: [ | ||||
|         { | ||||
|           path: "/", | ||||
|           name: "dashboard", | ||||
|           component: () => import("@/views/dashboard.vue") | ||||
|         }, | ||||
|       ] | ||||
|     }, | ||||
|   ] | ||||
| }) | ||||
|  | ||||
| export default router | ||||
							
								
								
									
										35
									
								
								pkg/sideload/view/src/views/dashboard.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								pkg/sideload/view/src/views/dashboard.vue
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| <template> | ||||
|   <div class="flex flex-col gap-2"> | ||||
|     <div class="grid gap-2 grid-cols-2 lg:grid-cols-4"> | ||||
|       <n-card embedded> | ||||
|         <n-statistic label="Status">{{ data?.status ? "Operational" : "Incident" }}</n-statistic> | ||||
|       </n-card> | ||||
|       <n-card embedded> | ||||
|         <n-statistic label="Sites">{{ data?.sites }}</n-statistic> | ||||
|       </n-card> | ||||
|       <n-card embedded> | ||||
|         <n-statistic label="Upstreams">{{ data?.upstreams }}</n-statistic> | ||||
|       </n-card> | ||||
|       <n-card embedded> | ||||
|         <n-statistic label="Processes">{{ data?.processes }}</n-statistic> | ||||
|       </n-card> | ||||
|     </div> | ||||
|  | ||||
|     <sites-table /> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
| <script setup lang="ts"> | ||||
| import { NCard, NStatistic } from "naive-ui" | ||||
| import { ref } from "vue" | ||||
| import SitesTable from "@/components/data/sites-table.vue" | ||||
|  | ||||
| const data = ref<any>({}) | ||||
|  | ||||
| async function readStatistics() { | ||||
|   const resp = await fetch("/cgi/statistics") | ||||
|   data.value = await resp.json() | ||||
| } | ||||
|  | ||||
| readStatistics() | ||||
| </script> | ||||
							
								
								
									
										13
									
								
								pkg/sideload/view/tsconfig.app.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								pkg/sideload/view/tsconfig.app.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | ||||
| { | ||||
|   "extends": "@vue/tsconfig/tsconfig.dom.json", | ||||
|   "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], | ||||
|   "exclude": ["src/**/__tests__/*"], | ||||
|   "compilerOptions": { | ||||
|     "composite": true, | ||||
|     "noEmit": true, | ||||
|     "baseUrl": ".", | ||||
|     "paths": { | ||||
|       "@/*": ["./src/*"] | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										11
									
								
								pkg/sideload/view/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								pkg/sideload/view/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | ||||
| { | ||||
|   "files": [], | ||||
|   "references": [ | ||||
|     { | ||||
|       "path": "./tsconfig.node.json" | ||||
|     }, | ||||
|     { | ||||
|       "path": "./tsconfig.app.json" | ||||
|     } | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										17
									
								
								pkg/sideload/view/tsconfig.node.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								pkg/sideload/view/tsconfig.node.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| { | ||||
|   "extends": "@tsconfig/node18/tsconfig.json", | ||||
|   "include": [ | ||||
|     "vite.config.*", | ||||
|     "vitest.config.*", | ||||
|     "cypress.config.*", | ||||
|     "nightwatch.conf.*", | ||||
|     "playwright.config.*" | ||||
|   ], | ||||
|   "compilerOptions": { | ||||
|     "composite": true, | ||||
|     "noEmit": true, | ||||
|     "module": "ESNext", | ||||
|     "moduleResolution": "Bundler", | ||||
|     "types": ["node"] | ||||
|   } | ||||
| } | ||||
							
								
								
									
										5
									
								
								pkg/sideload/view/unocss.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								pkg/sideload/view/unocss.config.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| import { defineConfig, presetUno } from "unocss" | ||||
|  | ||||
| export default defineConfig({ | ||||
|   presets: [presetUno({ preflight: false })] | ||||
| }) | ||||
							
								
								
									
										24
									
								
								pkg/sideload/view/vite.config.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								pkg/sideload/view/vite.config.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,24 @@ | ||||
| import { fileURLToPath, URL } from "node:url" | ||||
|  | ||||
| import { defineConfig } from "vite" | ||||
| import vue from "@vitejs/plugin-vue" | ||||
| import unocss from "unocss/vite" | ||||
|  | ||||
| // https://vitejs.dev/config/ | ||||
| export default defineConfig({ | ||||
|   plugins: [ | ||||
|     vue(), | ||||
|     unocss() | ||||
|   ], | ||||
|   resolve: { | ||||
|     alias: { | ||||
|       "@": fileURLToPath(new URL("./src", import.meta.url)) | ||||
|     } | ||||
|   }, | ||||
|   server: { | ||||
|     proxy: { | ||||
|       "/webhooks": "http://127.0.0.1:81", | ||||
|       "/cgi": "http://127.0.0.1:81" | ||||
|     } | ||||
|   } | ||||
| }) | ||||
							
								
								
									
										2904
									
								
								pkg/sideload/view/yarn.lock
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2904
									
								
								pkg/sideload/view/yarn.lock
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -2,12 +2,25 @@ package sign | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"github.com/samber/lo" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| 	"path/filepath" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| ) | ||||
|  | ||||
| type ProcessConfig struct { | ||||
| type ProcessStatus = int8 | ||||
|  | ||||
| const ( | ||||
| 	ProcessCreated = ProcessStatus(iota) | ||||
| 	ProcessStarting | ||||
| 	ProcessStarted | ||||
| 	ProcessExited | ||||
| 	ProcessFailure | ||||
| ) | ||||
|  | ||||
| type ProcessInstance struct { | ||||
| 	ID          string     `json:"id" yaml:"id"` | ||||
| 	Workdir     string     `json:"workdir" yaml:"workdir"` | ||||
| 	Command     []string   `json:"command" yaml:"command"` | ||||
| @@ -15,10 +28,13 @@ type ProcessConfig struct { | ||||
| 	Prepares    [][]string `json:"prepares" yaml:"prepares"` | ||||
| 	Preheat     bool       `json:"preheat" yaml:"preheat"` | ||||
|  | ||||
| 	Cmd *exec.Cmd `json:"-"` | ||||
| 	Cmd    *exec.Cmd       `json:"-"` | ||||
| 	Logger strings.Builder `json:"-"` | ||||
|  | ||||
| 	Status ProcessStatus `json:"status"` | ||||
| } | ||||
|  | ||||
| func (v *ProcessConfig) BootProcess() error { | ||||
| func (v *ProcessInstance) BootProcess() error { | ||||
| 	if v.Cmd != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| @@ -43,7 +59,7 @@ func (v *ProcessConfig) BootProcess() error { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (v *ProcessConfig) PrepareProcess() error { | ||||
| func (v *ProcessInstance) PrepareProcess() error { | ||||
| 	for _, script := range v.Prepares { | ||||
| 		if len(script) <= 0 { | ||||
| 			continue | ||||
| @@ -57,7 +73,7 @@ func (v *ProcessConfig) PrepareProcess() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (v *ProcessConfig) StartProcess() error { | ||||
| func (v *ProcessInstance) StartProcess() error { | ||||
| 	if len(v.Command) <= 0 { | ||||
| 		return fmt.Errorf("you need set the command for %s to enable process manager", v.ID) | ||||
| 	} | ||||
| @@ -65,11 +81,28 @@ func (v *ProcessConfig) StartProcess() error { | ||||
| 	v.Cmd = exec.Command(v.Command[0], v.Command[1:]...) | ||||
| 	v.Cmd.Dir = filepath.Join(v.Workdir) | ||||
| 	v.Cmd.Env = append(v.Cmd.Env, v.Environment...) | ||||
| 	v.Cmd.Stdout = &v.Logger | ||||
| 	v.Cmd.Stderr = &v.Logger | ||||
|  | ||||
| 	// Monitor | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			if v.Cmd.Process == nil || v.Cmd.ProcessState == nil { | ||||
| 				v.Status = ProcessStarting | ||||
| 			} else if !v.Cmd.ProcessState.Exited() { | ||||
| 				v.Status = ProcessStarted | ||||
| 			} else { | ||||
| 				v.Status = lo.Ternary(v.Cmd.ProcessState.Success(), ProcessExited, ProcessFailure) | ||||
| 				return | ||||
| 			} | ||||
| 			time.Sleep(100 * time.Millisecond) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	return v.Cmd.Start() | ||||
| } | ||||
|  | ||||
| func (v *ProcessConfig) StopProcess() error { | ||||
| func (v *ProcessInstance) StopProcess() error { | ||||
| 	if v.Cmd != nil && v.Cmd.Process != nil { | ||||
| 		if err := v.Cmd.Process.Signal(os.Interrupt); err != nil { | ||||
| 			v.Cmd.Process.Kill() | ||||
| @@ -82,8 +115,12 @@ func (v *ProcessConfig) StopProcess() error { | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| func (v *ProcessInstance) GetLogs() string { | ||||
| 	return v.Logger.String() | ||||
| } | ||||
|  | ||||
| func (v *RoadApp) PreheatProcesses(callbacks ...func(total int, success int)) { | ||||
| 	var processes []*ProcessConfig | ||||
| 	var processes []*ProcessInstance | ||||
| 	for _, site := range v.Sites { | ||||
| 		for _, process := range site.Processes { | ||||
| 			if process.Preheat { | ||||
|   | ||||
| @@ -18,7 +18,7 @@ import ( | ||||
| 	"github.com/valyala/fasthttp" | ||||
| ) | ||||
|  | ||||
| func makeHypertextResponse(c *fiber.Ctx, upstream *UpstreamConfig) error { | ||||
| func makeHypertextResponse(c *fiber.Ctx, upstream *UpstreamInstance) error { | ||||
| 	timeout := time.Duration(viper.GetInt64("performance.network_timeout")) * time.Millisecond | ||||
| 	return proxy.Do(c, upstream.MakeURI(c), &fasthttp.Client{ | ||||
| 		ReadTimeout:  timeout, | ||||
| @@ -26,7 +26,7 @@ func makeHypertextResponse(c *fiber.Ctx, upstream *UpstreamConfig) error { | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func makeFileResponse(c *fiber.Ctx, upstream *UpstreamConfig) error { | ||||
| func makeFileResponse(c *fiber.Ctx, upstream *UpstreamInstance) error { | ||||
| 	uri, queries := upstream.GetRawURI() | ||||
| 	root := http.Dir(uri) | ||||
|  | ||||
|   | ||||
| @@ -44,13 +44,13 @@ type RequestTransformerConfig = transformers.RequestTransformerConfig | ||||
|  | ||||
| type SiteConfig struct { | ||||
| 	ID           string                      `json:"id"` | ||||
| 	Rules        []*RouterRuleConfig         `json:"rules" yaml:"rules"` | ||||
| 	Rules        []*RouterRule               `json:"rules" yaml:"rules"` | ||||
| 	Transformers []*RequestTransformerConfig `json:"transformers" yaml:"transformers"` | ||||
| 	Upstreams    []*UpstreamConfig           `json:"upstreams" yaml:"upstreams"` | ||||
| 	Processes    []*ProcessConfig            `json:"processes" yaml:"processes"` | ||||
| 	Upstreams    []*UpstreamInstance         `json:"upstreams" yaml:"upstreams"` | ||||
| 	Processes    []*ProcessInstance          `json:"processes" yaml:"processes"` | ||||
| } | ||||
|  | ||||
| type RouterRuleConfig struct { | ||||
| type RouterRule struct { | ||||
| 	Host    []string            `json:"host" yaml:"host"` | ||||
| 	Path    []string            `json:"path" yaml:"path"` | ||||
| 	Queries map[string]string   `json:"queries" yaml:"queries"` | ||||
|   | ||||
| @@ -15,12 +15,12 @@ const ( | ||||
| 	UpstreamTypeUnknown   = "unknown" | ||||
| ) | ||||
|  | ||||
| type UpstreamConfig struct { | ||||
| type UpstreamInstance struct { | ||||
| 	ID  string `json:"id" yaml:"id"` | ||||
| 	URI string `json:"uri" yaml:"uri"` | ||||
| } | ||||
|  | ||||
| func (v *UpstreamConfig) GetType() string { | ||||
| func (v *UpstreamInstance) GetType() string { | ||||
| 	protocol := strings.SplitN(v.URI, "://", 2)[0] | ||||
| 	switch protocol { | ||||
| 	case "file", "files": | ||||
| @@ -32,7 +32,7 @@ func (v *UpstreamConfig) GetType() string { | ||||
| 	return UpstreamTypeUnknown | ||||
| } | ||||
|  | ||||
| func (v *UpstreamConfig) GetRawURI() (string, url.Values) { | ||||
| func (v *UpstreamInstance) GetRawURI() (string, url.Values) { | ||||
| 	uri := strings.SplitN(v.URI, "://", 2)[1] | ||||
| 	data := strings.SplitN(uri, "?", 2) | ||||
| 	data = append(data, " ") // Make data array least have two element | ||||
| @@ -41,7 +41,7 @@ func (v *UpstreamConfig) GetRawURI() (string, url.Values) { | ||||
| 	return data[0], qs | ||||
| } | ||||
|  | ||||
| func (v *UpstreamConfig) MakeURI(ctx *fiber.Ctx) string { | ||||
| func (v *UpstreamInstance) MakeURI(ctx *fiber.Ctx) string { | ||||
| 	var queries []string | ||||
| 	for k, v := range ctx.Queries() { | ||||
| 		parsed, _ := url.QueryUnescape(v) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user