You've already forked nginx-proxy-manager
							
							
				mirror of
				https://github.com/NginxProxyManager/nginx-proxy-manager.git
				synced 2025-10-30 18:05:34 +03:00 
			
		
		
		
	React
This commit is contained in:
		| @@ -1,17 +0,0 @@ | ||||
| { | ||||
| 	"presets": [ | ||||
| 		[ | ||||
| 			"env", | ||||
| 			{ | ||||
| 				"targets": { | ||||
| 					"browsers": [ | ||||
| 						"Chrome >= 65" | ||||
| 					] | ||||
| 				}, | ||||
| 				"debug": false, | ||||
| 				"modules": false, | ||||
| 				"useBuiltIns": "usage" | ||||
| 			} | ||||
| 		] | ||||
| 	] | ||||
| } | ||||
							
								
								
									
										24
									
								
								frontend/.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								frontend/.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,4 +1,22 @@ | ||||
| dist | ||||
| # Logs | ||||
| logs | ||||
| *.log | ||||
| npm-debug.log* | ||||
| yarn-debug.log* | ||||
| yarn-error.log* | ||||
| pnpm-debug.log* | ||||
| lerna-debug.log* | ||||
|  | ||||
| node_modules | ||||
| webpack_stats.html | ||||
| yarn-error.log | ||||
| dist | ||||
| dist-ssr | ||||
| *.local | ||||
|  | ||||
| # Editor directories and files | ||||
| .idea | ||||
| .DS_Store | ||||
| *.suo | ||||
| *.ntvs* | ||||
| *.njsproj | ||||
| *.sln | ||||
| *.sw? | ||||
|   | ||||
							
								
								
									
										91
									
								
								frontend/biome.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								frontend/biome.json
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,91 @@ | ||||
| { | ||||
|     "$schema": "https://biomejs.dev/schemas/2.2.2/schema.json", | ||||
|     "vcs": { | ||||
|         "enabled": true, | ||||
|         "clientKind": "git", | ||||
|         "useIgnoreFile": true | ||||
|     }, | ||||
|     "files": { | ||||
|         "ignoreUnknown": false, | ||||
|         "includes": [ | ||||
|             "**/*.ts", | ||||
|             "**/*.tsx", | ||||
|             "**/*.js", | ||||
|             "**/*.jsx", | ||||
|             "!**/dist/**/*" | ||||
|         ] | ||||
|     }, | ||||
|     "formatter": { | ||||
|         "enabled": true, | ||||
|         "indentStyle": "tab", | ||||
|         "indentWidth": 4, | ||||
|         "lineWidth": 120, | ||||
|         "formatWithErrors": true | ||||
|     }, | ||||
|     "assist": { | ||||
|         "actions": { | ||||
|             "source": { | ||||
|                 "organizeImports": { | ||||
|                     "level": "on", | ||||
|                     "options": { | ||||
|                         "groups": [ | ||||
|                             ":BUN:", | ||||
|                             ":NODE:", | ||||
|                             [ | ||||
|                                 "npm:*", | ||||
|                                 "npm:*/**" | ||||
|                             ], | ||||
|                             ":PACKAGE_WITH_PROTOCOL:", | ||||
|                             ":URL:", | ||||
|                             ":PACKAGE:", | ||||
|                             [ | ||||
|                                 "/src/*", | ||||
|                                 "/src/**" | ||||
|                             ], | ||||
|                             [ | ||||
|                                 "/**" | ||||
|                             ], | ||||
|                             [ | ||||
|                                 "#*", | ||||
|                                 "#*/**" | ||||
|                             ], | ||||
|                             ":PATH:" | ||||
|                         ] | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|     "linter": { | ||||
|         "enabled": true, | ||||
|         "rules": { | ||||
|             "recommended": true, | ||||
|             "correctness": { | ||||
|                 "useUniqueElementIds": "off" | ||||
|             }, | ||||
|             "suspicious": { | ||||
|                 "noExplicitAny": "off" | ||||
|             }, | ||||
|             "performance": { | ||||
|                 "noDelete": "off" | ||||
|             }, | ||||
|             "nursery": "off", | ||||
|             "a11y": { | ||||
|                 "useSemanticElements": "off", | ||||
|                 "useValidAnchor": "off" | ||||
|             }, | ||||
|             "style": { | ||||
|                 "noParameterAssign": "error", | ||||
|                 "useAsConstAssertion": "error", | ||||
|                 "useDefaultParameterLast": "error", | ||||
|                 "useEnumInitializers": "error", | ||||
|                 "useSelfClosingElements": "error", | ||||
|                 "useSingleVarDeclarator": "error", | ||||
|                 "noUnusedTemplateLiteral": "error", | ||||
|                 "useNumberNamespace": "error", | ||||
|                 "noInferrableTypes": "error", | ||||
|                 "noUselessElse": "error" | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										172
									
								
								frontend/check-locales.cjs
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										172
									
								
								frontend/check-locales.cjs
									
									
									
									
									
										Executable file
									
								
							| @@ -0,0 +1,172 @@ | ||||
| #!/usr/bin/env node | ||||
|  | ||||
| // This file does a few things to ensure that the Locales are present and valid: | ||||
| // - Ensures that the name of the locale exists in the language list | ||||
| // - Ensures that each locale contains the translations used in the application | ||||
| // - Ensures that there are no unused translations in the locale files | ||||
| // - Also checks the error messages returned by the backend | ||||
|  | ||||
| const allLocales = [ | ||||
| 	["en", "en-US"], | ||||
| 	["de", "de-DE"], | ||||
| 	["fa", "fa-IR"], | ||||
| ]; | ||||
|  | ||||
| const ignoreUnused = [ | ||||
| 	/^capability\..*$/, | ||||
| 	/^status\..*$/, | ||||
| 	/^type\..*$/, | ||||
| ]; | ||||
|  | ||||
| const { spawnSync } = require("child_process"); | ||||
| const fs = require("fs"); | ||||
|  | ||||
| const tmp = require("tmp"); | ||||
|  | ||||
| // Parse backend errors | ||||
| const BACKEND_ERRORS_FILE = "../backend/internal/errors/errors.go"; | ||||
| const BACKEND_ERRORS = []; | ||||
| /* | ||||
| try { | ||||
| 	const backendErrorsContent = fs.readFileSync(BACKEND_ERRORS_FILE, "utf8"); | ||||
| 	const backendErrorsContentRes = [ | ||||
| 		...backendErrorsContent.matchAll(/(?:errors|eris)\.New\("([^"]+)"\)/g), | ||||
| 	]; | ||||
| 	backendErrorsContentRes.map((item) => { | ||||
| 		BACKEND_ERRORS.push("error." + item[1]); | ||||
| 		return null; | ||||
| 	}); | ||||
| } catch (err) { | ||||
| 	console.log("\x1b[31m%s\x1b[0m", err); | ||||
| 	process.exit(1); | ||||
| } | ||||
| */ | ||||
|  | ||||
| // get all translations used in frontend code | ||||
| const tmpobj = tmp.fileSync({ postfix: ".json" }); | ||||
| spawnSync("yarn", ["locale-extract", "--out-file", tmpobj.name]); | ||||
|  | ||||
| const allLocalesInProject = require(tmpobj.name); | ||||
|  | ||||
| // get list og language names and locales | ||||
| const langList = require("./src/locale/src/lang-list.json"); | ||||
|  | ||||
| // store a list of all validation errors | ||||
| const allErrors = []; | ||||
| const allWarnings = []; | ||||
| const allKeys = []; | ||||
|  | ||||
| const checkLangList = (fullCode) => { | ||||
| 	const key = "locale-" + fullCode; | ||||
| 	if (typeof langList[key] === "undefined") { | ||||
| 		allErrors.push( | ||||
| 			"ERROR: `" + key + "` language does not exist in lang-list.json", | ||||
| 		); | ||||
| 	} | ||||
| }; | ||||
|  | ||||
| const compareLocale = (locale) => { | ||||
| 	const projectLocaleKeys = Object.keys(allLocalesInProject); | ||||
| 	// Check that locale contains the items used in the codebase | ||||
| 	projectLocaleKeys.map((key) => { | ||||
| 		if (typeof locale.data[key] === "undefined") { | ||||
| 			allErrors.push( | ||||
| 				"ERROR: `" + locale[0] + "` does not contain item: `" + key + "`", | ||||
| 			); | ||||
| 		} | ||||
| 		return null; | ||||
| 	}); | ||||
| 	// Check that locale contains all error.* items | ||||
| 	BACKEND_ERRORS.forEach((key) => { | ||||
| 		if (typeof locale.data[key] === "undefined") { | ||||
| 			allErrors.push( | ||||
| 				"ERROR: `" + locale[0] + "` does not contain item: `" + key + "`", | ||||
| 			); | ||||
| 		} | ||||
| 		return null; | ||||
| 	}); | ||||
|  | ||||
| 	// Check that locale does not contain items not used in the codebase | ||||
| 	const localeKeys = Object.keys(locale.data); | ||||
| 	localeKeys.map((key) => { | ||||
| 		let ignored = false; | ||||
| 		ignoreUnused.map((regex) => { | ||||
| 			if (key.match(regex)) { | ||||
| 				ignored = true; | ||||
| 			} | ||||
| 			return null; | ||||
| 		}); | ||||
|  | ||||
| 		if (!ignored && typeof allLocalesInProject[key] === "undefined") { | ||||
| 			// ensure this key doesn't exist in the backend errors either | ||||
| 			if (!BACKEND_ERRORS.includes(key)) { | ||||
| 				allErrors.push( | ||||
| 					"ERROR: `" + locale[0] + "` contains unused item: `" + key + "`", | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Add this key to allKeys | ||||
| 		if (allKeys.indexOf(key) === -1) { | ||||
| 			allKeys.push(key); | ||||
| 		} | ||||
| 		return null; | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| // Checks for any keys missing from this locale, that | ||||
| // have been defined in any other locales | ||||
| const checkForMissing = (locale) => { | ||||
| 	allKeys.forEach((key) => { | ||||
| 		if (typeof locale.data[key] === "undefined") { | ||||
| 			let ignored = false; | ||||
| 			ignoreMissing.map((regex) => { | ||||
| 				if (key.match(regex)) { | ||||
| 					ignored = true; | ||||
| 				} | ||||
| 				return null; | ||||
| 			}); | ||||
|  | ||||
| 			if (!ignored) { | ||||
| 				allWarnings.push( | ||||
| 					"WARN: `" + locale[0] + "` does not contain item: `" + key + "`", | ||||
| 				); | ||||
| 			} | ||||
| 		} | ||||
| 		return null; | ||||
| 	}); | ||||
| }; | ||||
|  | ||||
| // Local all locale data | ||||
| allLocales.map((locale, idx) => { | ||||
| 	checkLangList(locale[1]); | ||||
| 	allLocales[idx].data = require("./src/locale/src/" + locale[0] + ".json"); | ||||
| 	return null; | ||||
| }); | ||||
|  | ||||
| // Verify all locale data | ||||
| allLocales.map((locale) => { | ||||
| 	compareLocale(locale); | ||||
| 	checkForMissing(locale); | ||||
| 	return null; | ||||
| }); | ||||
|  | ||||
| if (allErrors.length) { | ||||
| 	allErrors.map((err) => { | ||||
| 		console.log("\x1b[31m%s\x1b[0m", err); | ||||
| 		return null; | ||||
| 	}); | ||||
| } | ||||
| if (allWarnings.length) { | ||||
| 	allWarnings.map((err) => { | ||||
| 		console.log("\x1b[33m%s\x1b[0m", err); | ||||
| 		return null; | ||||
| 	}); | ||||
| } | ||||
|  | ||||
| if (allErrors.length) { | ||||
| 	process.exit(1); | ||||
| } | ||||
|  | ||||
| console.log("\x1b[32m%s\x1b[0m", "Locale check passed"); | ||||
| process.exit(0); | ||||
| @@ -1 +0,0 @@ | ||||
| ../node_modules/tabler-ui/dist/assets/fonts/feather | ||||
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											Binary file not shown.
										
									
								
							| @@ -1,9 +0,0 @@ | ||||
| <% var title = 'Nginx Proxy Manager' %> | ||||
| <%- include partials/header.ejs %> | ||||
|  | ||||
| <div id="app" class="page"> | ||||
| 	<span class="loader"></span> | ||||
| </div> | ||||
|  | ||||
| <script type="text/javascript" src="/js/main.bundle.js?v=<%= version %>"></script> | ||||
| <%- include partials/footer.ejs %> | ||||
| @@ -1,9 +0,0 @@ | ||||
| <% var title = 'Login – Nginx Proxy Manager' %> | ||||
| <%- include partials/header.ejs %> | ||||
|  | ||||
| <div class="page" id="login" data-version="<%= version %>"> | ||||
| 	<span class="loader"></span> | ||||
| </div> | ||||
|  | ||||
| <script type="text/javascript" src="/js/login.bundle.js?v=<%= version %>"></script> | ||||
| <%- include partials/footer.ejs %> | ||||
| @@ -1,2 +0,0 @@ | ||||
| 	</body> | ||||
| </html> | ||||
| @@ -1,34 +0,0 @@ | ||||
| <!doctype html> | ||||
| <html lang="en" dir="ltr"> | ||||
| 	<head> | ||||
| 		<meta charset="utf-8"> | ||||
| 		<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> | ||||
| 		<meta http-equiv="X-UA-Compatible" content="ie=edge"> | ||||
| 		<meta http-equiv="Content-Language" content="en"> | ||||
| 		<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> | ||||
| 		<meta name="apple-mobile-web-app-capable" content="yes"> | ||||
| 		<meta name="mobile-web-app-capable" content="yes"> | ||||
| 		<meta name="HandheldFriendly" content="True"> | ||||
| 		<meta name="MobileOptimized" content="320"> | ||||
| 		<meta name="robots" content="noindex"> | ||||
| 		<title><%- title %></title> | ||||
| 		<link rel="apple-touch-icon" sizes="180x180" href="/images/favicons/apple-touch-icon.png"> | ||||
| 		<link rel="icon" type="image/png" sizes="32x32" href="/images/favicons/favicon-32x32.png"> | ||||
| 		<link rel="icon" type="image/png" sizes="16x16" href="/images/favicons/favicon-16x16.png"> | ||||
| 		<link rel="manifest" href="/images/favicons/site.webmanifest"> | ||||
| 		<link rel="mask-icon" href="/images/favicons/safari-pinned-tab.svg" color="#5bbad5"> | ||||
| 		<link rel="shortcut icon" href="/images/favicons/favicon.ico"> | ||||
| 		<meta name="msapplication-TileColor" content="#333333"> | ||||
| 		<meta name="msapplication-config" content="/images/favicons/browserconfig.xml"> | ||||
| 		<meta name="theme-color" content="#ffffff"> | ||||
| 		<link href="/css/main.css?v=<%= version %>" rel="stylesheet"> | ||||
| 	</head> | ||||
| 	<body> | ||||
|  | ||||
| 	<noscript> | ||||
| 		<div class="container no-js-warning"> | ||||
| 			<div class="alert alert-warning text-center"> | ||||
| 				<strong>Warning!</strong> This application requires Javascript and your browser doesn't support it. | ||||
| 			</div> | ||||
| 		</div> | ||||
| 	</noscript> | ||||
| @@ -1 +0,0 @@ | ||||
| ./node_modules/tabler-ui/dist/assets/images | ||||
							
								
								
									
										44
									
								
								frontend/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								frontend/index.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang="en"> | ||||
| 	<head> | ||||
| 		<meta charset="utf-8" /> | ||||
| 		<meta name="viewport" content="width=device-width, initial-scale=1" /> | ||||
| 		<title>Nginx Proxy Manager</title> | ||||
| 		<meta name="description" content="In The Office Planner" /> | ||||
| 		<link | ||||
| 			rel="apple-touch-icon" | ||||
| 			sizes="180x180" | ||||
| 			href="/images/favicon/apple-touch-icon.png" /> | ||||
| 		<link | ||||
| 			rel="icon" | ||||
| 			type="image/png" | ||||
| 			sizes="32x32" | ||||
| 			href="/images/favicon/favicon-32x32.png" /> | ||||
| 		<link | ||||
| 			rel="icon" | ||||
| 			type="image/png" | ||||
| 			sizes="16x16" | ||||
| 			href="/images/favicon/favicon-16x16.png" /> | ||||
| 		<link rel="manifest" href="/images/favicon/site.webmanifest" /> | ||||
| 		<link | ||||
| 			rel="mask-icon" | ||||
| 			href="/images/favicon/safari-pinned-tab.svg" | ||||
| 			color="#5bbad5" /> | ||||
| 		<link rel="shortcut icon" href="/images/favicon/favicon.ico" /> | ||||
| 		<meta name="msapplication-TileColor" content="#4a4a4a" /> | ||||
| 		<meta | ||||
| 			name="msapplication-config" | ||||
| 			content="/images/favicon/browserconfig.xml" /> | ||||
| 		<meta name="theme-color" content="#ffffff" /> | ||||
| 	</head> | ||||
| 	<body> | ||||
| 		<noscript>You need to enable JavaScript to run this app.</noscript> | ||||
| 		<div id="root"></div> | ||||
| 		<script type="module" src="/src/main.tsx"></script> | ||||
| 		<script> | ||||
| 			if (global === undefined) { | ||||
| 				var global = window; | ||||
| 			} | ||||
| 		</script> | ||||
| 	</body> | ||||
| </html> | ||||
| @@ -1,757 +0,0 @@ | ||||
| const $      = require('jquery'); | ||||
| const _      = require('underscore'); | ||||
| const Tokens = require('./tokens'); | ||||
|  | ||||
| /** | ||||
|  * @param {String}  message | ||||
|  * @param {*}       debug | ||||
|  * @param {Number} code | ||||
|  * @constructor | ||||
|  */ | ||||
| const ApiError = function (message, debug, code) { | ||||
|     let temp     = Error.call(this, message); | ||||
|     temp.name    = this.name = 'ApiError'; | ||||
|     this.stack   = temp.stack; | ||||
|     this.message = temp.message; | ||||
|     this.debug   = debug; | ||||
|     this.code    = code; | ||||
| }; | ||||
|  | ||||
| ApiError.prototype = Object.create(Error.prototype, { | ||||
|     constructor: { | ||||
|         value:        ApiError, | ||||
|         writable:     true, | ||||
|         configurable: true | ||||
|     } | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * | ||||
|  * @param   {String} verb | ||||
|  * @param   {String} path | ||||
|  * @param   {Object} [data] | ||||
|  * @param   {Object} [options] | ||||
|  * @returns {Promise} | ||||
|  */ | ||||
| function fetch(verb, path, data, options) { | ||||
|     options = options || {}; | ||||
|  | ||||
|     return new Promise(function (resolve, reject) { | ||||
|         let api_url = '/api/'; | ||||
|         let url     = api_url + path; | ||||
|         let token   = Tokens.getTopToken(); | ||||
|  | ||||
|         if ((typeof options.contentType === 'undefined' || options.contentType.match(/json/im)) && typeof data === 'object') { | ||||
|             data = JSON.stringify(data); | ||||
|         } | ||||
|  | ||||
|         $.ajax({ | ||||
|             url:         url, | ||||
|             data:        typeof data === 'object' ? JSON.stringify(data) : data, | ||||
|             type:        verb, | ||||
|             dataType:    'json', | ||||
|             contentType: options.contentType || 'application/json; charset=UTF-8', | ||||
|             processData: options.processData || true, | ||||
|             crossDomain: true, | ||||
|             timeout:     options.timeout ? options.timeout : 180000, | ||||
|             xhrFields:   { | ||||
|                 withCredentials: true | ||||
|             }, | ||||
|  | ||||
|             beforeSend: function (xhr) { | ||||
|                 xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); | ||||
|             }, | ||||
|  | ||||
|             success: function (data, textStatus, response) { | ||||
|                 let total = response.getResponseHeader('X-Dataset-Total'); | ||||
|                 if (total !== null) { | ||||
|                     resolve({ | ||||
|                         data:       data, | ||||
|                         pagination: { | ||||
|                             total:  parseInt(total, 10), | ||||
|                             offset: parseInt(response.getResponseHeader('X-Dataset-Offset'), 10), | ||||
|                             limit:  parseInt(response.getResponseHeader('X-Dataset-Limit'), 10) | ||||
|                         } | ||||
|                     }); | ||||
|                 } else { | ||||
|                     resolve(response); | ||||
|                 } | ||||
|             }, | ||||
|  | ||||
|             error: function (xhr, status, error_thrown) { | ||||
|                 let code = 400; | ||||
|  | ||||
|                 if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') { | ||||
|                     error_thrown = xhr.responseJSON.error.message; | ||||
|                     code         = xhr.responseJSON.error.code || 500; | ||||
|                 } | ||||
|  | ||||
|                 reject(new ApiError(error_thrown, xhr.responseText, code)); | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * | ||||
|  * @param {Array} expand | ||||
|  * @returns {String} | ||||
|  */ | ||||
| function makeExpansionString(expand) { | ||||
|     let items = []; | ||||
|     _.forEach(expand, function (exp) { | ||||
|         items.push(encodeURIComponent(exp)); | ||||
|     }); | ||||
|  | ||||
|     return items.join(','); | ||||
| } | ||||
|  | ||||
| /** | ||||
|  * @param   {String}   path | ||||
|  * @param   {Array}    [expand] | ||||
|  * @param   {String}   [query] | ||||
|  * @returns {Promise} | ||||
|  */ | ||||
| function getAllObjects(path, expand, query) { | ||||
|     let params = []; | ||||
|  | ||||
|     if (typeof expand === 'object' && expand !== null && expand.length) { | ||||
|         params.push('expand=' + makeExpansionString(expand)); | ||||
|     } | ||||
|  | ||||
|     if (typeof query === 'string') { | ||||
|         params.push('query=' + query); | ||||
|     } | ||||
|  | ||||
|     return fetch('get', path + (params.length ? '?' + params.join('&') : '')); | ||||
| } | ||||
|  | ||||
| function FileUpload(path, fd) { | ||||
|     return new Promise((resolve, reject) => { | ||||
|         let xhr   = new XMLHttpRequest(); | ||||
|         let token = Tokens.getTopToken(); | ||||
|  | ||||
|         xhr.open('POST', '/api/' + path); | ||||
|         xhr.overrideMimeType('text/plain'); | ||||
|         xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); | ||||
|         xhr.send(fd); | ||||
|  | ||||
|         xhr.onreadystatechange = function () { | ||||
|             if (this.readyState === XMLHttpRequest.DONE) { | ||||
|                 if (xhr.status !== 200 && xhr.status !== 201) { | ||||
|                     try { | ||||
|                         reject(new Error('Upload failed: ' + JSON.parse(xhr.responseText).error.message)); | ||||
|                     } catch (err) { | ||||
|                         reject(new Error('Upload failed: ' + xhr.status)); | ||||
|                     }   | ||||
|                 } else { | ||||
|                     resolve(xhr.responseText); | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|     }); | ||||
| } | ||||
|  | ||||
| //ref : https://codepen.io/chrisdpratt/pen/RKxJNo | ||||
| function DownloadFile(verb, path, filename) { | ||||
|     return new Promise(function (resolve, reject) { | ||||
|         let api_url = '/api/'; | ||||
|         let url = api_url + path; | ||||
|         let token = Tokens.getTopToken(); | ||||
|  | ||||
|         $.ajax({ | ||||
|             url: url, | ||||
|             type: verb, | ||||
|             crossDomain: true, | ||||
|             xhrFields: { | ||||
|                 withCredentials: true, | ||||
|                 responseType: 'blob' | ||||
|             }, | ||||
|  | ||||
|             beforeSend: function (xhr) { | ||||
|                 xhr.setRequestHeader('Authorization', 'Bearer ' + (token ? token.t : null)); | ||||
|             }, | ||||
|  | ||||
|             success: function (data) { | ||||
|                 var a = document.createElement('a'); | ||||
|                 var url = window.URL.createObjectURL(data); | ||||
|                 a.href = url; | ||||
|                 a.download = filename; | ||||
|                 document.body.append(a); | ||||
|                 a.click(); | ||||
|                 a.remove(); | ||||
|                 window.URL.revokeObjectURL(url); | ||||
|             }, | ||||
|  | ||||
|             error: function (xhr, status, error_thrown) { | ||||
|                 let code = 400; | ||||
|  | ||||
|                 if (typeof xhr.responseJSON !== 'undefined' && typeof xhr.responseJSON.error !== 'undefined' && typeof xhr.responseJSON.error.message !== 'undefined') { | ||||
|                     error_thrown = xhr.responseJSON.error.message; | ||||
|                     code = xhr.responseJSON.error.code || 500; | ||||
|                 } | ||||
|  | ||||
|                 reject(new ApiError(error_thrown, xhr.responseText, code)); | ||||
|             } | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
|  | ||||
| module.exports = { | ||||
|     status: function () { | ||||
|         return fetch('get', ''); | ||||
|     }, | ||||
|  | ||||
|     Tokens: { | ||||
|  | ||||
|         /** | ||||
|          * @param   {String}  identity | ||||
|          * @param   {String}  secret | ||||
|          * @param   {Boolean} [wipe]       Will wipe the stack before adding to it again if login was successful | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         login: function (identity, secret, wipe) { | ||||
|             return fetch('post', 'tokens', {identity: identity, secret: secret}) | ||||
|                 .then(response => { | ||||
|                     if (response.token) { | ||||
|                         if (wipe) { | ||||
|                             Tokens.clearTokens(); | ||||
|                         } | ||||
|  | ||||
|                         // Set storage token | ||||
|                         Tokens.addToken(response.token); | ||||
|                         return response.token; | ||||
|                     } else { | ||||
|                         Tokens.clearTokens(); | ||||
|                         throw(new Error('No token returned')); | ||||
|                     } | ||||
|                 }); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         refresh: function () { | ||||
|             return fetch('get', 'tokens') | ||||
|                 .then(response => { | ||||
|                     if (response.token) { | ||||
|                         Tokens.setCurrentToken(response.token); | ||||
|                         return response.token; | ||||
|                     } else { | ||||
|                         Tokens.clearTokens(); | ||||
|                         throw(new Error('No token returned')); | ||||
|                     } | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     Users: { | ||||
|  | ||||
|         /** | ||||
|          * @param   {Number|String}  user_id | ||||
|          * @param   {Array}           [expand] | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         getById: function (user_id, expand) { | ||||
|             return fetch('get', 'users/' + user_id + (typeof expand === 'object' && expand.length ? '?expand=' + makeExpansionString(expand) : '')); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @param   {Array}    [expand] | ||||
|          * @param   {String}   [query] | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         getAll: function (expand, query) { | ||||
|             return getAllObjects('users', expand, query); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @param   {Object}  data | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         create: function (data) { | ||||
|             return fetch('post', 'users', data); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @param   {Object}   data | ||||
|          * @param   {Number}   data.id | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         update: function (data) { | ||||
|             let id = data.id; | ||||
|             delete data.id; | ||||
|             return fetch('put', 'users/' + id, data); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @param   {Number}  id | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         delete: function (id) { | ||||
|             return fetch('delete', 'users/' + id); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * | ||||
|          * @param   {Number}   id | ||||
|          * @param   {Object}   auth | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         setPassword: function (id, auth) { | ||||
|             return fetch('put', 'users/' + id + '/auth', auth); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @param   {Number}  id | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         loginAs: function (id) { | ||||
|             return fetch('post', 'users/' + id + '/login'); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * | ||||
|          * @param   {Number}   id | ||||
|          * @param   {Object}   perms | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         setPermissions: function (id, perms) { | ||||
|             return fetch('put', 'users/' + id + '/permissions', perms); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     Nginx: { | ||||
|  | ||||
|         ProxyHosts: { | ||||
|             /** | ||||
|              * @param   {Array}    [expand] | ||||
|              * @param   {String}   [query] | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             getAll: function (expand, query) { | ||||
|                 return getAllObjects('nginx/proxy-hosts', expand, query); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param {Object}  data | ||||
|              */ | ||||
|             create: function (data) { | ||||
|                 return fetch('post', 'nginx/proxy-hosts', data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Object}   data | ||||
|              * @param   {Number}  data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/proxy-hosts/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/proxy-hosts/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             get: function (id) { | ||||
|                 return fetch('get', 'nginx/proxy-hosts/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             enable: function (id) { | ||||
|                 return fetch('post', 'nginx/proxy-hosts/' + id + '/enable'); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             disable: function (id) { | ||||
|                 return fetch('post', 'nginx/proxy-hosts/' + id + '/disable'); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         RedirectionHosts: { | ||||
|             /** | ||||
|              * @param   {Array}    [expand] | ||||
|              * @param   {String}   [query] | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             getAll: function (expand, query) { | ||||
|                 return getAllObjects('nginx/redirection-hosts', expand, query); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param {Object}  data | ||||
|              */ | ||||
|             create: function (data) { | ||||
|                 return fetch('post', 'nginx/redirection-hosts', data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Object}   data | ||||
|              * @param   {Number}   data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/redirection-hosts/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/redirection-hosts/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             get: function (id) { | ||||
|                 return fetch('get', 'nginx/redirection-hosts/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param  {Number}   id | ||||
|              * @param  {FormData} form_data | ||||
|              * @params {Promise} | ||||
|              */ | ||||
|             setCerts: function (id, form_data) { | ||||
|                 return FileUpload('nginx/redirection-hosts/' + id + '/certificates', form_data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             enable: function (id) { | ||||
|                 return fetch('post', 'nginx/redirection-hosts/' + id + '/enable'); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             disable: function (id) { | ||||
|                 return fetch('post', 'nginx/redirection-hosts/' + id + '/disable'); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         Streams: { | ||||
|             /** | ||||
|              * @param   {Array}    [expand] | ||||
|              * @param   {String}   [query] | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             getAll: function (expand, query) { | ||||
|                 return getAllObjects('nginx/streams', expand, query); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param {Object}  data | ||||
|              */ | ||||
|             create: function (data) { | ||||
|                 return fetch('post', 'nginx/streams', data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Object}   data | ||||
|              * @param   {Number}   data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/streams/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/streams/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             get: function (id) { | ||||
|                 return fetch('get', 'nginx/streams/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             enable: function (id) { | ||||
|                 return fetch('post', 'nginx/streams/' + id + '/enable'); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             disable: function (id) { | ||||
|                 return fetch('post', 'nginx/streams/' + id + '/disable'); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         DeadHosts: { | ||||
|             /** | ||||
|              * @param   {Array}    [expand] | ||||
|              * @param   {String}   [query] | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             getAll: function (expand, query) { | ||||
|                 return getAllObjects('nginx/dead-hosts', expand, query); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param {Object}  data | ||||
|              */ | ||||
|             create: function (data) { | ||||
|                 return fetch('post', 'nginx/dead-hosts', data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Object}   data | ||||
|              * @param   {Number}   data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/dead-hosts/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/dead-hosts/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             get: function (id) { | ||||
|                 return fetch('get', 'nginx/dead-hosts/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param  {Number}   id | ||||
|              * @param  {FormData} form_data | ||||
|              * @params {Promise} | ||||
|              */ | ||||
|             setCerts: function (id, form_data) { | ||||
|                 return FileUpload('nginx/dead-hosts/' + id + '/certificates', form_data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             enable: function (id) { | ||||
|                 return fetch('post', 'nginx/dead-hosts/' + id + '/enable'); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             disable: function (id) { | ||||
|                 return fetch('post', 'nginx/dead-hosts/' + id + '/disable'); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         AccessLists: { | ||||
|             /** | ||||
|              * @param   {Array}    [expand] | ||||
|              * @param   {String}   [query] | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             getAll: function (expand, query) { | ||||
|                 return getAllObjects('nginx/access-lists', expand, query); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param {Object}  data | ||||
|              */ | ||||
|             create: function (data) { | ||||
|                 return fetch('post', 'nginx/access-lists', data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Object}   data | ||||
|              * @param   {Number}   data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/access-lists/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/access-lists/' + id); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         Certificates: { | ||||
|             /** | ||||
|              * @param   {Array}    [expand] | ||||
|              * @param   {String}   [query] | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             getAll: function (expand, query) { | ||||
|                 return getAllObjects('nginx/certificates', expand, query); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param {Object}  data | ||||
|              */ | ||||
|             create: function (data) { | ||||
|  | ||||
|                 const timeout = 180000 + (data && data.meta && data.meta.propagation_seconds ? Number(data.meta.propagation_seconds) * 1000 : 0); | ||||
|                 return fetch('post', 'nginx/certificates', data, {timeout}); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Object}   data | ||||
|              * @param   {Number}   data.id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             update: function (data) { | ||||
|                 let id = data.id; | ||||
|                 delete data.id; | ||||
|                 return fetch('put', 'nginx/certificates/' + id, data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             delete: function (id) { | ||||
|                 return fetch('delete', 'nginx/certificates/' + id); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param  {Number}  id | ||||
|              * @param  {FormData} form_data | ||||
|              * @params {Promise} | ||||
|              */ | ||||
|             upload: function (id, form_data) { | ||||
|                 return FileUpload('nginx/certificates/' + id + '/upload', form_data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param  {FormData} form_data | ||||
|              * @params {Promise} | ||||
|              */ | ||||
|             validate: function (form_data) { | ||||
|                 return FileUpload('nginx/certificates/validate', form_data); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             renew: function (id, timeout = 180000) { | ||||
|                 return fetch('post', 'nginx/certificates/' + id + '/renew', undefined, {timeout}); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param  {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             testHttpChallenge: function (domains) { | ||||
|                 return fetch('get', 'nginx/certificates/test-http?' + new URLSearchParams({ | ||||
|                     domains: JSON.stringify(domains), | ||||
|                 })); | ||||
|             }, | ||||
|  | ||||
|             /** | ||||
|              * @param   {Number}  id | ||||
|              * @returns {Promise} | ||||
|              */ | ||||
|             download: function (id) { | ||||
|                 return DownloadFile('get', "nginx/certificates/" + id + "/download", "certificate.zip") | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     AuditLog: { | ||||
|         /** | ||||
|          * @param   {Array}    [expand] | ||||
|          * @param   {String}   [query] | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         getAll: function (expand, query) { | ||||
|             return getAllObjects('audit-log', expand, query); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     Reports: { | ||||
|  | ||||
|         /** | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         getHostStats: function () { | ||||
|             return fetch('get', 'reports/hosts'); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     Settings: { | ||||
|  | ||||
|         /** | ||||
|          * @param   {String}  setting_id | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         getById: function (setting_id) { | ||||
|             return fetch('get', 'settings/' + setting_id); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         getAll: function () { | ||||
|             return getAllObjects('settings'); | ||||
|         }, | ||||
|  | ||||
|         /** | ||||
|          * @param   {Object}   data | ||||
|          * @param   {Number}   data.id | ||||
|          * @returns {Promise} | ||||
|          */ | ||||
|         update: function (data) { | ||||
|             let id = data.id; | ||||
|             delete data.id; | ||||
|             return fetch('put', 'settings/' + id, data); | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| @@ -1,80 +0,0 @@ | ||||
| <td class="text-center"> | ||||
|     <div class="avatar d-block" style="background-image: url(<%- user.avatar || '/images/default-avatar.jpg' %>)"> | ||||
|         <span class="avatar-status <%- user.is_disabled ? 'bg-red' : 'bg-green' %>"></span> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div> | ||||
|         <% if (user.is_deleted) { | ||||
|             %> | ||||
|             <span class="mdi-format-strikethrough" title="Deleted"><%- user.name %></span> | ||||
|             <% | ||||
|         } else { | ||||
|             %> | ||||
|             <%- user.name %> | ||||
|             <% | ||||
|         } | ||||
|         %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div> | ||||
|         <% | ||||
|         var items = []; | ||||
|         switch (object_type) { | ||||
|             case 'proxy-host': | ||||
|                 %> <span class="text-success"><i class="fe fe-zap"></i></span> <% | ||||
|                 items = meta.domain_names; | ||||
|                 break; | ||||
|             case 'redirection-host': | ||||
|                 %> <span class="text-yellow"><i class="fe fe-shuffle"></i></span> <% | ||||
|                 items = meta.domain_names; | ||||
|                 break; | ||||
|             case 'stream': | ||||
|                 %> <span class="text-blue"><i class="fe fe-radio"></i></span> <% | ||||
|                 items.push(meta.incoming_port); | ||||
|                 break; | ||||
|             case 'dead-host': | ||||
|                 %> <span class="text-danger"><i class="fe fe-zap-off"></i></span> <% | ||||
|                 items = meta.domain_names; | ||||
|                 break; | ||||
|             case 'access-list': | ||||
|                 %> <span class="text-teal"><i class="fe fe-lock"></i></span> <% | ||||
|                 items.push(meta.name); | ||||
|                 break; | ||||
|             case 'user': | ||||
|                 %> <span class="text-teal"><i class="fe fe-user"></i></span> <% | ||||
|                 items.push(meta.name); | ||||
|                 break; | ||||
|             case 'certificate': | ||||
|                 %> <span class="text-pink"><i class="fe fe-shield"></i></span> <% | ||||
|                 if (meta.provider === 'letsencrypt') { | ||||
|                     items = meta.domain_names; | ||||
|                 } else { | ||||
|                     items.push(meta.nice_name); | ||||
|                 } | ||||
|                 break; | ||||
|         } | ||||
|         %> <%- i18n('audit-log', action, {name: i18n('audit-log', object_type)}) %> | ||||
|         — | ||||
|         <% | ||||
|         if (items && items.length) { | ||||
|             items.map(function(item) { | ||||
|                 %> | ||||
|                 <span class="tag"><%- item %></span> | ||||
|                 <% | ||||
|             }); | ||||
|         } else { | ||||
|             %> | ||||
|             #<%- object_id %> | ||||
|             <% | ||||
|         } | ||||
|         %> | ||||
|     </div> | ||||
|     <div class="small text-muted"> | ||||
|         <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %> | ||||
|     </div> | ||||
| </td> | ||||
| <td class="text-right"> | ||||
|     <a href="#" class="meta btn btn-secondary btn-sm"><%- i18n('audit-log', 'view-meta') %></a> | ||||
| </td> | ||||
| @@ -1,32 +0,0 @@ | ||||
| const Mn         = require('backbone.marionette'); | ||||
| const Controller = require('../../controller'); | ||||
| const template   = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         meta: 'a.meta' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.meta': function (e) { | ||||
|             e.preventDefault(); | ||||
|             Controller.showAuditMeta(this.model); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         more: function() { | ||||
|             switch (this.object_type) { | ||||
|                 case 'redirection-host': | ||||
|                 case 'stream': | ||||
|                 case 'proxy-host': | ||||
|                     return this.meta.domain_names.join(', '); | ||||
|             } | ||||
|  | ||||
|             return '#' + (this.object_id || '?'); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -1,9 +0,0 @@ | ||||
| <thead> | ||||
|     <th width="30"> </th> | ||||
|     <th>User</th> | ||||
|     <th>Event</th> | ||||
|     <th> </th> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
| @@ -1,27 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const ItemView = require('./item'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| const TableBody = Mn.CollectionView.extend({ | ||||
|     tagName:   'tbody', | ||||
|     childView: ItemView | ||||
| }); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     tagName:   'table', | ||||
|     className: 'table table-hover table-outline table-vcenter card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
| @@ -1,25 +0,0 @@ | ||||
| <div class="card"> | ||||
|     <div class="card-status bg-teal"></div> | ||||
|     <div class="card-header"> | ||||
|         <h3 class="card-title"><%- i18n('audit-log', 'title') %></h3> | ||||
|         <div class="card-options"> | ||||
|             <form class="search-form" role="search"> | ||||
|                 <div class="input-icon"> | ||||
|                     <span class="input-icon-addon"> | ||||
|                       <i class="fe fe-search"></i> | ||||
|                     </span> | ||||
|                     <input name="source-query" type="text" value="" class="form-control form-control-sm" placeholder="<%- i18n('audit-log', 'search') %>" aria-label="<%- i18n('audit-log', 'search') %>"> | ||||
|                 </div> | ||||
|             </form> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div class="card-body no-padding min-100"> | ||||
|         <div class="dimmer active"> | ||||
|             <div class="loader"></div> | ||||
|             <div class="dimmer-content list-region"> | ||||
|                 <!-- List Region --> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,82 +0,0 @@ | ||||
| const Mn            = require('backbone.marionette'); | ||||
| const App           = require('../main'); | ||||
| const AuditLogModel = require('../../models/audit-log'); | ||||
| const ListView      = require('./list/main'); | ||||
| const template      = require('./main.ejs'); | ||||
| const ErrorView     = require('../error/main'); | ||||
| const EmptyView     = require('../empty/main'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     id:       'audit-log', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         dimmer:      '.dimmer', | ||||
|         search:      '.search-form', | ||||
|         query:       'input[name="source-query"]' | ||||
|     }, | ||||
|  | ||||
|     fetch: App.Api.AuditLog.getAll, | ||||
|  | ||||
|     showData: function(response) { | ||||
|         this.showChildView('list_region', new ListView({ | ||||
|             collection: new AuditLogModel.Collection(response) | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     showError: function(err) { | ||||
|         this.showChildView('list_region', new ErrorView({ | ||||
|             code:    err.code, | ||||
|             message: err.message, | ||||
|             retry:   function () { | ||||
|                 App.Controller.showAuditLog(); | ||||
|             } | ||||
|         })); | ||||
|  | ||||
|         console.error(err); | ||||
|     }, | ||||
|  | ||||
|     showEmpty: function() { | ||||
|         this.showChildView('list_region', new EmptyView({ | ||||
|             title:    App.i18n('audit-log', 'empty'), | ||||
|             subtitle: App.i18n('audit-log', 'empty-subtitle') | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'submit @ui.search': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let query = this.ui.query.val(); | ||||
|  | ||||
|             this.fetch(['user'], query) | ||||
|                 .then(response => this.showData(response)) | ||||
|                 .catch(err => { | ||||
|                     this.showError(err); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         view.fetch(['user']) | ||||
|             .then(response => { | ||||
|                 if (!view.isDestroyed() && response && response.length) { | ||||
|                     view.showData(response); | ||||
|                 } else { | ||||
|                     view.showEmpty(); | ||||
|                 } | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 view.showError(err); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 view.ui.dimmer.removeClass('active'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
| @@ -1,27 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('audit-log', 'meta-title') %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <div class="mb-2"> | ||||
|             <div class="tag tag-dark"> | ||||
|                 <%- i18n('audit-log', action, {name: i18n('audit-log', object_type)}) %> | ||||
|                 <span class="tag-addon tag-orange">#<%- object_id %></span> | ||||
|             </div> | ||||
|             <div class="tag tag-dark"> | ||||
|                 <%- i18n('audit-log', 'user') %> | ||||
|                 <span class="tag-addon tag-teal"><%- user.name %></span> | ||||
|             </div> | ||||
|             <div class="tag tag-dark"> | ||||
|                 <%- i18n('audit-log', 'date') %> | ||||
|                 <span class="tag-addon tag-primary"><%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %></span> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <pre><%- JSON.stringify(meta, null, 2) %></pre> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'close') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,7 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const template = require('./meta.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog wide' | ||||
| }); | ||||
| @@ -1,10 +0,0 @@ | ||||
| const UserModel = require('../models/user'); | ||||
|  | ||||
| let cache = { | ||||
|     User:    new UserModel.Model(), | ||||
|     locale:  'en', | ||||
|     version: null | ||||
| }; | ||||
|  | ||||
| module.exports = cache; | ||||
|  | ||||
| @@ -1,441 +0,0 @@ | ||||
| const Backbone = require('backbone'); | ||||
| const Cache    = require('./cache'); | ||||
| const Tokens   = require('./tokens'); | ||||
|  | ||||
| module.exports = { | ||||
|  | ||||
| 	/** | ||||
| 	 * @param {String} route | ||||
| 	 * @param {Object} [options] | ||||
| 	 * @returns {Boolean} | ||||
| 	 */ | ||||
| 	navigate: function (route, options) { | ||||
| 		options = options || {}; | ||||
| 		Backbone.history.navigate(route.toString(), options); | ||||
| 		return true; | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Login | ||||
| 	 */ | ||||
| 	showLogin: function () { | ||||
| 		window.location = '/login'; | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Users | ||||
| 	 */ | ||||
| 	showUsers: function () { | ||||
| 		const controller = this; | ||||
| 		if (Cache.User.isAdmin()) { | ||||
| 			require(['./main', './users/main'], (App, View) => { | ||||
| 				controller.navigate('/users'); | ||||
| 				App.UI.showAppContent(new View()); | ||||
| 			}); | ||||
| 		} else { | ||||
| 			this.showDashboard(); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * User Form | ||||
| 	 * | ||||
| 	 * @param [model] | ||||
| 	 */ | ||||
| 	showUserForm: function (model) { | ||||
| 		if (Cache.User.isAdmin()) { | ||||
| 			require(['./main', './user/form'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * User Permissions Form | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showUserPermissions: function (model) { | ||||
| 		if (Cache.User.isAdmin()) { | ||||
| 			require(['./main', './user/permissions'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * User Password Form | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showUserPasswordForm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || model.get('id') === Cache.User.get('id')) { | ||||
| 			require(['./main', './user/password'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * User Delete Confirm | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showUserDeleteConfirm: function (model) { | ||||
| 		if (Cache.User.isAdmin() && model.get('id') !== Cache.User.get('id')) { | ||||
| 			require(['./main', './user/delete'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Dashboard | ||||
| 	 */ | ||||
| 	showDashboard: function () { | ||||
| 		const controller = this; | ||||
| 		require(['./main', './dashboard/main'], (App, View) => { | ||||
| 			controller.navigate('/'); | ||||
| 			App.UI.showAppContent(new View()); | ||||
| 		}); | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Nginx Proxy Hosts | ||||
| 	 */ | ||||
| 	showNginxProxy: function () { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canView('proxy_hosts')) { | ||||
| 			const controller = this; | ||||
|  | ||||
| 			require(['./main', './nginx/proxy/main'], (App, View) => { | ||||
| 				controller.navigate('/nginx/proxy'); | ||||
| 				App.UI.showAppContent(new View()); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Nginx Proxy Host Form | ||||
| 	 * | ||||
| 	 * @param [model] | ||||
| 	 */ | ||||
| 	showNginxProxyForm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { | ||||
| 			require(['./main', './nginx/proxy/form'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Proxy Host Delete Confirm | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showNginxProxyDeleteConfirm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('proxy_hosts')) { | ||||
| 			require(['./main', './nginx/proxy/delete'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Nginx Redirection Hosts | ||||
| 	 */ | ||||
| 	showNginxRedirection: function () { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canView('redirection_hosts')) { | ||||
| 			const controller = this; | ||||
| 			require(['./main', './nginx/redirection/main'], (App, View) => { | ||||
| 				controller.navigate('/nginx/redirection'); | ||||
| 				App.UI.showAppContent(new View()); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Nginx Redirection Host Form | ||||
| 	 * | ||||
| 	 * @param [model] | ||||
| 	 */ | ||||
| 	showNginxRedirectionForm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { | ||||
| 			require(['./main', './nginx/redirection/form'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Proxy Redirection Delete Confirm | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showNginxRedirectionDeleteConfirm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('redirection_hosts')) { | ||||
| 			require(['./main', './nginx/redirection/delete'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Nginx Stream Hosts | ||||
| 	 */ | ||||
| 	showNginxStream: function () { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canView('streams')) { | ||||
| 			const controller = this; | ||||
| 			require(['./main', './nginx/stream/main'], (App, View) => { | ||||
| 				controller.navigate('/nginx/stream'); | ||||
| 				App.UI.showAppContent(new View()); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Stream Form | ||||
| 	 * | ||||
| 	 * @param [model] | ||||
| 	 */ | ||||
| 	showNginxStreamForm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { | ||||
| 			require(['./main', './nginx/stream/form'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Stream Delete Confirm | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showNginxStreamDeleteConfirm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('streams')) { | ||||
| 			require(['./main', './nginx/stream/delete'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Nginx Dead Hosts | ||||
| 	 */ | ||||
| 	showNginxDead: function () { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canView('dead_hosts')) { | ||||
| 			const controller = this; | ||||
| 			require(['./main', './nginx/dead/main'], (App, View) => { | ||||
| 				controller.navigate('/nginx/404'); | ||||
| 				App.UI.showAppContent(new View()); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Dead Host Form | ||||
| 	 * | ||||
| 	 * @param [model] | ||||
| 	 */ | ||||
| 	showNginxDeadForm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { | ||||
| 			require(['./main', './nginx/dead/form'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Dead Host Delete Confirm | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showNginxDeadDeleteConfirm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('dead_hosts')) { | ||||
| 			require(['./main', './nginx/dead/delete'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Help Dialog | ||||
| 	 * | ||||
| 	 * @param {String}  title | ||||
| 	 * @param {String}  content | ||||
| 	 */ | ||||
| 	showHelp: function (title, content) { | ||||
| 		require(['./main', './help/main'], function (App, View) { | ||||
| 			App.UI.showModalDialog(new View({title: title, content: content})); | ||||
| 		}); | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Nginx Access | ||||
| 	 */ | ||||
| 	showNginxAccess: function () { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canView('access_lists')) { | ||||
| 			const controller = this; | ||||
| 			require(['./main', './nginx/access/main'], (App, View) => { | ||||
| 				controller.navigate('/nginx/access'); | ||||
| 				App.UI.showAppContent(new View()); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Nginx Access List Form | ||||
| 	 * | ||||
| 	 * @param [model] | ||||
| 	 */ | ||||
| 	showNginxAccessListForm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { | ||||
| 			require(['./main', './nginx/access/form'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Access List Delete Confirm | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showNginxAccessListDeleteConfirm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('access_lists')) { | ||||
| 			require(['./main', './nginx/access/delete'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Nginx Certificates | ||||
| 	 */ | ||||
| 	showNginxCertificates: function () { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canView('certificates')) { | ||||
| 			const controller = this; | ||||
| 			require(['./main', './nginx/certificates/main'], (App, View) => { | ||||
| 				controller.navigate('/nginx/certificates'); | ||||
| 				App.UI.showAppContent(new View()); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Nginx Certificate Form | ||||
| 	 * | ||||
| 	 * @param [model] | ||||
| 	 */ | ||||
| 	showNginxCertificateForm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||
| 			require(['./main', './nginx/certificates/form'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Certificate Renew | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showNginxCertificateRenew: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||
| 			require(['./main', './nginx/certificates/renew'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Certificate Delete Confirm | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showNginxCertificateDeleteConfirm: function (model) { | ||||
| 		if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||
| 			require(['./main', './nginx/certificates/delete'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Certificate Test Reachability | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showNginxCertificateTestReachability: function (model) { | ||||
| 	  if (Cache.User.isAdmin() || Cache.User.canManage('certificates')) { | ||||
| 		require(['./main', './nginx/certificates/test'], function (App, View) { | ||||
| 		  App.UI.showModalDialog(new View({model: model})); | ||||
| 		}); | ||||
| 	  } | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Audit Log | ||||
| 	 */ | ||||
| 	showAuditLog: function () { | ||||
| 		const controller = this; | ||||
| 		if (Cache.User.isAdmin()) { | ||||
| 			require(['./main', './audit-log/main'], (App, View) => { | ||||
| 				controller.navigate('/audit-log'); | ||||
| 				App.UI.showAppContent(new View()); | ||||
| 			}); | ||||
| 		} else { | ||||
| 			this.showDashboard(); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Audit Log Metadata | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showAuditMeta: function (model) { | ||||
| 		if (Cache.User.isAdmin()) { | ||||
| 			require(['./main', './audit-log/meta'], function (App, View) { | ||||
| 				App.UI.showModalDialog(new View({model: model})); | ||||
| 			}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Settings | ||||
| 	 */ | ||||
| 	showSettings: function () { | ||||
| 		const controller = this; | ||||
| 		if (Cache.User.isAdmin()) { | ||||
| 			require(['./main', './settings/main'], (App, View) => { | ||||
| 				controller.navigate('/settings'); | ||||
| 				App.UI.showAppContent(new View()); | ||||
| 			}); | ||||
| 		} else { | ||||
| 			this.showDashboard(); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Settings Item Form | ||||
| 	 * | ||||
| 	 * @param model | ||||
| 	 */ | ||||
| 	showSettingForm: function (model) { | ||||
| 		if (Cache.User.isAdmin()) { | ||||
| 			if (model.get('id') === 'default-site') { | ||||
| 				require(['./main', './settings/default-site/main'], function (App, View) { | ||||
| 					App.UI.showModalDialog(new View({model: model})); | ||||
| 				}); | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * Logout | ||||
| 	 */ | ||||
| 	logout: function () { | ||||
| 		Tokens.dropTopToken(); | ||||
| 		this.showLogin(); | ||||
| 	} | ||||
| }; | ||||
| @@ -1,67 +0,0 @@ | ||||
| <div class="page-header"> | ||||
|     <h1 class="page-title"><%- i18n('dashboard', 'title', {name: getUserName()}) %></h1> | ||||
| </div> | ||||
|  | ||||
| <% if (columns) { %> | ||||
| <div class="row"> | ||||
|     <% if (canShow('proxy_hosts')) { %> | ||||
|     <div class="col-sm-<%- 24 / columns %> col-lg-<%- 12 / columns %>"> | ||||
|         <div class="card p-3"> | ||||
|             <div class="d-flex align-items-center"> | ||||
|                     <span class="stamp stamp-md bg-green mr-3"> | ||||
|                       <i class="fe fe-zap"></i> | ||||
|                     </span> | ||||
|                 <div> | ||||
|                     <h4 class="m-0"><a href="/nginx/proxy"><%- getHostStat('proxy') %> <small><%- i18n('proxy-hosts', 'title') %></small></a></h4> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <% } %> | ||||
|  | ||||
|     <% if (canShow('redirection_hosts')) { %> | ||||
|     <div class="col-sm-<%- 24 / columns %> col-lg-<%- 12 / columns %>"> | ||||
|         <div class="card p-3"> | ||||
|             <div class="d-flex align-items-center"> | ||||
|                     <span class="stamp stamp-md bg-yellow mr-3"> | ||||
|                       <i class="fe fe-shuffle"></i> | ||||
|                     </span> | ||||
|                 <div> | ||||
|                     <h4 class="m-0"><a href="/nginx/redirection"><%- getHostStat('redirection') %> <small><%- i18n('redirection-hosts', 'title') %></small></a></h4> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <% } %> | ||||
|  | ||||
|     <% if (canShow('streams')) { %> | ||||
|     <div class="col-sm-<%- 24 / columns %> col-lg-<%- 12 / columns %>"> | ||||
|         <div class="card p-3"> | ||||
|             <div class="d-flex align-items-center"> | ||||
|                     <span class="stamp stamp-md bg-blue mr-3"> | ||||
|                       <i class="fe fe-radio"></i> | ||||
|                     </span> | ||||
|                 <div> | ||||
|                     <h4 class="m-0"><a href="/nginx/stream"><%- getHostStat('stream') %> <small> <%- i18n('streams', 'title') %></small></a></h4> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <% } %> | ||||
|  | ||||
|     <% if (canShow('dead_hosts')) { %> | ||||
|     <div class="col-sm-<%- 24 / columns %> col-lg-<%- 12 / columns %>"> | ||||
|         <div class="card p-3"> | ||||
|             <div class="d-flex align-items-center"> | ||||
|                     <span class="stamp stamp-md bg-red mr-3"> | ||||
|                       <i class="fe fe-zap-off"></i> | ||||
|                     </span> | ||||
|                 <div> | ||||
|                     <h4 class="m-0"><a href="/nginx/404"><%- getHostStat('dead') %> <small><%- i18n('dead-hosts', 'title') %></small></a></h4> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
|     <% } %> | ||||
| </div> | ||||
| <% } %> | ||||
| @@ -1,90 +0,0 @@ | ||||
| const Mn         = require('backbone.marionette'); | ||||
| const Cache      = require('../cache'); | ||||
| const Controller = require('../controller'); | ||||
| const Api        = require('../api'); | ||||
| const Helpers    = require('../../lib/helpers'); | ||||
| const template   = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
| 	template: template, | ||||
| 	id:       'dashboard', | ||||
| 	columns:  0, | ||||
|  | ||||
| 	stats: {}, | ||||
|  | ||||
| 	ui: { | ||||
| 		links: 'a' | ||||
| 	}, | ||||
|  | ||||
| 	events: { | ||||
| 		'click @ui.links': function (e) { | ||||
| 			e.preventDefault(); | ||||
| 			Controller.navigate($(e.currentTarget).attr('href'), true); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	templateContext: function () { | ||||
| 		const view = this; | ||||
|  | ||||
| 		return { | ||||
| 			getUserName: function () { | ||||
| 				return Cache.User.get('nickname') || Cache.User.get('name'); | ||||
| 			}, | ||||
|  | ||||
| 			getHostStat: function (type) { | ||||
| 				if (view.stats && typeof view.stats.hosts !== 'undefined' && typeof view.stats.hosts[type] !== 'undefined') { | ||||
| 					return Helpers.niceNumber(view.stats.hosts[type]); | ||||
| 				} | ||||
|  | ||||
| 				return '-'; | ||||
| 			}, | ||||
|  | ||||
| 			canShow: function (perm) { | ||||
| 				return Cache.User.isAdmin() || Cache.User.canView(perm); | ||||
| 			}, | ||||
|  | ||||
| 			columns: view.columns | ||||
| 		}; | ||||
| 	}, | ||||
|  | ||||
| 	onRender: function () { | ||||
| 		const view = this; | ||||
| 		if (typeof view.stats.hosts === 'undefined') { | ||||
| 			Api.Reports.getHostStats() | ||||
| 				.then(response => { | ||||
| 					if (!view.isDestroyed()) { | ||||
| 						view.stats.hosts = response; | ||||
| 						view.render(); | ||||
| 					} | ||||
| 				}) | ||||
| 				.catch(err => { | ||||
| 					console.log(err); | ||||
| 				}); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	/** | ||||
| 	 * @param {Object}  [model] | ||||
| 	 */ | ||||
| 	preRender: function (model) { | ||||
| 		this.columns = 0; | ||||
|  | ||||
| 		// calculate the available columns based on permissions for the objects | ||||
| 		// and store as a variable | ||||
| 		const perms = ['proxy_hosts', 'redirection_hosts', 'streams', 'dead_hosts']; | ||||
|  | ||||
| 		perms.map(perm => { | ||||
| 			this.columns += Cache.User.isAdmin() || Cache.User.canView(perm) ? 1 : 0; | ||||
| 		}); | ||||
|  | ||||
| 		// Prevent double rendering on initial calls | ||||
| 		if (typeof model !== 'undefined') { | ||||
| 			this.render(); | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| 	initialize: function () { | ||||
| 		this.preRender(); | ||||
| 		this.listenTo(Cache.User, 'change', this.preRender); | ||||
| 	} | ||||
| }); | ||||
| @@ -1,11 +0,0 @@ | ||||
| <% if (title) { %> | ||||
|     <h1 class="h2 mb-3"><%- title %></h1> | ||||
| <% } | ||||
|  | ||||
| if (subtitle) { %> | ||||
|     <p class="h4 text-muted font-weight-normal mb-7"><%- subtitle %></p> | ||||
| <% } | ||||
|  | ||||
| if (link) { %> | ||||
|     <a class="btn btn-<%- btn_color %>" href="#"><%- link %></a> | ||||
| <% } %> | ||||
| @@ -1,33 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     className: 'text-center m-7', | ||||
|     template:  template, | ||||
|  | ||||
|     options: { | ||||
|         btn_color: 'teal' | ||||
|     }, | ||||
|  | ||||
|     ui: { | ||||
|         action: 'a' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.action': function (e) { | ||||
|             e.preventDefault(); | ||||
|             this.getOption('action')(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: function () { | ||||
|         return { | ||||
|             title:     this.getOption('title'), | ||||
|             subtitle:  this.getOption('subtitle'), | ||||
|             link:      this.getOption('link'), | ||||
|             action:    typeof this.getOption('action') === 'function', | ||||
|             btn_color: this.getOption('btn_color') | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| }); | ||||
| @@ -1,7 +0,0 @@ | ||||
| <i class="fe fe-alert-triangle mr-2" aria-hidden="true"></i> | ||||
| <%= code ? '<strong>' + code + '</strong> — ' : '' %> | ||||
| <%- message %> | ||||
|  | ||||
| <% if (retry) { %> | ||||
|     <br><br><a href="#" class="btn btn-sm btn-warning retry"><%- i18n('str', 'try-again') %></a> | ||||
| <% } %> | ||||
| @@ -1,27 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'alert alert-icon alert-warning m-5', | ||||
|  | ||||
|     ui: { | ||||
|         retry: 'a.retry' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.retry': function (e) { | ||||
|             e.preventDefault(); | ||||
|             this.getOption('retry')(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: function () { | ||||
|         return { | ||||
|             message: this.getOption('message'), | ||||
|             code:    this.getOption('code'), | ||||
|             retry:   typeof this.getOption('retry') === 'function' | ||||
|         }; | ||||
|     } | ||||
|  | ||||
| }); | ||||
| @@ -1,12 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- title %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <%= content %> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'close') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,16 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog wide', | ||||
|  | ||||
|     templateContext: function () { | ||||
|         let content = this.getOption('content').split("\n"); | ||||
|  | ||||
|         return { | ||||
|             title:   this.getOption('title'), | ||||
|             content: '<p>' + content.join('</p><p>') + '</p>' | ||||
|         }; | ||||
|     } | ||||
| }); | ||||
| @@ -1,23 +0,0 @@ | ||||
| const Cache    = ('./cache'); | ||||
| const messages = require('../i18n/messages.json'); | ||||
|  | ||||
| /** | ||||
|  * @param {String}  namespace | ||||
|  * @param {String}  key | ||||
|  * @param {Object}  [data] | ||||
|  */ | ||||
| module.exports = function (namespace, key, data) { | ||||
|     let locale = Cache.locale; | ||||
|     // check that the locale exists | ||||
|     if (typeof messages[locale] === 'undefined') { | ||||
|         locale = 'en'; | ||||
|     } | ||||
|  | ||||
|     if (typeof messages[locale][namespace] !== 'undefined' && typeof messages[locale][namespace][key] !== 'undefined') { | ||||
|         return messages[locale][namespace][key](data); | ||||
|     } else if (locale !== 'en' && typeof messages['en'][namespace] !== 'undefined' && typeof messages['en'][namespace][key] !== 'undefined') { | ||||
|         return messages['en'][namespace][key](data); | ||||
|     } | ||||
|  | ||||
|     return '(MISSING: ' + namespace + '/' + key + ')'; | ||||
| }; | ||||
| @@ -1,155 +0,0 @@ | ||||
| const _          = require('underscore'); | ||||
| const Backbone   = require('backbone'); | ||||
| const Mn         = require('../lib/marionette'); | ||||
| const Cache      = require('./cache'); | ||||
| const Controller = require('./controller'); | ||||
| const Router     = require('./router'); | ||||
| const Api        = require('./api'); | ||||
| const Tokens     = require('./tokens'); | ||||
| const UI         = require('./ui/main'); | ||||
| const i18n       = require('./i18n'); | ||||
|  | ||||
| const App = Mn.Application.extend({ | ||||
|  | ||||
|     Cache:      Cache, | ||||
|     Api:        Api, | ||||
|     UI:         null, | ||||
|     i18n:       i18n, | ||||
|     Controller: Controller, | ||||
|  | ||||
|     region: { | ||||
|         el:             '#app', | ||||
|         replaceElement: true | ||||
|     }, | ||||
|  | ||||
|     onStart: function (app, options) { | ||||
|         console.log(i18n('main', 'welcome')); | ||||
|  | ||||
|         // Check if token is coming through | ||||
|         if (this.getParam('token')) { | ||||
|             Tokens.addToken(this.getParam('token')); | ||||
|         } | ||||
|  | ||||
|         // Check if we are still logged in by refreshing the token | ||||
|         Api.status() | ||||
|             .then(result => { | ||||
|                 Cache.version = [result.version.major, result.version.minor, result.version.revision].join('.'); | ||||
|             }) | ||||
|             .then(Api.Tokens.refresh) | ||||
|             .then(this.bootstrap) | ||||
|             .then(() => { | ||||
|                 console.info(i18n('main', 'logged-in', Cache.User.attributes)); | ||||
|                 this.bootstrapTimer(); | ||||
|                 this.refreshTokenTimer(); | ||||
|  | ||||
|                 this.UI = new UI(); | ||||
|                 this.UI.on('render', () => { | ||||
|                     new Router(options); | ||||
|                     Backbone.history.start({pushState: true}); | ||||
|  | ||||
|                     // Ask the admin use to change their details | ||||
|                     if (Cache.User.get('email') === 'admin@example.com') { | ||||
|                         Controller.showUserForm(Cache.User); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 this.getRegion().show(this.UI); | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 console.warn('Not logged in:', err.message); | ||||
|                 Controller.showLogin(); | ||||
|             }); | ||||
|     }, | ||||
|  | ||||
|     History: { | ||||
|         replace: function (data) { | ||||
|             window.history.replaceState(_.extend(window.history.state || {}, data), document.title); | ||||
|         }, | ||||
|  | ||||
|         get: function (attr) { | ||||
|             return window.history.state ? window.history.state[attr] : undefined; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     getParam: function (name) { | ||||
|         name        = name.replace(/[\[\]]/g, '\\$&'); | ||||
|         let url     = window.location.href; | ||||
|         let regex   = new RegExp('[?&]' + name + '(=([^&#]*)|&|#|$)'); | ||||
|         let results = regex.exec(url); | ||||
|  | ||||
|         if (!results) { | ||||
|             return null; | ||||
|         } | ||||
|  | ||||
|         if (!results[2]) { | ||||
|             return ''; | ||||
|         } | ||||
|  | ||||
|         return decodeURIComponent(results[2].replace(/\+/g, ' ')); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Get user and other base info to start prime the cache and the application | ||||
|      * | ||||
|      * @returns {Promise} | ||||
|      */ | ||||
|     bootstrap: function () { | ||||
|         return Api.Users.getById('me', ['permissions']) | ||||
|             .then(response => { | ||||
|                 Cache.User.set(response); | ||||
|                 Tokens.setCurrentName(response.nickname || response.name); | ||||
|             }); | ||||
|     }, | ||||
|  | ||||
|     /** | ||||
|      * Bootstraps the user from time to time | ||||
|      */ | ||||
|     bootstrapTimer: function () { | ||||
|         setTimeout(() => { | ||||
|             Api.status() | ||||
|                 .then(result => { | ||||
|                     let version = [result.version.major, result.version.minor, result.version.revision].join('.'); | ||||
|                     if (version !== Cache.version) { | ||||
|                         document.location.reload(); | ||||
|                     } | ||||
|                 }) | ||||
|                 .then(this.bootstrap) | ||||
|                 .then(() => { | ||||
|                     this.bootstrapTimer(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     if (err.message !== 'timeout' && err.code && err.code !== 400) { | ||||
|                         console.log(err); | ||||
|                         console.error(err.message); | ||||
|                         console.info('Not logged in?'); | ||||
|                         Controller.showLogin(); | ||||
|                     } else { | ||||
|                         this.bootstrapTimer(); | ||||
|                     } | ||||
|                 }); | ||||
|         }, 30 * 1000); // 30 seconds | ||||
|     }, | ||||
|  | ||||
|     refreshTokenTimer: function () { | ||||
|         setTimeout(() => { | ||||
|             return Api.Tokens.refresh() | ||||
|                 .then(this.bootstrap) | ||||
|                 .then(() => { | ||||
|                     this.refreshTokenTimer(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     if (err.message !== 'timeout' && err.code && err.code !== 400) { | ||||
|                         console.log(err); | ||||
|                         console.error(err.message); | ||||
|                         console.info('Not logged in?'); | ||||
|                         Controller.showLogin(); | ||||
|                     } else { | ||||
|                         this.refreshTokenTimer(); | ||||
|                     } | ||||
|                 }); | ||||
|         }, 10 * 60 * 1000); | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const app      = new App(); | ||||
| module.exports = app; | ||||
| @@ -1,23 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('access-lists', 'delete') %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <form> | ||||
|             <div class="row"> | ||||
|                 <div class="col-sm-12 col-md-12"> | ||||
|                     <%= i18n('access-lists', 'delete-confirm') %> | ||||
|                     <% if (proxy_host_count) { %> | ||||
|                         <br><br> | ||||
|                         <%- i18n('access-lists', 'delete-has-hosts', {count: proxy_host_count}) %> | ||||
|                     <% } %> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </form> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||
|         <button type="button" class="btn btn-danger save"><%- i18n('str', 'sure') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,32 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         form:    'form', | ||||
|         buttons: '.modal-footer button', | ||||
|         cancel:  'button.cancel', | ||||
|         save:    'button.save' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|  | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|  | ||||
|             App.Api.Nginx.AccessLists.delete(this.model.get('id')) | ||||
|                 .then(() => { | ||||
|                     App.Controller.showNginxAccess(); | ||||
|                     App.UI.closeModal(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -1,108 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('access-lists', 'form-title', {id: id}) %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body has-tabs"> | ||||
|         <form> | ||||
|             <ul class="nav nav-tabs" role="tablist"> | ||||
|                 <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active show" aria-selected="true"><i class="fe fe-zap"></i> <%- i18n('access-lists', 'details') %></a></li> | ||||
|                 <li role="presentation" class="nav-item"><a href="#auth" aria-controls="tab4" role="tab" data-toggle="tab" class="nav-link" aria-selected="false"><i class="fe fe-users"></i> <%- i18n('access-lists', 'authorization') %></a></li> | ||||
|                 <li role="presentation" class="nav-item"><a href="#access" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link" aria-selected="false"><i class="fe fe-radio"></i> <%- i18n('access-lists', 'access') %></a></li> | ||||
|             </ul> | ||||
|  | ||||
|             <div class="tab-content"> | ||||
|                 <!-- Details --> | ||||
|                 <div role="tabpanel" class="tab-pane active show" id="details"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('str', 'name') %> <span class="form-required">*</span></label> | ||||
|                                 <input type="text" name="name" class="form-control" value="<%- name %>" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="satisfy_any" value="1"<%- typeof satisfy_any !== 'undefined' && satisfy_any ? ' checked' : '' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('access-lists', 'satisfy-any') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="pass_auth" value="1"<%- typeof pass_auth !== 'undefined' && pass_auth ? ' checked' : '' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('access-lists', 'pass-auth') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Authorization --> | ||||
|                 <div class="tab-pane" id="auth"> | ||||
|                     <p> | ||||
|                         Basic Authorization via | ||||
|                         <a target="_blank" href="https://nginx.org/en/docs/http/ngx_http_auth_basic_module.html"> | ||||
|                             Nginx HTTP Basic Authentication | ||||
|                         </a> | ||||
|                     </p> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('str', 'username') %></label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('str', 'password') %></label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|  | ||||
|                     <div class="items"><!-- items --></div> | ||||
|                     <div class="btn-list justify-content-end"> | ||||
|                         <button type="button" class="btn btn-teal auth_add"><%- i18n('access-lists', 'auth-add') %></button> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Access --> | ||||
|                 <div class="tab-pane" id="access"> | ||||
|                     <p> | ||||
|                         IP Address Whitelist/Blacklist via | ||||
|                         <a target="_blank" href="https://nginx.org/en/docs/http/ngx_http_access_module.html"> | ||||
|                             Nginx HTTP Access | ||||
|                         </a> | ||||
|                     </p> | ||||
|                     <div class="clients"><!-- clients --></div> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-3 col-md-3"> | ||||
|                             <div class="form-group"> | ||||
|                                 <input type="text" class="form-control disabled" value="deny" disabled> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-9 col-md-9"> | ||||
|                             <div class="form-group"> | ||||
|                                 <input type="text" class="form-control disabled" value="all" disabled> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="text-muted">Note that the <code>allow</code> and <code>deny</code> directives will be applied in the order they are defined.</div> | ||||
|                     <div class="btn-list justify-content-end"> | ||||
|                         <button type="button" class="btn btn-teal access_add"><%- i18n('access-lists', 'access-add') %></button> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|             </div> | ||||
|         </form> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||
|         <button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,153 +0,0 @@ | ||||
| const Mn              = require('backbone.marionette'); | ||||
| const App             = require('../../main'); | ||||
| const AccessListModel = require('../../../models/access-list'); | ||||
| const template        = require('./form.ejs'); | ||||
| const ItemView        = require('./form/item'); | ||||
| const ClientView      = require('./form/client'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
|  | ||||
| const ItemsView = Mn.CollectionView.extend({ | ||||
|     childView: ItemView | ||||
| }); | ||||
|  | ||||
| const ClientsView = Mn.CollectionView.extend({ | ||||
|     childView: ClientView | ||||
| }); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         items_region:   '.items', | ||||
|         clients_region: '.clients', | ||||
|         form:           'form', | ||||
|         buttons:        '.modal-footer button', | ||||
|         cancel:         'button.cancel', | ||||
|         save:           'button.save', | ||||
|         access_add:     'button.access_add', | ||||
|         auth_add:       'button.auth_add' | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         items_region:   '@ui.items_region', | ||||
|         clients_region: '@ui.clients_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|  | ||||
|             if (!this.ui.form[0].checkValidity()) { | ||||
|                 $('<input type="submit">').hide().appendTo(this.ui.form).click().remove(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let view         = this; | ||||
|             let form_data    = this.ui.form.serializeJSON(); | ||||
|             let items_data   = []; | ||||
|             let clients_data = []; | ||||
|  | ||||
|             form_data.username.map(function (val, idx) { | ||||
|                 if (val.trim().length) { | ||||
|                     items_data.push({ | ||||
|                         username: val.trim(), | ||||
|                         password: form_data.password[idx] | ||||
|                     }); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             form_data.address.map(function (val, idx) { | ||||
|                 if (val.trim().length) { | ||||
|                     clients_data.push({ | ||||
|                         address: val.trim(), | ||||
|                         directive: form_data.directive[idx] | ||||
|                     }) | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             if (!items_data.length && !clients_data.length) { | ||||
|                 alert('You must specify at least 1 Authorization or Access rule'); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let data = { | ||||
|                 name:       form_data.name, | ||||
|                 satisfy_any: !!form_data.satisfy_any, | ||||
|                 pass_auth: !!form_data.pass_auth, | ||||
|                 items:      items_data, | ||||
|                 clients:    clients_data | ||||
|             }; | ||||
|  | ||||
|             console.log(data); | ||||
|  | ||||
|             let method = App.Api.Nginx.AccessLists.create; | ||||
|             let is_new = true; | ||||
|  | ||||
|             if (this.model.get('id')) { | ||||
|                 // edit | ||||
|                 is_new  = false; | ||||
|                 method  = App.Api.Nginx.AccessLists.update; | ||||
|                 data.id = this.model.get('id'); | ||||
|             } | ||||
|  | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|             method(data) | ||||
|                 .then(result => { | ||||
|                     view.model.set(result); | ||||
|  | ||||
|                     App.UI.closeModal(function () { | ||||
|                         if (is_new) { | ||||
|                             App.Controller.showNginxAccess(); | ||||
|                         } | ||||
|                     }); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         }, | ||||
|         'click @ui.access_add': function (e) {  | ||||
|             e.preventDefault(); | ||||
|  | ||||
|             let clients = this.model.get('clients'); | ||||
|             clients.push({}); | ||||
|             this.showChildView('clients_region', new ClientsView({ | ||||
|                 collection: new Backbone.Collection(clients) | ||||
|             })); | ||||
|         }, | ||||
|         'click @ui.auth_add': function (e) {  | ||||
|             e.preventDefault(); | ||||
|  | ||||
|             let items = this.model.get('items'); | ||||
|             items.push({}); | ||||
|             this.showChildView('items_region', new ItemsView({ | ||||
|                 collection: new Backbone.Collection(items) | ||||
|             })); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let items = this.model.get('items'); | ||||
|         let clients = this.model.get('clients'); | ||||
|  | ||||
|         // Ensure at least one field is shown initally | ||||
|         if (!items.length) items.push({}); | ||||
|         if (!clients.length) clients.push({}); | ||||
|  | ||||
|         this.showChildView('items_region', new ItemsView({ | ||||
|             collection: new Backbone.Collection(items) | ||||
|         })); | ||||
|  | ||||
|         this.showChildView('clients_region', new ClientsView({ | ||||
|             collection: new Backbone.Collection(clients) | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     initialize: function (options) { | ||||
|         if (typeof options.model === 'undefined' || !options.model) { | ||||
|             this.model = new AccessListModel.Model(); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -1,13 +0,0 @@ | ||||
| <div class="col-sm-3 col-md-3"> | ||||
|     <div class="form-group"> | ||||
|         <select name="directive[]" class="form-control custom-select" placeholder="http"> | ||||
|             <option value="allow" <%- typeof directive == 'undefined' || directive === 'allow' ? 'selected' : '' %>>allow</option> | ||||
|             <option value="deny" <%- typeof directive !== 'undefined' && directive === 'deny' ? 'selected' : '' %>>deny</option> | ||||
|         </select> | ||||
|     </div> | ||||
| </div> | ||||
| <div class="col-sm-9 col-md-9"> | ||||
|     <div class="form-group"> | ||||
|         <input type="text" name="address[]" placeholder="IP / Subnet" class="form-control" value="<%- typeof address !== 'undefined' ? address : '' %>" value=""> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,7 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const template = require('./client.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'row' | ||||
| }); | ||||
| @@ -1,10 +0,0 @@ | ||||
| <div class="col-sm-6 col-md-6"> | ||||
|     <div class="form-group"> | ||||
|         <input type="text" name="username[]" class="form-control" value="<%- typeof username !== 'undefined' ? username : '' %>"> | ||||
|     </div> | ||||
| </div> | ||||
| <div class="col-sm-6 col-md-6"> | ||||
|     <div class="form-group"> | ||||
|         <input type="password" name="password[]" class="form-control" placeholder="<%- typeof hint !== 'undefined' ? hint : '' %>" value=""> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,7 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const template = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'row' | ||||
| }); | ||||
| @@ -1,42 +0,0 @@ | ||||
| <td class="text-center"> | ||||
|     <div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>"> | ||||
|         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div> | ||||
|         <%- name %> | ||||
|     </div> | ||||
|     <div class="small text-muted"> | ||||
|         <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <%- i18n('access-lists', 'item-count', {count: items.length || 0}) %> | ||||
| </td> | ||||
| <td> | ||||
|     <%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %> | ||||
| </td> | ||||
| <td> | ||||
|     <% if (satisfy_any) { %> | ||||
|     <%- i18n('str', 'any') %> | ||||
|     <%} else { %> | ||||
|     <%- i18n('str', 'all') %> | ||||
|     <% } %> | ||||
| </td> | ||||
| <td> | ||||
|     <%- i18n('access-lists', 'proxy-host-count', {count: proxy_host_count}) %> | ||||
| </td> | ||||
| <% if (canManage) { %> | ||||
| <td class="text-right"> | ||||
|     <div class="item-action dropdown"> | ||||
|         <a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a> | ||||
|         <div class="dropdown-menu dropdown-menu-right"> | ||||
|             <span class="dropdown-header"><%- i18n('audit-log', 'access-list') %> #<%- id %></span> | ||||
|             <a href="#" class="edit dropdown-item"><i class="dropdown-icon fe fe-edit"></i> <%- i18n('str', 'edit') %></a> | ||||
|             <div class="dropdown-divider"></div> | ||||
|             <a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a> | ||||
|         </div> | ||||
|     </div> | ||||
| </td> | ||||
| <% } %> | ||||
| @@ -1,33 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const template = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         edit:   'a.edit', | ||||
|         delete: 'a.delete' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.edit': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxAccessListForm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxAccessListDeleteConfirm(this.model); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('access_lists') | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
| @@ -1,14 +0,0 @@ | ||||
| <thead> | ||||
|     <th width="30"> </th> | ||||
|     <th><%- i18n('str', 'name') %></th> | ||||
|     <th><%- i18n('access-lists', 'authorization') %></th> | ||||
|     <th><%- i18n('access-lists', 'access') %></th> | ||||
|     <th><%- i18n('access-lists', 'satisfy') %></th> | ||||
|     <th><%- i18n('proxy-hosts', 'title') %></th> | ||||
|     <% if (canManage) { %> | ||||
|     <th> </th> | ||||
|     <% } %> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
| @@ -1,32 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const ItemView = require('./item'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| const TableBody = Mn.CollectionView.extend({ | ||||
|     tagName:   'tbody', | ||||
|     childView: ItemView | ||||
| }); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     tagName:   'table', | ||||
|     className: 'table table-hover table-outline table-vcenter card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('access_lists') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
| @@ -1,28 +0,0 @@ | ||||
| <div class="card"> | ||||
|     <div class="card-status bg-teal"></div> | ||||
|     <div class="card-header"> | ||||
|         <h3 class="card-title"><%- i18n('access-lists', 'title') %></h3> | ||||
|         <div class="card-options"> | ||||
|             <form class="search-form" role="search"> | ||||
|                 <div class="input-icon"> | ||||
|                     <span class="input-icon-addon"> | ||||
|                       <i class="fe fe-search"></i> | ||||
|                     </span> | ||||
|                     <input name="source-query" type="text" value="" class="form-control form-control-sm" placeholder="<%- i18n('access-lists', 'search') %>" aria-label="<%- i18n('access-lists', 'search') %>"> | ||||
|                 </div> | ||||
|             </form> | ||||
|             <a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a> | ||||
|             <% if (showAddButton) { %> | ||||
|             <a href="#" class="btn btn-outline-teal btn-sm ml-2 add-item"><%- i18n('access-lists', 'add') %></a> | ||||
|             <% } %> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div class="card-body no-padding min-100"> | ||||
|         <div class="dimmer active"> | ||||
|             <div class="loader"></div> | ||||
|             <div class="dimmer-content list-region"> | ||||
|                 <!-- List Region --> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,108 +0,0 @@ | ||||
| const Mn              = require('backbone.marionette'); | ||||
| const App             = require('../../main'); | ||||
| const AccessListModel = require('../../../models/access-list'); | ||||
| const ListView        = require('./list/main'); | ||||
| const ErrorView       = require('../../error/main'); | ||||
| const EmptyView       = require('../../empty/main'); | ||||
| const template        = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     id:       'nginx-access', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         add:         '.add-item', | ||||
|         help:        '.help', | ||||
|         dimmer:      '.dimmer', | ||||
|         search:      '.search-form', | ||||
|         query:       'input[name="source-query"]' | ||||
|     }, | ||||
|  | ||||
|     fetch: App.Api.Nginx.AccessLists.getAll, | ||||
|  | ||||
|     showData: function(response) { | ||||
|         this.showChildView('list_region', new ListView({ | ||||
|             collection: new AccessListModel.Collection(response) | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     showError: function(err) { | ||||
|         this.showChildView('list_region', new ErrorView({ | ||||
|             code:    err.code, | ||||
|             message: err.message, | ||||
|             retry:   function () { | ||||
|                 App.Controller.showNginxAccess(); | ||||
|             } | ||||
|         })); | ||||
|  | ||||
|         console.error(err); | ||||
|     }, | ||||
|  | ||||
|     showEmpty: function() { | ||||
|         let manage = App.Cache.User.canManage('access_lists'); | ||||
|  | ||||
|         this.showChildView('list_region', new EmptyView({ | ||||
|             title:      App.i18n('access-lists', 'empty'), | ||||
|             subtitle:   App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), | ||||
|             link:       manage ? App.i18n('access-lists', 'add') : null, | ||||
|             btn_color:  'teal', | ||||
|             permission: 'access_lists', | ||||
|             action:     function () { | ||||
|                 App.Controller.showNginxAccessListForm(); | ||||
|             } | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.add': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxAccessListForm(); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.help': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showHelp(App.i18n('access-lists', 'help-title'), App.i18n('access-lists', 'help-content')); | ||||
|         }, | ||||
|  | ||||
|         'submit @ui.search': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let query = this.ui.query.val(); | ||||
|  | ||||
|             this.fetch(['owner', 'items', 'clients'], query) | ||||
|                 .then(response => this.showData(response)) | ||||
|                 .catch(err => { | ||||
|                     this.showError(err); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         showAddButton: App.Cache.User.canManage('access_lists') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         view.fetch(['owner', 'items', 'clients']) | ||||
|             .then(response => { | ||||
|                 if (!view.isDestroyed()) { | ||||
|                     if (response && response.length) { | ||||
|                         view.showData(response); | ||||
|                     } else { | ||||
|                         view.showEmpty(); | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 view.showError(err); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 view.ui.dimmer.removeClass('active'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
| @@ -1,18 +0,0 @@ | ||||
| <div> | ||||
|     <% if (id === 'new') { %> | ||||
|         <div class="title"> | ||||
|             <i class="fe fe-shield text-success"></i> <%- i18n('all-hosts', 'new-cert') %> | ||||
|         </div> | ||||
|         <span class="description"><%- i18n('all-hosts', 'with-le') %></span> | ||||
|     <% } else if (id > 0) { %> | ||||
|         <div class="title"> | ||||
|             <i class="fe fe-shield text-pink"></i> <%- provider === 'other' ? nice_name : domain_names.join(', ') %> | ||||
|         </div> | ||||
|         <span class="description"><%- i18n('ssl', provider) %> – Expires: <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %></span> | ||||
|     <% } else { %> | ||||
|         <div class="title"> | ||||
|             <i class="fe fe-shield-off text-danger"></i> <%- i18n('all-hosts', 'none') %> | ||||
|         </div> | ||||
|         <span class="description"><%- i18n('all-hosts', 'no-ssl') %></span> | ||||
|     <% } %> | ||||
| </div> | ||||
| @@ -1,19 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('certificates', 'delete') %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <form> | ||||
|             <div class="row"> | ||||
|                 <div class="col-sm-12 col-md-12"> | ||||
|                     <%= i18n('certificates', 'delete-confirm') %> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </form> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||
|         <button type="button" class="btn btn-danger save"><%- i18n('str', 'sure') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,34 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         form:    'form', | ||||
|         buttons: '.modal-footer button', | ||||
|         cancel:  'button.cancel', | ||||
|         save:    'button.save' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|             this.ui.save.addClass('btn-loading'); | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|  | ||||
|             App.Api.Nginx.Certificates.delete(this.model.get('id')) | ||||
|                 .then(() => { | ||||
|                     App.Controller.showNginxCertificates(); | ||||
|                     App.UI.closeModal(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                     this.ui.save.removeClass('btn-loading'); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -1,185 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('certificates', 'form-title', {provider: provider}) %></h5> | ||||
|         <button type="button" class="close cancel non-loader-content" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div> | ||||
|         <div class="text-center loader-content"> | ||||
|             <div class="loader mx-auto my-6"></div> | ||||
|             <p><%- i18n('ssl', 'processing-info') %></p> | ||||
|         </div> | ||||
|         <form class="non-loader-content"> | ||||
|             <div class="row"> | ||||
|                 <% if (provider === 'letsencrypt') { %> | ||||
|                     <div class="col-sm-12 col-md-12"> | ||||
|                         <div class="form-group"> | ||||
|                             <label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label> | ||||
|                             <input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>" required> | ||||
|                             <div class="text-blue"><i class="fe fe-alert-triangle"></i> <%- i18n('ssl', 'hosts-warning') %></div> | ||||
|                         </div> | ||||
|  | ||||
|                         <div class="mb-3 test-domains-container"> | ||||
|                             <button type="button" class="btn btn-secondary test-domains col-sm-12"><%- i18n('certificates', 'test-reachability') %></button> | ||||
|                             <div class="text-secondary small"> | ||||
|                                 <i class="fe fe-info"></i> | ||||
|                                 <%- i18n('certificates', 'reachability-info') %> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="col-sm-12 col-md-12"> | ||||
|                         <div class="form-group"> | ||||
|                             <label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label> | ||||
|                             <input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required> | ||||
|                         </div> | ||||
|                     </div> | ||||
|  | ||||
|                     <!-- DNS challenge --> | ||||
|                     <div class="col-sm-12 col-md-12"> | ||||
|                         <div class="form-group"> | ||||
|                             <label class="custom-switch"> | ||||
|                                 <input | ||||
|                                     type="checkbox" | ||||
|                                     class="custom-switch-input" | ||||
|                                     name="meta[dns_challenge]" | ||||
|                                     value="1" | ||||
|                                     <%- getUseDnsChallenge() ? 'checked' : '' %> | ||||
|                                 > | ||||
|                                 <span class="custom-switch-indicator"></span> | ||||
|                                 <span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span> | ||||
|                             </label> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="col-sm-12 col-md-12"> | ||||
|                         <fieldset class="form-fieldset dns-challenge"> | ||||
|                             <div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div> | ||||
|  | ||||
|                             <!-- Certbot DNS plugin selection --> | ||||
|                             <div class="row"> | ||||
|                                 <div class="col-sm-12 col-md-12"> | ||||
|                                     <div class="form-group"> | ||||
|                                         <label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label> | ||||
|                                         <select | ||||
|                                             name="meta[dns_provider]" | ||||
|                                             id="dns_provider" | ||||
|                                             class="form-control custom-select" | ||||
|                                         > | ||||
|                                             <option | ||||
|                                                 value="" | ||||
|                                                 disabled | ||||
|                                                 hidden | ||||
|                                                 <%- getDnsProvider() === null ? 'selected' : '' %> | ||||
|                                             >Please Choose...</option> | ||||
|                                             <% _.each(dns_plugins, function(plugin_info, plugin_name){ %> | ||||
|                                             <option | ||||
|                                                 value="<%- plugin_name %>" | ||||
|                                                 <%- getDnsProvider() === plugin_name ? 'selected' : '' %> | ||||
|                                             ><%- plugin_info.name %></option> | ||||
|                                             <% }); %> | ||||
|                                         </select> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|  | ||||
|                             <!-- Certbot credentials file content --> | ||||
|                             <div class="row credentials-file-content"> | ||||
|                                 <div class="col-sm-12 col-md-12"> | ||||
|                                     <div class="form-group"> | ||||
|                                         <label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label> | ||||
|                                         <textarea | ||||
|                                             name="meta[dns_provider_credentials]" | ||||
|                                             class="form-control text-monospace" | ||||
|                                             id="dns_provider_credentials" | ||||
|                                         ><%- getDnsProviderCredentials() %></textarea> | ||||
|                                         <div class="text-secondary small"> | ||||
|                                             <i class="fe fe-info"></i> | ||||
|                                             <%= i18n('ssl', 'credentials-file-content-info') %> | ||||
|                                         </div> | ||||
|                                         <div class="text-red small"> | ||||
|                                             <i class="fe fe-alert-triangle"></i> | ||||
|                                             <%= i18n('ssl', 'stored-as-plaintext-info') %> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|  | ||||
|                             <!-- DNS propagation delay --> | ||||
|                             <div class="row"> | ||||
|                                 <div class="col-sm-12 col-md-12"> | ||||
|                                     <div class="form-group mb-0"> | ||||
|                                         <label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label> | ||||
|                                         <input | ||||
|                                             type="number" | ||||
|                                             min="0" | ||||
|                                             name="meta[propagation_seconds]" | ||||
|                                             class="form-control" | ||||
|                                             id="propagation_seconds" | ||||
|                                             value="<%- getPropagationSeconds() %>" | ||||
|                                         > | ||||
|                                         <div class="text-secondary small"> | ||||
|                                             <i class="fe fe-info"></i> | ||||
|                                             <%= i18n('ssl', 'propagation-seconds-info') %> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </div> | ||||
|                         </fieldset> | ||||
|                     </div> | ||||
|  | ||||
|                     <div class="col-sm-12 col-md-12"> | ||||
|                         <div class="form-group"> | ||||
|                             <label class="custom-switch"> | ||||
|                                 <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required<%- getLetsencryptAgree() ? ' checked' : '' %>> | ||||
|                                 <span class="custom-switch-indicator"></span> | ||||
|                                 <span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span> | ||||
|                             </label> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 <% } else if (provider === 'other') { %> | ||||
|                     <!-- Other --> | ||||
|                     <div class="col-sm-12 col-md-12"> | ||||
|                         <div class="text-blue mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'passphrase-protection-support-info') %></div> | ||||
|                     </div> | ||||
|                     <div class="col-sm-12 col-md-12"> | ||||
|                         <div class="form-group"> | ||||
|                             <label class="form-label"><%- i18n('str', 'name') %> <span class="form-required">*</span></label> | ||||
|                             <input name="nice_name" type="text" class="form-control" placeholder="" value="<%- nice_name %>" required> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="col-sm-12 col-md-12 other-ssl"> | ||||
|                         <div class="form-group"> | ||||
|                             <div class="form-label"><%- i18n('certificates', 'other-certificate-key') %><span class="form-required">*</span></div> | ||||
|                             <div class="custom-file"> | ||||
|                                 <input type="file" class="custom-file-input" name="meta[other_certificate_key]" id="other_certificate_key" required> | ||||
|                                 <label id="other_certificate_key_label" class="custom-file-label"><%- i18n('str', 'choose-file') %></label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="col-sm-12 col-md-12 other-ssl"> | ||||
|                         <div class="form-group"> | ||||
|                             <div class="form-label"><%- i18n('certificates', 'other-certificate') %><span class="form-required">*</span></div> | ||||
|                             <div class="custom-file"> | ||||
|                                 <input type="file" class="custom-file-input" name="meta[other_certificate]" id="other_certificate"> | ||||
|                                 <label id="other_certificate_label" class="custom-file-label"><%- i18n('str', 'choose-file') %></label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="col-sm-12 col-md-12 other-ssl"> | ||||
|                         <div class="form-group"> | ||||
|                             <div class="form-label"><%- i18n('certificates', 'other-intermediate-certificate') %></div> | ||||
|                             <div class="custom-file"> | ||||
|                                 <input type="file" class="custom-file-input" name="meta[other_intermediate_certificate]" id="other_intermediate_certificate"> | ||||
|                                 <label id="other_intermediate_certificate_label" class="custom-file-label"><%- i18n('str', 'choose-file') %></label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|  | ||||
|                 <% } %> | ||||
|             </div> | ||||
|         </form> | ||||
|     </div> | ||||
|     <div class="modal-footer non-loader-content"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||
|         <button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,294 +0,0 @@ | ||||
| const _                = require('underscore'); | ||||
| const Mn               = require('backbone.marionette'); | ||||
| const App              = require('../../main'); | ||||
| const CertificateModel = require('../../../models/certificate'); | ||||
| const template         = require('./form.ejs'); | ||||
| const i18n             = require('../../i18n'); | ||||
| const dns_providers    = sortProvidersAlphabetically(require('../../../../../global/certbot-dns-plugins')); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
| require('selectize'); | ||||
|  | ||||
| function sortProvidersAlphabetically(obj) { | ||||
|     return Object.entries(obj) | ||||
|         .sort((a,b) => a[1].name.toLowerCase() > b[1].name.toLowerCase()) | ||||
|         .reduce((result, entry) => { | ||||
|             result[entry[0]] = entry[1]; | ||||
|             return result; | ||||
|         }, {}); | ||||
| } | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:      template, | ||||
|     className:     'modal-dialog', | ||||
|     max_file_size: 102400, | ||||
|  | ||||
|     ui: { | ||||
|         form:                                 'form', | ||||
|         loader_content:                       '.loader-content', | ||||
|         non_loader_content:                   '.non-loader-content', | ||||
|         le_error_info:                        '#le-error-info', | ||||
|         domain_names:                         'input[name="domain_names"]', | ||||
|         test_domains_container:               '.test-domains-container', | ||||
|         test_domains_button:                  '.test-domains', | ||||
|         buttons:                              '.modal-footer button', | ||||
|         cancel:                               'button.cancel', | ||||
|         save:                                 'button.save', | ||||
|         other_certificate:                    '#other_certificate', | ||||
|         other_certificate_label:              '#other_certificate_label', | ||||
|         other_certificate_key:                '#other_certificate_key', | ||||
|         dns_challenge_switch:                 'input[name="meta[dns_challenge]"]', | ||||
|         dns_challenge_content:                '.dns-challenge', | ||||
|         dns_provider:                         'select[name="meta[dns_provider]"]', | ||||
|         credentials_file_content:             '.credentials-file-content', | ||||
|         dns_provider_credentials:             'textarea[name="meta[dns_provider_credentials]"]', | ||||
|         propagation_seconds:                  'input[name="meta[propagation_seconds]"]', | ||||
|         other_certificate_key_label:          '#other_certificate_key_label', | ||||
|         other_intermediate_certificate:       '#other_intermediate_certificate', | ||||
|         other_intermediate_certificate_label: '#other_intermediate_certificate_label' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'change @ui.dns_challenge_switch': function () { | ||||
|             const checked = this.ui.dns_challenge_switch.prop('checked'); | ||||
|             if (checked) { | ||||
|                 this.ui.dns_provider.prop('required', 'required'); | ||||
|                 const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; | ||||
|                 if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ | ||||
|                     this.ui.dns_provider_credentials.prop('required', 'required'); | ||||
|                 } | ||||
|                 this.ui.dns_challenge_content.show(); | ||||
|                 this.ui.test_domains_container.hide(); | ||||
|             } else { | ||||
|                 this.ui.dns_provider.prop('required', false); | ||||
|                 this.ui.dns_provider_credentials.prop('required', false); | ||||
|                 this.ui.dns_challenge_content.hide(); | ||||
|                 this.ui.test_domains_container.show(); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'change @ui.dns_provider': function () { | ||||
|             const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; | ||||
|             if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { | ||||
|                 this.ui.dns_provider_credentials.prop('required', 'required'); | ||||
|                 this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; | ||||
|                 this.ui.credentials_file_content.show(); | ||||
|             } else { | ||||
|                 this.ui.dns_provider_credentials.prop('required', false); | ||||
|                 this.ui.credentials_file_content.hide(); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|             this.ui.le_error_info.hide(); | ||||
|  | ||||
|             if (!this.ui.form[0].checkValidity()) { | ||||
|                 $('<input type="submit">').hide().appendTo(this.ui.form).click().remove(); | ||||
|                 $(this).removeClass('btn-loading'); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let data      = this.ui.form.serializeJSON(); | ||||
|             data.provider = this.model.get('provider'); | ||||
|             let ssl_files = []; | ||||
|  | ||||
|             if (data.provider === 'letsencrypt') { | ||||
|                 if (typeof data.meta === 'undefined') data.meta = {}; | ||||
|  | ||||
|                 let domain_err = false; | ||||
|                 if (!data.meta.dns_challenge) { | ||||
|                     data.domain_names.split(',').map(function (name) { | ||||
|                         if (name.match(/\*/im)) { | ||||
|                             domain_err = true; | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 if (domain_err) { | ||||
|                     alert(i18n('ssl', 'no-wildcard-without-dns')); | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 // Manipulate | ||||
|                 data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; | ||||
|                 data.meta.dns_challenge = data.meta.dns_challenge == 1; | ||||
|  | ||||
|                 if(!data.meta.dns_challenge){ | ||||
|                     data.meta.dns_provider = undefined; | ||||
|                     data.meta.dns_provider_credentials = undefined; | ||||
|                     data.meta.propagation_seconds = undefined; | ||||
|                 } else { | ||||
|                     if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined; | ||||
|                 } | ||||
|  | ||||
|                 if (typeof data.domain_names === 'string' && data.domain_names) { | ||||
|                     data.domain_names = data.domain_names.split(','); | ||||
|                 } | ||||
|             } else if (data.provider === 'other' && !this.model.hasSslFiles()) { | ||||
|                 // check files are attached | ||||
|                 if (!this.ui.other_certificate[0].files.length || !this.ui.other_certificate[0].files[0].size) { | ||||
|                     alert('Certificate file is not attached'); | ||||
|                     return; | ||||
|                 } else { | ||||
|                     if (this.ui.other_certificate[0].files[0].size > this.max_file_size) { | ||||
|                         alert('Certificate file is too large (> 100kb)'); | ||||
|                         return; | ||||
|                     } | ||||
|                     ssl_files.push({name: 'certificate', file: this.ui.other_certificate[0].files[0]}); | ||||
|                 } | ||||
|  | ||||
|                 if (!this.ui.other_certificate_key[0].files.length || !this.ui.other_certificate_key[0].files[0].size) { | ||||
|                     alert('Certificate key file is not attached'); | ||||
|                     return; | ||||
|                 } else { | ||||
|                     if (this.ui.other_certificate_key[0].files[0].size > this.max_file_size) { | ||||
|                         alert('Certificate key file is too large (> 100kb)'); | ||||
|                         return; | ||||
|                     } | ||||
|                     ssl_files.push({name: 'certificate_key', file: this.ui.other_certificate_key[0].files[0]}); | ||||
|                 } | ||||
|  | ||||
|                 if (this.ui.other_intermediate_certificate[0].files.length && this.ui.other_intermediate_certificate[0].files[0].size) { | ||||
|                     if (this.ui.other_intermediate_certificate[0].files[0].size > this.max_file_size) { | ||||
|                         alert('Intermediate Certificate file is too large (> 100kb)'); | ||||
|                         return; | ||||
|                     } | ||||
|                     ssl_files.push({name: 'intermediate_certificate', file: this.ui.other_intermediate_certificate[0].files[0]}); | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             this.ui.loader_content.show(); | ||||
|             this.ui.non_loader_content.hide(); | ||||
|  | ||||
|             // compile file data | ||||
|             let form_data = new FormData(); | ||||
|             if (data.provider === 'other' && ssl_files.length) { | ||||
|                 ssl_files.map(function (file) { | ||||
|                     form_data.append(file.name, file.file); | ||||
|                 }); | ||||
|             } | ||||
|  | ||||
|             new Promise(resolve => { | ||||
|                 if (data.provider === 'other') { | ||||
|                     resolve(App.Api.Nginx.Certificates.validate(form_data)); | ||||
|                 } else { | ||||
|                     resolve(); | ||||
|                 } | ||||
|             }) | ||||
|                 .then(() => { | ||||
|                     return App.Api.Nginx.Certificates.create(data); | ||||
|                 }) | ||||
|                 .then(result => { | ||||
|                     this.model.set(result); | ||||
|  | ||||
|                     // Now upload the certs if we need to | ||||
|                     if (data.provider === 'other') { | ||||
|                         return App.Api.Nginx.Certificates.upload(this.model.get('id'), form_data) | ||||
|                             .then(result => { | ||||
|                                 this.model.set('meta', _.assign({}, this.model.get('meta'), result)); | ||||
|                             }); | ||||
|                     } | ||||
|                 }) | ||||
|                 .then(() => { | ||||
|                     App.UI.closeModal(function () { | ||||
|                         App.Controller.showNginxCertificates(); | ||||
|                     }); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     let more_info = ''; | ||||
|                     if (err.code === 500 && err.debug) { | ||||
|                         try{ | ||||
|                             more_info = JSON.parse(err.debug).debug.stack.join("\n"); | ||||
|                         } catch(e) {} | ||||
|                     } | ||||
|                     this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`; | ||||
|                     this.ui.le_error_info.show(); | ||||
|                     this.ui.le_error_info[0].scrollIntoView(); | ||||
|                     this.ui.loader_content.hide(); | ||||
|                     this.ui.non_loader_content.show(); | ||||
|                 }); | ||||
|         }, | ||||
|         'click @ui.test_domains_button': function (e) { | ||||
|             e.preventDefault(); | ||||
|             const domainNames = this.ui.domain_names[0].value.split(','); | ||||
|             if (domainNames && domainNames.length > 0) { | ||||
|                 this.model.set('domain_names', domainNames); | ||||
|                 this.model.set('back_to_add', true); | ||||
|                 App.Controller.showNginxCertificateTestReachability(this.model); | ||||
|             } | ||||
|         }, | ||||
|         'change @ui.domain_names': function(e){ | ||||
|             const domainNames = e.target.value.split(','); | ||||
|             if (domainNames && domainNames.length > 0) { | ||||
|                 this.ui.test_domains_button.prop('disabled', false); | ||||
|             } else { | ||||
|                 this.ui.test_domains_button.prop('disabled', true); | ||||
|             } | ||||
|         }, | ||||
|         'change @ui.other_certificate_key': function(e){ | ||||
|             this.setFileName("other_certificate_key_label", e) | ||||
|         }, | ||||
|         'change @ui.other_certificate': function(e){ | ||||
|             this.setFileName("other_certificate_label", e) | ||||
|         }, | ||||
|         'change @ui.other_intermediate_certificate': function(e){ | ||||
|             this.setFileName("other_intermediate_certificate_label", e) | ||||
|         } | ||||
|     }, | ||||
|     setFileName(ui, e){ | ||||
|         this.getUI(ui).text(e.target.files[0].name) | ||||
|     }, | ||||
|     templateContext: { | ||||
|         getLetsencryptEmail: function () { | ||||
|             return typeof this.meta.letsencrypt_email !== 'undefined' ? this.meta.letsencrypt_email : App.Cache.User.get('email'); | ||||
|         }, | ||||
|         getLetsencryptAgree: function () { | ||||
|             return typeof this.meta.letsencrypt_agree !== 'undefined' ? this.meta.letsencrypt_agree : false; | ||||
|         }, | ||||
|         getUseDnsChallenge: function () { | ||||
|             return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; | ||||
|         }, | ||||
|         getDnsProvider: function () { | ||||
|             return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; | ||||
|         }, | ||||
|         getDnsProviderCredentials: function () { | ||||
|             return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; | ||||
|         }, | ||||
|         getPropagationSeconds: function () { | ||||
|             return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; | ||||
|         }, | ||||
|         dns_plugins: dns_providers, | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.ui.domain_names.selectize({ | ||||
|             delimiter:    ',', | ||||
|             persist:      false, | ||||
|             maxOptions:   100, | ||||
|             create:       function (input) { | ||||
|                 return { | ||||
|                     value: input, | ||||
|                     text:  input | ||||
|                 }; | ||||
|             }, | ||||
|             createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ | ||||
|         }); | ||||
|         this.ui.dns_challenge_content.hide(); | ||||
|         this.ui.credentials_file_content.hide(); | ||||
|         this.ui.loader_content.hide(); | ||||
|         this.ui.le_error_info.hide(); | ||||
|         if (this.ui.domain_names[0]) { | ||||
|             const domainNames = this.ui.domain_names[0].value.split(','); | ||||
|             if (!domainNames || domainNames.length === 0 || (domainNames.length === 1 && domainNames[0] === "")) { | ||||
|                 this.ui.test_domains_button.prop('disabled', true); | ||||
|             } | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     initialize: function (options) { | ||||
|         if (typeof options.model === 'undefined' || !options.model) { | ||||
|             this.model = new CertificateModel.Model({provider: 'letsencrypt'}); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -1,68 +0,0 @@ | ||||
| <td class="text-center"> | ||||
|     <div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>"> | ||||
|         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div class="wrap"> | ||||
|         <% | ||||
|         if (provider === 'letsencrypt') { | ||||
|             domain_names.map(function(host) { | ||||
|                 if (host.indexOf('*') === -1) { | ||||
|                     %> | ||||
|                     <span class="tag host-link hover-pink" rel="https://<%- host %>"><%- host %></span> | ||||
|                     <% | ||||
|                 } else { | ||||
|                     %> | ||||
|                     <span class="tag"><%- host %></span> | ||||
|                     <% | ||||
|                 } | ||||
|             }); | ||||
|         } else { | ||||
|             %><%- nice_name %><% | ||||
|         } | ||||
|         %> | ||||
|     </div> | ||||
|     <div class="small text-muted"> | ||||
|         <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <%- i18n('ssl', provider) %><% if (meta.dns_provider) { %> - <%- dns_providers[meta.dns_provider].name %><% } %> | ||||
| </td> | ||||
| <td class="<%- isExpired() ? 'text-danger' : '' %>"> | ||||
|     <%- formatDbDate(expires_on, 'Do MMMM YYYY, h:mm a') %> | ||||
| </td> | ||||
| <td> | ||||
|     <% if (active_domain_names().length > 0) { %> | ||||
|         <span class="status-icon bg-success"></span> <%- i18n('certificates', 'in-use') %> | ||||
|     <% } else { %> | ||||
|         <span class="status-icon bg-danger"></span> <%- i18n('certificates', 'inactive') %> | ||||
|     <% } %> | ||||
| </td> | ||||
| <% if (canManage) { %> | ||||
| <td class="text-right"> | ||||
|     <div class="item-action dropdown"> | ||||
|         <a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a> | ||||
|         <div class="dropdown-menu dropdown-menu-right"> | ||||
|             <span class="dropdown-header"><%- i18n('audit-log', 'certificate') %> #<%- id %></span> | ||||
|             <% if (provider === 'letsencrypt') { %> | ||||
|                 <a href="#" class="renew dropdown-item"><i class="dropdown-icon fe fe-refresh-cw"></i> <%- i18n('certificates', 'force-renew') %></a> | ||||
|                 <a href="#" class="download dropdown-item"><i class="dropdown-icon fe fe-download"></i> <%- i18n('certificates', 'download') %></a> | ||||
|                 <% if (meta.dns_challenge === false) { %> | ||||
|                     <a href="#" class="test dropdown-item"><i class="dropdown-icon fe fe-globe"></i> <%- i18n('certificates', 'test-reachability') %></a> | ||||
|                 <% } %> | ||||
|                 <div class="dropdown-divider"></div> | ||||
|             <% } %> | ||||
|             <a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a> | ||||
|             <% if (active_domain_names().length > 0) { %> | ||||
|                 <div class="dropdown-divider"></div> | ||||
|                 <span class="dropdown-header"><%- i18n('certificates', 'active-domain_names') %></span> | ||||
|                 <% active_domain_names().forEach(function(host) { %> | ||||
|                     <a href="https://<%- host %>" class="dropdown-item" target="_blank"><%- host %></a> | ||||
|                 <% }); %> | ||||
|             <% } %> | ||||
|         </div> | ||||
|     </div> | ||||
| </td> | ||||
| <% } %> | ||||
| @@ -1,68 +0,0 @@ | ||||
| const Mn            = require('backbone.marionette'); | ||||
| const moment        = require('moment'); | ||||
| const App           = require('../../../main'); | ||||
| const template      = require('./item.ejs'); | ||||
| const dns_providers = require('../../../../../../global/certbot-dns-plugins'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         host_link: '.host-link', | ||||
|         renew:     'a.renew', | ||||
|         delete:    'a.delete', | ||||
|         download:  'a.download', | ||||
|         test:      'a.test' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.renew': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxCertificateRenew(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxCertificateDeleteConfirm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.host_link': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let win = window.open($(e.currentTarget).attr('rel'), '_blank'); | ||||
|             win.focus(); | ||||
|         }, | ||||
|                  | ||||
|         'click @ui.download': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Api.Nginx.Certificates.download(this.model.get('id')); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.test': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxCertificateTestReachability(this.model); | ||||
|         }, | ||||
|     }, | ||||
|  | ||||
|     templateContext: function () { | ||||
|         return { | ||||
|             canManage: App.Cache.User.canManage('certificates'), | ||||
|             isExpired: function () { | ||||
|                 return moment(this.expires_on).isBefore(moment()); | ||||
|             }, | ||||
|             dns_providers: dns_providers, | ||||
|             active_domain_names: function () { | ||||
|                 const { proxy_hosts = [], redirect_hosts = [], dead_hosts = [] } = this; | ||||
|                 return [...proxy_hosts, ...redirect_hosts, ...dead_hosts].reduce((acc, host) => { | ||||
|                     acc.push(...(host.domain_names || [])); | ||||
|                     return acc; | ||||
|                 }, []); | ||||
|             } | ||||
|         }; | ||||
|     }, | ||||
|  | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
| @@ -1,13 +0,0 @@ | ||||
| <thead> | ||||
|     <th width="30"> </th> | ||||
|     <th><%- i18n('str', 'name') %></th> | ||||
|     <th><%- i18n('all-hosts', 'cert-provider') %></th> | ||||
|     <th><%- i18n('str', 'expires') %></th> | ||||
|     <th><%- i18n('str', 'status') %></th> | ||||
|     <% if (canManage) { %> | ||||
|     <th> </th> | ||||
|     <% } %> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
| @@ -1,32 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const ItemView = require('./item'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| const TableBody = Mn.CollectionView.extend({ | ||||
|     tagName:   'tbody', | ||||
|     childView: ItemView | ||||
| }); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     tagName:   'table', | ||||
|     className: 'table table-hover table-outline table-vcenter card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('certificates') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
| @@ -1,36 +0,0 @@ | ||||
| <div class="card"> | ||||
|     <div class="card-status bg-pink"></div> | ||||
|     <div class="card-header"> | ||||
|         <h3 class="card-title"><%- i18n('certificates', 'title') %></h3> | ||||
|         <div class="card-options"> | ||||
|             <form class="search-form" role="search"> | ||||
|                 <div class="input-icon"> | ||||
|                     <span class="input-icon-addon"> | ||||
|                       <i class="fe fe-search"></i> | ||||
|                     </span> | ||||
|                     <input name="source-query" type="text" value="" class="form-control form-control-sm" placeholder="<%- i18n('certificates', 'search') %>" aria-label="<%- i18n('certificates', 'search') %>"> | ||||
|                 </div> | ||||
|             </form> | ||||
|             <a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a> | ||||
|             <% if (showAddButton) { %> | ||||
|             <div class="dropdown"> | ||||
|                 <button type="button" class="btn btn-outline-pink btn-sm ml-2 dropdown-toggle" data-toggle="dropdown"> | ||||
|                     <%- i18n('certificates', 'add') %> | ||||
|                 </button> | ||||
|                 <div class="dropdown-menu"> | ||||
|                     <a class="dropdown-item add-item" data-cert="letsencrypt" href="#"><%- i18n('ssl', 'letsencrypt') %></a> | ||||
|                     <a class="dropdown-item add-item" data-cert="other" href="#"><%- i18n('ssl', 'other') %></a> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <% } %> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div class="card-body no-padding min-100"> | ||||
|         <div class="dimmer active"> | ||||
|             <div class="loader"></div> | ||||
|             <div class="dimmer-content list-region"> | ||||
|                 <!-- List Region --> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,109 +0,0 @@ | ||||
| const Mn               = require('backbone.marionette'); | ||||
| const App              = require('../../main'); | ||||
| const CertificateModel = require('../../../models/certificate'); | ||||
| const ListView         = require('./list/main'); | ||||
| const ErrorView        = require('../../error/main'); | ||||
| const EmptyView        = require('../../empty/main'); | ||||
| const template         = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     id:       'nginx-certificates', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         add:         '.add-item', | ||||
|         help:        '.help', | ||||
|         dimmer:      '.dimmer', | ||||
|         search:      '.search-form', | ||||
|         query:       'input[name="source-query"]' | ||||
|     }, | ||||
|  | ||||
|     fetch: App.Api.Nginx.Certificates.getAll, | ||||
|  | ||||
|     showData: function(response) { | ||||
|         this.showChildView('list_region', new ListView({ | ||||
|             collection: new CertificateModel.Collection(response) | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     showError: function(err) { | ||||
|         this.showChildView('list_region', new ErrorView({ | ||||
|             code:    err.code, | ||||
|             message: err.message, | ||||
|             retry:   function () { | ||||
|                 App.Controller.showNginxCertificates(); | ||||
|             } | ||||
|         })); | ||||
|  | ||||
|         console.error(err); | ||||
|     }, | ||||
|  | ||||
|     showEmpty: function() { | ||||
|         let manage = App.Cache.User.canManage('certificates'); | ||||
|  | ||||
|         this.showChildView('list_region', new EmptyView({ | ||||
|             title:      App.i18n('certificates', 'empty'), | ||||
|             subtitle:   App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), | ||||
|             link:       manage ? App.i18n('certificates', 'add') : null, | ||||
|             btn_color:  'pink', | ||||
|             permission: 'certificates', | ||||
|             action:     function () { | ||||
|                 App.Controller.showNginxCertificateForm(); | ||||
|             } | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.add': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let model = new CertificateModel.Model({provider: $(e.currentTarget).data('cert')}); | ||||
|             App.Controller.showNginxCertificateForm(model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.help': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showHelp(App.i18n('certificates', 'help-title'), App.i18n('certificates', 'help-content')); | ||||
|         }, | ||||
|  | ||||
|         'submit @ui.search': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let query = this.ui.query.val(); | ||||
|  | ||||
|             this.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts'], query) | ||||
|                 .then(response => this.showData(response)) | ||||
|                 .catch(err => { | ||||
|                     this.showError(err); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         showAddButton: App.Cache.User.canManage('certificates') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         view.fetch(['owner','proxy_hosts', 'dead_hosts', 'redirection_hosts']) | ||||
|             .then(response => { | ||||
|                 if (!view.isDestroyed()) { | ||||
|                     if (response && response.length) { | ||||
|                         view.showData(response); | ||||
|                     } else { | ||||
|                         view.showEmpty(); | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 view.showError(err); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 view.ui.dimmer.removeClass('active'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
| @@ -1,14 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('certificates', 'renew-title') %></h5> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <div class="waiting text-center"> | ||||
|             <%= i18n('str', 'please-wait') %> | ||||
|         </div> | ||||
|         <div class="alert alert-danger error" role="alert"></div> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal" disabled><%- i18n('str', 'close') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,31 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./renew.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         waiting: '.waiting', | ||||
|         error:   '.error', | ||||
|         close:   'button.cancel' | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.ui.error.hide(); | ||||
|  | ||||
|         App.Api.Nginx.Certificates.renew(this.model.get('id')) | ||||
|             .then((result) => { | ||||
|                 this.model.set(result); | ||||
|                 setTimeout(() => { | ||||
|                     App.UI.closeModal(); | ||||
|                 }, 1000); | ||||
|             }) | ||||
|             .catch((err) => { | ||||
|                 this.ui.waiting.hide(); | ||||
|                 this.ui.error.text(err.message).show(); | ||||
|                 this.ui.close.prop('disabled', false); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
| @@ -1,15 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('certificates', 'reachability-title') %></h5> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <div class="waiting text-center"> | ||||
|             <%= i18n('str', 'please-wait') %> | ||||
|         </div> | ||||
|         <div class="alert alert-danger error" role="alert"></div> | ||||
|         <div class="alert alert-success success" role="alert"></div> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" disabled><%- i18n('str', 'close') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,75 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./test.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
| 	template:  template, | ||||
| 	className: 'modal-dialog', | ||||
|  | ||||
| 	ui: { | ||||
| 		waiting: '.waiting', | ||||
| 		error:   '.error', | ||||
| 		success: '.success', | ||||
| 		close:   'button.cancel' | ||||
| 	}, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.close': function (e) { | ||||
|             e.preventDefault(); | ||||
|             if (this.model.get('back_to_add')) { | ||||
|                 App.Controller.showNginxCertificateForm(this.model); | ||||
|             } else { | ||||
|                 App.UI.closeModal(); | ||||
|             } | ||||
|         }, | ||||
|     }, | ||||
|  | ||||
| 	onRender: function () { | ||||
| 		this.ui.error.hide(); | ||||
| 		this.ui.success.hide(); | ||||
|  | ||||
| 		App.Api.Nginx.Certificates.testHttpChallenge(this.model.get('domain_names')) | ||||
| 			.then((result) => { | ||||
| 				let allOk = true; | ||||
| 				let text  = ''; | ||||
|  | ||||
| 				for (const domain in result) { | ||||
| 					const status = result[domain]; | ||||
| 					if (status === 'ok') { | ||||
| 						text += `<p><strong>${domain}:</strong> ${App.i18n('certificates', 'reachability-ok')}</p>`; | ||||
| 					} else { | ||||
| 						allOk = false; | ||||
| 						if (status === 'no-host') { | ||||
| 							text += `<p><strong>${domain}:</strong> ${App.i18n('certificates', 'reachability-not-resolved')}</p>`; | ||||
| 						} else if (status === 'failed') { | ||||
| 							text += `<p><strong>${domain}:</strong> ${App.i18n('certificates', 'reachability-failed-to-check')}</p>`; | ||||
| 						} else if (status === '404') { | ||||
| 							text += `<p><strong>${domain}:</strong> ${App.i18n('certificates', 'reachability-404')}</p>`; | ||||
| 						} else if (status === 'wrong-data') { | ||||
| 							text += `<p><strong>${domain}:</strong> ${App.i18n('certificates', 'reachability-wrong-data')}</p>`; | ||||
| 						} else if (status.startsWith('other:')) { | ||||
| 							const code = status.substring(6); | ||||
| 							text      += `<p><strong>${domain}:</strong> ${App.i18n('certificates', 'reachability-other', {code})}</p>`; | ||||
| 						} else { | ||||
| 							// This should never happen | ||||
| 							text += `<p><strong>${domain}:</strong> ?</p>`; | ||||
| 						} | ||||
| 					} | ||||
| 				} | ||||
|  | ||||
| 				this.ui.waiting.hide(); | ||||
| 				if (allOk) { | ||||
| 					this.ui.success.html(text).show(); | ||||
| 				} else { | ||||
| 					this.ui.error.html(text).show(); | ||||
| 				} | ||||
| 				this.ui.close.prop('disabled', false); | ||||
| 			}) | ||||
| 			.catch((e) => { | ||||
| 				console.error(e); | ||||
| 				this.ui.waiting.hide(); | ||||
| 				this.ui.error.text(App.i18n('certificates', 'reachability-failed-to-reach-api')).show(); | ||||
| 				this.ui.close.prop('disabled', false); | ||||
| 			}); | ||||
| 	} | ||||
| }); | ||||
| @@ -1,23 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('dead-hosts', 'delete') %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <form> | ||||
|             <div class="row"> | ||||
|                 <div class="col-sm-12 col-md-12"> | ||||
|                     <%= i18n('dead-hosts', 'delete-confirm', {domains: domain_names.join(', ').toHtmlEntities()}) %> | ||||
|                     <% if (certificate_id) { %> | ||||
|                         <br><br> | ||||
|                         <%- i18n('ssl', 'delete-ssl') %> | ||||
|                     <% } %> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </form> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||
|         <button type="button" class="btn btn-danger save"><%- i18n('str', 'sure') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,32 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         form:    'form', | ||||
|         buttons: '.modal-footer button', | ||||
|         cancel:  'button.cancel', | ||||
|         save:    'button.save' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|  | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|  | ||||
|             App.Api.Nginx.DeadHosts.delete(this.model.get('id')) | ||||
|                 .then(() => { | ||||
|                     App.Controller.showNginxDead(); | ||||
|                     App.UI.closeModal(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -1,206 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('dead-hosts', 'form-title', {id: id}) %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body has-tabs"> | ||||
|         <div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div> | ||||
|         <form> | ||||
|             <ul class="nav nav-tabs" role="tablist"> | ||||
|                 <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li> | ||||
|                 <li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li> | ||||
|                 <li role="presentation" class="nav-item"><a href="#advanced" aria-controls="tab3" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-settings"></i> <%- i18n('all-hosts', 'advanced') %></a></li> | ||||
|             </ul> | ||||
|             <div class="tab-content"> | ||||
|                 <!-- Details --> | ||||
|                 <div role="tabpanel" class="tab-pane active" id="details"> | ||||
|                     <div class="row"> | ||||
|  | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label> | ||||
|                                 <input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- SSL --> | ||||
|                 <div role="tabpanel" class="tab-pane" id="ssl-options"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'ssl-certificate') %></label> | ||||
|                                 <select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>"> | ||||
|                                     <option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option> | ||||
|                                     <option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option> | ||||
|                                 </select> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="ssl_forced" value="1"<%- ssl_forced ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'force-ssl') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="http2_support" value="1"<%- http2_support ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'http2-support') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="hsts_enabled" value="1"<%- hsts_enabled ? ' checked' : '' %><%- certificate_id && ssl_forced ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'hsts-enabled') %> <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security" target="_blank"><i class="fe fe-help-circle"></i></a></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="hsts_subdomains" value="1"<%- hsts_subdomains ? ' checked' : '' %><%- certificate_id && ssl_forced && hsts_enabled ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'hsts-subdomains') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- DNS challenge --> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input | ||||
|                                         type="checkbox" | ||||
|                                         class="custom-switch-input" | ||||
|                                         name="meta[dns_challenge]" | ||||
|                                         value="1" | ||||
|                                         <%- getUseDnsChallenge() ? 'checked' : '' %> | ||||
|                                     > | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <fieldset class="form-fieldset dns-challenge"> | ||||
|                                 <div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div> | ||||
|  | ||||
|                                 <!-- Certbot DNS plugin selection --> | ||||
|                                 <div class="row"> | ||||
|                                     <div class="col-sm-12 col-md-12"> | ||||
|                                         <div class="form-group"> | ||||
|                                             <label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label> | ||||
|                                             <select | ||||
|                                                 name="meta[dns_provider]" | ||||
|                                                 id="dns_provider" | ||||
|                                                 class="form-control custom-select" | ||||
|                                             > | ||||
|                                                 <option | ||||
|                                                     value="" | ||||
|                                                     disabled | ||||
|                                                     hidden | ||||
|                                                     <%- getDnsProvider() === null ? 'selected' : '' %> | ||||
|                                                 >Please Choose...</option> | ||||
|                                                 <% _.each(dns_plugins, function(plugin_info, plugin_name){ %> | ||||
|                                                 <option | ||||
|                                                     value="<%- plugin_name %>" | ||||
|                                                     <%- getDnsProvider() === plugin_name ? 'selected' : '' %> | ||||
|                                                 ><%- plugin_info.name %></option> | ||||
|                                                 <% }); %> | ||||
|                                             </select> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|  | ||||
|                                 <!-- Certbot credentials file content --> | ||||
|                                 <div class="row credentials-file-content"> | ||||
|                                     <div class="col-sm-12 col-md-12"> | ||||
|                                         <div class="form-group"> | ||||
|                                             <label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label> | ||||
|                                             <textarea | ||||
|                                                 name="meta[dns_provider_credentials]" | ||||
|                                                 class="form-control text-monospace" | ||||
|                                                 id="dns_provider_credentials" | ||||
|                                             ><%- getDnsProviderCredentials() %></textarea> | ||||
|                                             <div class="text-secondary small"> | ||||
|                                                 <i class="fe fe-info"></i> | ||||
|                                                 <%= i18n('ssl', 'credentials-file-content-info') %> | ||||
|                                             </div> | ||||
|                                             <div class="text-red small"> | ||||
|                                                 <i class="fe fe-alert-triangle"></i> | ||||
|                                                 <%= i18n('ssl', 'stored-as-plaintext-info') %> | ||||
|                                             </div> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|  | ||||
|                                 <!-- DNS propagation delay --> | ||||
|                                 <div class="row"> | ||||
|                                     <div class="col-sm-12 col-md-12"> | ||||
|                                         <div class="form-group mb-0"> | ||||
|                                             <label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label> | ||||
|                                             <input | ||||
|                                                 type="number" | ||||
|                                                 min="0" | ||||
|                                                 name="meta[propagation_seconds]" | ||||
|                                                 class="form-control" | ||||
|                                                 id="propagation_seconds" | ||||
|                                                 value="<%- getPropagationSeconds() %>" | ||||
|                                             > | ||||
|                                             <div class="text-secondary small"> | ||||
|                                                 <i class="fe fe-info"></i> | ||||
|                                                 <%= i18n('ssl', 'propagation-seconds-info') %> | ||||
|                                             </div> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </fieldset> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- Lets encrypt --> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label> | ||||
|                                 <input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required disabled> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required disabled> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Advanced --> | ||||
|                 <div role="tabpanel" class="tab-pane" id="advanced"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-md-12"> | ||||
|                             <div class="form-group mb-0"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'advanced-config') %></label> | ||||
|                                 <textarea name="advanced_config" rows="8" class="form-control text-monospace" placeholder="# <%- i18n('all-hosts', 'advanced-warning') %>"><%- advanced_config %></textarea> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </form> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||
|         <button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,286 +0,0 @@ | ||||
| const Mn                   = require('backbone.marionette'); | ||||
| const App                  = require('../../main'); | ||||
| const DeadHostModel        = require('../../../models/dead-host'); | ||||
| const template             = require('./form.ejs'); | ||||
| const certListItemTemplate = require('../certificates-list-item.ejs'); | ||||
| const Helpers              = require('../../../lib/helpers'); | ||||
| const i18n                 = require('../../i18n'); | ||||
| const dns_providers        = require('../../../../../global/certbot-dns-plugins'); | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
| require('selectize'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         form:                     'form', | ||||
|         domain_names:             'input[name="domain_names"]', | ||||
|         buttons:                  '.modal-footer button', | ||||
|         cancel:                   'button.cancel', | ||||
|         save:                     'button.save', | ||||
|         le_error_info:            '#le-error-info', | ||||
|         certificate_select:       'select[name="certificate_id"]', | ||||
|         ssl_forced:               'input[name="ssl_forced"]', | ||||
|         hsts_enabled:             'input[name="hsts_enabled"]', | ||||
|         hsts_subdomains:          'input[name="hsts_subdomains"]', | ||||
|         http2_support:            'input[name="http2_support"]', | ||||
|         dns_challenge_switch:     'input[name="meta[dns_challenge]"]', | ||||
|         dns_challenge_content:    '.dns-challenge', | ||||
|         dns_provider:             'select[name="meta[dns_provider]"]', | ||||
|         credentials_file_content: '.credentials-file-content', | ||||
|         dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', | ||||
|         propagation_seconds:      'input[name="meta[propagation_seconds]"]', | ||||
|         letsencrypt:              '.letsencrypt' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'change @ui.certificate_select': function () { | ||||
|             let id = this.ui.certificate_select.val(); | ||||
|             if (id === 'new') { | ||||
|                 this.ui.letsencrypt.show().find('input').prop('disabled', false); | ||||
|                 this.ui.dns_challenge_content.hide(); | ||||
|             } else { | ||||
|                 this.ui.letsencrypt.hide().find('input').prop('disabled', true); | ||||
|             } | ||||
|  | ||||
|  | ||||
|             let enabled = id === 'new' || parseInt(id, 10) > 0; | ||||
|  | ||||
|             let inputs = this.ui.ssl_forced.add(this.ui.http2_support); | ||||
|             inputs | ||||
|                 .prop('disabled', !enabled) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', enabled ? 1 : 0.5); | ||||
|  | ||||
|             if (!enabled) { | ||||
|                 inputs.prop('checked', false); | ||||
|             } | ||||
|  | ||||
|             inputs.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.ssl_forced': function () { | ||||
|             let checked = this.ui.ssl_forced.prop('checked'); | ||||
|             this.ui.hsts_enabled | ||||
|                 .prop('disabled', !checked) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', checked ? 1 : 0.5); | ||||
|  | ||||
|             if (!checked) { | ||||
|                 this.ui.hsts_enabled.prop('checked', false); | ||||
|             } | ||||
|  | ||||
|             this.ui.hsts_enabled.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.hsts_enabled': function () { | ||||
|             let checked = this.ui.hsts_enabled.prop('checked'); | ||||
|             this.ui.hsts_subdomains | ||||
|                 .prop('disabled', !checked) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', checked ? 1 : 0.5); | ||||
|  | ||||
|             if (!checked) { | ||||
|                 this.ui.hsts_subdomains.prop('checked', false); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'change @ui.dns_challenge_switch': function () { | ||||
|             const checked = this.ui.dns_challenge_switch.prop('checked'); | ||||
|             if (checked) { | ||||
|                 this.ui.dns_provider.prop('required', 'required'); | ||||
|                 const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; | ||||
|                 if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ | ||||
|                     this.ui.dns_provider_credentials.prop('required', 'required'); | ||||
|                 } | ||||
|                 this.ui.dns_challenge_content.show(); | ||||
|             } else { | ||||
|                 this.ui.dns_provider.prop('required', false); | ||||
|                 this.ui.dns_provider_credentials.prop('required', false); | ||||
|                 this.ui.dns_challenge_content.hide();                 | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'change @ui.dns_provider': function () { | ||||
|             const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; | ||||
|             if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { | ||||
|                 this.ui.dns_provider_credentials.prop('required', 'required'); | ||||
|                 this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; | ||||
|                 this.ui.credentials_file_content.show(); | ||||
|             } else { | ||||
|                 this.ui.dns_provider_credentials.prop('required', false); | ||||
|                 this.ui.credentials_file_content.hide();                 | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|             this.ui.le_error_info.hide(); | ||||
|  | ||||
|             if (!this.ui.form[0].checkValidity()) { | ||||
|                 $('<input type="submit">').hide().appendTo(this.ui.form).click().remove(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let view = this; | ||||
|             let data = this.ui.form.serializeJSON(); | ||||
|  | ||||
|             // Manipulate | ||||
|             data.hsts_enabled       = !!data.hsts_enabled; | ||||
|             data.hsts_subdomains    = !!data.hsts_subdomains; | ||||
|             data.http2_support      = !!data.http2_support; | ||||
|             data.ssl_forced         = !!data.ssl_forced; | ||||
|  | ||||
|             if (typeof data.meta === 'undefined') data.meta = {}; | ||||
|             data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; | ||||
|             data.meta.dns_challenge = data.meta.dns_challenge == 1; | ||||
|              | ||||
|             if(!data.meta.dns_challenge){ | ||||
|                 data.meta.dns_provider = undefined; | ||||
|                 data.meta.dns_provider_credentials = undefined; | ||||
|                 data.meta.propagation_seconds = undefined; | ||||
|             } else { | ||||
|                 if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;  | ||||
|             } | ||||
|  | ||||
|             if (typeof data.domain_names === 'string' && data.domain_names) { | ||||
|                 data.domain_names = data.domain_names.split(','); | ||||
|             } | ||||
|  | ||||
|             // Check for any domain names containing wildcards, which are not allowed with letsencrypt | ||||
|             if (data.certificate_id === 'new') {                 | ||||
|                 let domain_err = false; | ||||
|                 if (!data.meta.dns_challenge) { | ||||
|                     data.domain_names.map(function (name) { | ||||
|                         if (name.match(/\*/im)) { | ||||
|                             domain_err = true; | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 if (domain_err) { | ||||
|                     alert(i18n('ssl', 'no-wildcard-without-dns')); | ||||
|                     return; | ||||
|                 } | ||||
|             } else { | ||||
|                 data.certificate_id = parseInt(data.certificate_id, 10); | ||||
|             } | ||||
|  | ||||
|             let method = App.Api.Nginx.DeadHosts.create; | ||||
|             let is_new = true; | ||||
|  | ||||
|             if (this.model.get('id')) { | ||||
|                 // edit | ||||
|                 is_new  = false; | ||||
|                 method  = App.Api.Nginx.DeadHosts.update; | ||||
|                 data.id = this.model.get('id'); | ||||
|             } | ||||
|  | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|             this.ui.save.addClass('btn-loading'); | ||||
|  | ||||
|             method(data) | ||||
|                 .then(result => { | ||||
|                     view.model.set(result); | ||||
|  | ||||
|                     App.UI.closeModal(function () { | ||||
|                         if (is_new) { | ||||
|                             App.Controller.showNginxDead(); | ||||
|                         } | ||||
|                     }); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     let more_info = ''; | ||||
|                     if(err.code === 500 && err.debug){ | ||||
|                         try{ | ||||
|                             more_info = JSON.parse(err.debug).debug.stack.join("\n"); | ||||
|                         } catch(e) {} | ||||
|                     } | ||||
|                     this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`; | ||||
|                     this.ui.le_error_info.show(); | ||||
|                     this.ui.le_error_info[0].scrollIntoView(); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                     this.ui.save.removeClass('btn-loading'); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         getLetsencryptEmail: function () { | ||||
|             return App.Cache.User.get('email'); | ||||
|         }, | ||||
|         getUseDnsChallenge: function () { | ||||
|             return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; | ||||
|         }, | ||||
|         getDnsProvider: function () { | ||||
|             return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; | ||||
|         }, | ||||
|         getDnsProviderCredentials: function () { | ||||
|             return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; | ||||
|         }, | ||||
|         getPropagationSeconds: function () { | ||||
|             return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; | ||||
|         }, | ||||
|         dns_plugins: dns_providers, | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         // Domain names | ||||
|         this.ui.domain_names.selectize({ | ||||
|             delimiter:    ',', | ||||
|             persist:      false, | ||||
|             maxOptions:   100, | ||||
|             create:       function (input) { | ||||
|                 return { | ||||
|                     value: input, | ||||
|                     text:  input | ||||
|                 }; | ||||
|             }, | ||||
|             createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ | ||||
|         }); | ||||
|  | ||||
|         // Certificates | ||||
|         this.ui.le_error_info.hide(); | ||||
|         this.ui.dns_challenge_content.hide(); | ||||
|         this.ui.credentials_file_content.hide(); | ||||
|         this.ui.letsencrypt.hide(); | ||||
|         this.ui.certificate_select.selectize({ | ||||
|             valueField:       'id', | ||||
|             labelField:       'nice_name', | ||||
|             searchField:      ['nice_name', 'domain_names'], | ||||
|             create:           false, | ||||
|             preload:          true, | ||||
|             allowEmptyOption: true, | ||||
|             render:           { | ||||
|                 option: function (item) { | ||||
|                     item.i18n         = App.i18n; | ||||
|                     item.formatDbDate = Helpers.formatDbDate; | ||||
|                     return certListItemTemplate(item); | ||||
|                 } | ||||
|             }, | ||||
|             load:             function (query, callback) { | ||||
|                 App.Api.Nginx.Certificates.getAll() | ||||
|                     .then(rows => { | ||||
|                         callback(rows); | ||||
|                     }) | ||||
|                     .catch(err => { | ||||
|                         console.error(err); | ||||
|                         callback(); | ||||
|                     }); | ||||
|             }, | ||||
|             onLoad:           function () { | ||||
|                 view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); | ||||
|             } | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     initialize: function (options) { | ||||
|         if (typeof options.model === 'undefined' || !options.model) { | ||||
|             this.model = new DeadHostModel.Model(); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -1,54 +0,0 @@ | ||||
| <td class="text-center"> | ||||
|     <div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>"> | ||||
|         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div> | ||||
|         <% domain_names.map(function(host) { | ||||
|             if (host.indexOf('*') === -1) { | ||||
|                 %> | ||||
|                 <span class="tag host-link hover-red" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span> | ||||
|                 <% | ||||
|             } else { | ||||
|                 %> | ||||
|                 <span class="tag"><%- host %></span> | ||||
|                 <% | ||||
|             } | ||||
|         }); | ||||
|         %> | ||||
|     </div> | ||||
|     <div class="small text-muted"> | ||||
|         <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div><%- certificate ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <% | ||||
|     var o = isOnline(); | ||||
|     if (!enabled) { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'disabled') %> | ||||
|     <% } else if (o === true) { %> | ||||
|         <span class="status-icon bg-success"></span> <%- i18n('str', 'online') %> | ||||
|     <% } else if (o === false) { %> | ||||
|         <span title="<%- getOfflineError() %>"><span class="status-icon bg-danger"></span> <%- i18n('str', 'offline') %></span> | ||||
|     <% } else { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'unknown') %> | ||||
|     <% } %> | ||||
| </td> | ||||
| <% if (canManage) { %> | ||||
| <td class="text-right"> | ||||
|     <div class="item-action dropdown"> | ||||
|         <a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a> | ||||
|         <div class="dropdown-menu dropdown-menu-right"> | ||||
|             <span class="dropdown-header"><%- i18n('audit-log', 'dead-host') %> #<%- id %></span> | ||||
|             <a href="#" class="edit dropdown-item"><i class="dropdown-icon fe fe-edit"></i> <%- i18n('str', 'edit') %></a> | ||||
|             <a href="#" class="able dropdown-item"><i class="dropdown-icon fe fe-power"></i> <%- i18n('str', enabled ? 'disable' : 'enable') %></a> | ||||
|             <div class="dropdown-divider"></div> | ||||
|             <a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a> | ||||
|         </div> | ||||
|     </div> | ||||
| </td> | ||||
| <% } %> | ||||
| @@ -1,61 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const template = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         able:      'a.able', | ||||
|         edit:      'a.edit', | ||||
|         delete:    'a.delete', | ||||
|         host_link: '.host-link' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.able': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let id = this.model.get('id'); | ||||
|             App.Api.Nginx.DeadHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) | ||||
|                 .then(() => { | ||||
|                     return App.Api.Nginx.DeadHosts.get(id) | ||||
|                         .then(row => { | ||||
|                             this.model.set(row); | ||||
|                         }); | ||||
|                 }); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.edit': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxDeadForm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxDeadDeleteConfirm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.host_link': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let win = window.open($(e.currentTarget).attr('rel'), '_blank'); | ||||
|             win.focus(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('dead_hosts'), | ||||
|  | ||||
|         isOnline: function () { | ||||
|             return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; | ||||
|         }, | ||||
|  | ||||
|         getOfflineError: function () { | ||||
|             return this.meta.nginx_err || ''; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
| @@ -1,12 +0,0 @@ | ||||
| <thead> | ||||
|     <th width="30"> </th> | ||||
|     <th><%- i18n('str', 'source') %></th> | ||||
|     <th><%- i18n('str', 'ssl') %></th> | ||||
|     <th><%- i18n('str', 'status') %></th> | ||||
|     <% if (canManage) { %> | ||||
|     <th> </th> | ||||
|     <% } %> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
| @@ -1,32 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const ItemView = require('./item'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| const TableBody = Mn.CollectionView.extend({ | ||||
|     tagName:   'tbody', | ||||
|     childView: ItemView | ||||
| }); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     tagName:   'table', | ||||
|     className: 'table table-hover table-outline table-vcenter card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('dead_hosts') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
| @@ -1,28 +0,0 @@ | ||||
| <div class="card"> | ||||
|     <div class="card-status bg-danger"></div> | ||||
|     <div class="card-header"> | ||||
|         <h3 class="card-title"><%- i18n('dead-hosts', 'title') %></h3> | ||||
|         <div class="card-options"> | ||||
|             <form class="search-form" role="search"> | ||||
|                 <div class="input-icon"> | ||||
|                     <span class="input-icon-addon"> | ||||
|                       <i class="fe fe-search"></i> | ||||
|                     </span> | ||||
|                     <input name="source-query" type="text" value="" class="form-control form-control-sm" placeholder="<%- i18n('dead-hosts', 'search') %>" aria-label="<%- i18n('dead-hosts', 'search') %>"> | ||||
|                 </div> | ||||
|             </form> | ||||
|             <a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a> | ||||
|             <% if (showAddButton) { %> | ||||
|             <a href="#" class="btn btn-outline-danger btn-sm ml-2 add-item"><%- i18n('dead-hosts', 'add') %></a> | ||||
|             <% } %> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div class="card-body no-padding min-100"> | ||||
|         <div class="dimmer active"> | ||||
|             <div class="loader"></div> | ||||
|             <div class="dimmer-content list-region"> | ||||
|                 <!-- List Region --> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,108 +0,0 @@ | ||||
| const Mn            = require('backbone.marionette'); | ||||
| const App           = require('../../main'); | ||||
| const DeadHostModel = require('../../../models/dead-host'); | ||||
| const ListView      = require('./list/main'); | ||||
| const ErrorView     = require('../../error/main'); | ||||
| const EmptyView     = require('../../empty/main'); | ||||
| const template      = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     id:       'nginx-dead', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         add:         '.add-item', | ||||
|         help:        '.help', | ||||
|         dimmer:      '.dimmer', | ||||
|         search:      '.search-form', | ||||
|         query:       'input[name="source-query"]' | ||||
|     }, | ||||
|  | ||||
|     fetch: App.Api.Nginx.DeadHosts.getAll, | ||||
|  | ||||
|     showData: function(response) { | ||||
|         this.showChildView('list_region', new ListView({ | ||||
|             collection: new DeadHostModel.Collection(response) | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     showError: function(err) { | ||||
|         this.showChildView('list_region', new ErrorView({ | ||||
|             code:    err.code, | ||||
|             message: err.message, | ||||
|             retry:   function () { | ||||
|                 App.Controller.showNginxDead(); | ||||
|             } | ||||
|         })); | ||||
|  | ||||
|         console.error(err); | ||||
|     }, | ||||
|  | ||||
|     showEmpty: function() { | ||||
|         let manage = App.Cache.User.canManage('dead_hosts'); | ||||
|  | ||||
|         this.showChildView('list_region', new EmptyView({ | ||||
|             title:      App.i18n('dead-hosts', 'empty'), | ||||
|             subtitle:   App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), | ||||
|             link:       manage ? App.i18n('dead-hosts', 'add') : null, | ||||
|             btn_color:  'danger', | ||||
|             permission: 'dead_hosts', | ||||
|             action:     function () { | ||||
|                 App.Controller.showNginxDeadForm(); | ||||
|             } | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.add': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxDeadForm(); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.help': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showHelp(App.i18n('dead-hosts', 'help-title'), App.i18n('dead-hosts', 'help-content')); | ||||
|         }, | ||||
|  | ||||
|         'submit @ui.search': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let query = this.ui.query.val(); | ||||
|  | ||||
|             this.fetch(['owner', 'certificate'], query) | ||||
|                 .then(response => this.showData(response)) | ||||
|                 .catch(err => { | ||||
|                     this.showError(err); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         showAddButton: App.Cache.User.canManage('dead_hosts') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         view.fetch(['owner', 'certificate']) | ||||
|             .then(response => { | ||||
|                 if (!view.isDestroyed()) { | ||||
|                     if (response && response.length) { | ||||
|                         view.showData(response); | ||||
|                     } else { | ||||
|                         view.showEmpty(); | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 view.showError(err); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 view.ui.dimmer.removeClass('active'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
| @@ -1,13 +0,0 @@ | ||||
| <div> | ||||
|     <% if (id > 0) { %> | ||||
|         <div class="title"> | ||||
|             <i class="fe fe-lock text-teal"></i> <%- name %> | ||||
|         </div> | ||||
|         <span class="description"><%- i18n('access-lists', 'item-count', {count: items.length || 0}) %>, <%- i18n('access-lists', 'client-count', {count: clients.length || 0}) %> – Created: <%- formatDbDate(created_on, 'Do MMMM YYYY, h:mm a') %></span> | ||||
|     <% } else { %> | ||||
|         <div class="title"> | ||||
|             <i class="fe fe-unlock text-yellow"></i> <%- i18n('access-lists', 'public') %> | ||||
|         </div> | ||||
|         <span class="description"><%- i18n('access-lists', 'public-sub') %></span> | ||||
|     <% } %> | ||||
| </div> | ||||
| @@ -1,23 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('proxy-hosts', 'delete') %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <form> | ||||
|             <div class="row"> | ||||
|                 <div class="col-sm-12 col-md-12"> | ||||
|                     <%= i18n('proxy-hosts', 'delete-confirm', {domains: domain_names.join(', ').toHtmlEntities()}) %> | ||||
|                     <% if (certificate_id) { %> | ||||
|                         <br><br> | ||||
|                         <%- i18n('ssl', 'delete-ssl') %> | ||||
|                     <% } %> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </form> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||
|         <button type="button" class="btn btn-danger save"><%- i18n('str', 'sure') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,32 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         form:    'form', | ||||
|         buttons: '.modal-footer button', | ||||
|         cancel:  'button.cancel', | ||||
|         save:    'button.save' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|  | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|  | ||||
|             App.Api.Nginx.ProxyHosts.delete(this.model.get('id')) | ||||
|                 .then(() => { | ||||
|                     App.Controller.showNginxProxy(); | ||||
|                     App.UI.closeModal(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -1,281 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('proxy-hosts', 'form-title', {id: id}) %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body has-tabs"> | ||||
|         <div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div> | ||||
|         <form> | ||||
|             <ul class="nav nav-tabs" role="tablist"> | ||||
|                 <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li> | ||||
|                 <li role="presentation" class="nav-item"><a href="#locations" aria-controls="tab4" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-layers"></i> <%- i18n('all-hosts', 'locations') %></a></li> | ||||
|                 <li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li> | ||||
|                 <li role="presentation" class="nav-item"><a href="#advanced" aria-controls="tab3" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-settings"></i> <%- i18n('all-hosts', 'advanced') %></a></li> | ||||
|             </ul> | ||||
|             <div class="tab-content"> | ||||
|  | ||||
|                 <!-- Locations --> | ||||
|                 <div class="tab-pane" id="locations"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-12"> | ||||
|                             <button type="button" class="btn btn-secondary add_location"><%- i18n('locations', 'new_location') %></button> | ||||
|                             <div class="locations_container mt-3"></div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Details --> | ||||
|                 <div role="tabpanel" class="tab-pane active" id="details"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label> | ||||
|                                 <input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-3 col-md-3"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('proxy-hosts', 'forward-scheme') %><span class="form-required">*</span></label> | ||||
|                                 <select name="forward_scheme" class="form-control custom-select" placeholder="http"> | ||||
|                                     <option value="http" <%- forward_scheme === 'http' ? 'selected' : '' %>>http</option> | ||||
|                                     <option value="https" <%- forward_scheme === 'https' ? 'selected' : '' %>>https</option> | ||||
|                                 </select> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-5 col-md-5"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label> | ||||
|                                 <input type="text" name="forward_host" class="form-control text-monospace" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="255" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-4 col-md-4"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('proxy-hosts', 'forward-port') %> <span class="form-required">*</span></label> | ||||
|                                 <input name="forward_port" type="number" class="form-control text-monospace" placeholder="80" value="<%- forward_port %>" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="caching_enabled" value="1"<%- caching_enabled ? ' checked' : '' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'caching-enabled') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="block_exploits" value="1"<%- block_exploits ? ' checked' : '' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'block-exploits') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="allow_websocket_upgrade" value="1"<%- allow_websocket_upgrade ? ' checked' : '' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('proxy-hosts', 'allow-websocket-upgrade') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('proxy-hosts', 'access-list') %></label> | ||||
|                                 <select name="access_list_id" class="form-control custom-select" placeholder="<%- i18n('access-lists', 'public') %>"> | ||||
|                                     <option selected value="0" data-data="{"id":0}" <%- access_list_id ? '' : 'selected' %>><%- i18n('access-lists', 'public') %></option> | ||||
|                                 </select> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- SSL --> | ||||
|                 <div role="tabpanel" class="tab-pane" id="ssl-options"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'ssl-certificate') %></label> | ||||
|                                 <select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>"> | ||||
|                                     <option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option> | ||||
|                                     <option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option> | ||||
|                                 </select> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="ssl_forced" value="1"<%- ssl_forced ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'force-ssl') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="http2_support" value="1"<%- http2_support ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'http2-support') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="hsts_enabled" value="1"<%- hsts_enabled ? ' checked' : '' %><%- certificate_id && ssl_forced ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'hsts-enabled') %> <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security" target="_blank"><i class="fe fe-help-circle"></i></a></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="hsts_subdomains" value="1"<%- hsts_subdomains ? ' checked' : '' %><%- certificate_id && ssl_forced && hsts_enabled ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'hsts-subdomains') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- DNS challenge --> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input | ||||
|                                         type="checkbox" | ||||
|                                         class="custom-switch-input" | ||||
|                                         name="meta[dns_challenge]" | ||||
|                                         value="1" | ||||
|                                         <%- getUseDnsChallenge() ? 'checked' : '' %> | ||||
|                                     > | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <fieldset class="form-fieldset dns-challenge"> | ||||
|                                 <div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div> | ||||
|  | ||||
|                                 <!-- Certbot DNS plugin selection --> | ||||
|                                 <div class="row"> | ||||
|                                     <div class="col-sm-12 col-md-12"> | ||||
|                                         <div class="form-group"> | ||||
|                                             <label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label> | ||||
|                                             <select | ||||
|                                                 name="meta[dns_provider]" | ||||
|                                                 id="dns_provider" | ||||
|                                                 class="form-control custom-select" | ||||
|                                             > | ||||
|                                                 <option | ||||
|                                                     value="" | ||||
|                                                     disabled | ||||
|                                                     hidden | ||||
|                                                     <%- getDnsProvider() === null ? 'selected' : '' %> | ||||
|                                                 >Please Choose...</option> | ||||
|                                                 <% _.each(dns_plugins, function(plugin_info, plugin_name){ %> | ||||
|                                                 <option | ||||
|                                                     value="<%- plugin_name %>" | ||||
|                                                     <%- getDnsProvider() === plugin_name ? 'selected' : '' %> | ||||
|                                                 ><%- plugin_info.name %></option> | ||||
|                                                 <% }); %> | ||||
|                                             </select> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|  | ||||
|                                 <!-- Certbot credentials file content --> | ||||
|                                 <div class="row credentials-file-content"> | ||||
|                                     <div class="col-sm-12 col-md-12"> | ||||
|                                         <div class="form-group"> | ||||
|                                             <label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label> | ||||
|                                             <textarea | ||||
|                                                 name="meta[dns_provider_credentials]" | ||||
|                                                 class="form-control text-monospace" | ||||
|                                                 id="dns_provider_credentials" | ||||
|                                             ><%- getDnsProviderCredentials() %></textarea> | ||||
|                                             <div class="text-secondary small"> | ||||
|                                                 <i class="fe fe-info"></i> | ||||
|                                                 <%= i18n('ssl', 'credentials-file-content-info') %> | ||||
|                                             </div> | ||||
|                                             <div class="text-red small"> | ||||
|                                                 <i class="fe fe-alert-triangle"></i> | ||||
|                                                 <%= i18n('ssl', 'stored-as-plaintext-info') %> | ||||
|                                             </div> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|  | ||||
|                                 <!-- DNS propagation delay --> | ||||
|                                 <div class="row"> | ||||
|                                     <div class="col-sm-12 col-md-12"> | ||||
|                                         <div class="form-group mb-0"> | ||||
|                                             <label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label> | ||||
|                                             <input | ||||
|                                                 type="number" | ||||
|                                                 min="0" | ||||
|                                                 name="meta[propagation_seconds]" | ||||
|                                                 class="form-control" | ||||
|                                                 id="propagation_seconds" | ||||
|                                                 value="<%- getPropagationSeconds() %>" | ||||
|                                             > | ||||
|                                             <div class="text-secondary small"> | ||||
|                                                 <i class="fe fe-info"></i> | ||||
|                                                 <%= i18n('ssl', 'propagation-seconds-info') %> | ||||
|                                             </div> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </fieldset> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- Lets encrypt --> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label> | ||||
|                                 <input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required disabled> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required disabled> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Advanced --> | ||||
|                 <div role="tabpanel" class="tab-pane" id="advanced"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-md-12"> | ||||
|                             <p><%- i18n('all-hosts', 'advanced-config-var-headline') %></p> | ||||
|                             <ul class="text-monospace"> | ||||
|                                 <li><code>$server</code> <%- i18n('proxy-hosts', 'forward-host') %></li> | ||||
|                                 <li><code>$port</code> <%- i18n('proxy-hosts', 'forward-port') %></li> | ||||
|                                 <li><code>$forward_scheme</code> <%- i18n('proxy-hosts', 'forward-scheme') %></li> | ||||
|                             </ul> | ||||
|                             <div class="form-group mb-0"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'advanced-config') %></label> | ||||
|                                 <textarea name="advanced_config" rows="8" class="form-control text-monospace" placeholder="# <%- i18n('all-hosts', 'advanced-warning') %>"><%- advanced_config %></textarea> | ||||
|                             </div> | ||||
|                             <p class="small text-gray"><i class="fe fe-alert-triangle"></i> <%- i18n('all-hosts', 'advanced-config-header-info') %></p> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </form> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||
|         <button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,369 +0,0 @@ | ||||
| const Mn                     = require('backbone.marionette'); | ||||
| const App                    = require('../../main'); | ||||
| const ProxyHostModel         = require('../../../models/proxy-host'); | ||||
| const ProxyLocationModel     = require('../../../models/proxy-host-location'); | ||||
| const template               = require('./form.ejs'); | ||||
| const certListItemTemplate   = require('../certificates-list-item.ejs'); | ||||
| const accessListItemTemplate = require('./access-list-item.ejs'); | ||||
| const CustomLocation         = require('./location'); | ||||
| const Helpers                = require('../../../lib/helpers'); | ||||
| const i18n                   = require('../../i18n'); | ||||
| const dns_providers          = require('../../../../../global/certbot-dns-plugins'); | ||||
|  | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
| require('selectize'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     locationsCollection: new ProxyLocationModel.Collection(), | ||||
|  | ||||
|     ui: { | ||||
|         form:                     'form', | ||||
|         domain_names:             'input[name="domain_names"]', | ||||
|         forward_host:             'input[name="forward_host"]', | ||||
|         buttons:                  '.modal-footer button', | ||||
|         cancel:                   'button.cancel', | ||||
|         save:                     'button.save', | ||||
|         add_location_btn:         'button.add_location', | ||||
|         locations_container:      '.locations_container', | ||||
|         le_error_info:            '#le-error-info', | ||||
|         certificate_select:       'select[name="certificate_id"]', | ||||
|         access_list_select:       'select[name="access_list_id"]', | ||||
|         ssl_forced:               'input[name="ssl_forced"]', | ||||
|         hsts_enabled:             'input[name="hsts_enabled"]', | ||||
|         hsts_subdomains:          'input[name="hsts_subdomains"]', | ||||
|         http2_support:            'input[name="http2_support"]', | ||||
|         dns_challenge_switch:     'input[name="meta[dns_challenge]"]', | ||||
|         dns_challenge_content:    '.dns-challenge', | ||||
|         dns_provider:             'select[name="meta[dns_provider]"]', | ||||
|         credentials_file_content: '.credentials-file-content', | ||||
|         dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', | ||||
|         propagation_seconds:      'input[name="meta[propagation_seconds]"]', | ||||
|         forward_scheme:           'select[name="forward_scheme"]', | ||||
|         letsencrypt:              '.letsencrypt' | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         locations_regions: '@ui.locations_container' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'change @ui.certificate_select': function () { | ||||
|             let id = this.ui.certificate_select.val(); | ||||
|             if (id === 'new') { | ||||
|                 this.ui.letsencrypt.show().find('input').prop('disabled', false); | ||||
|                 this.ui.dns_challenge_content.hide(); | ||||
|             } else { | ||||
|                 this.ui.letsencrypt.hide().find('input').prop('disabled', true); | ||||
|             } | ||||
|  | ||||
|             let enabled = id === 'new' || parseInt(id, 10) > 0; | ||||
|  | ||||
|             let inputs = this.ui.ssl_forced.add(this.ui.http2_support); | ||||
|             inputs | ||||
|                 .prop('disabled', !enabled) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', enabled ? 1 : 0.5); | ||||
|  | ||||
|             if (!enabled) { | ||||
|                 inputs.prop('checked', false); | ||||
|             } | ||||
|  | ||||
|             inputs.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.ssl_forced': function () { | ||||
|             let checked = this.ui.ssl_forced.prop('checked'); | ||||
|             this.ui.hsts_enabled | ||||
|                 .prop('disabled', !checked) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', checked ? 1 : 0.5); | ||||
|  | ||||
|             if (!checked) { | ||||
|                 this.ui.hsts_enabled.prop('checked', false); | ||||
|             } | ||||
|  | ||||
|             this.ui.hsts_enabled.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.hsts_enabled': function () { | ||||
|             let checked = this.ui.hsts_enabled.prop('checked'); | ||||
|             this.ui.hsts_subdomains | ||||
|                 .prop('disabled', !checked) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', checked ? 1 : 0.5); | ||||
|  | ||||
|             if (!checked) { | ||||
|                 this.ui.hsts_subdomains.prop('checked', false); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'change @ui.dns_challenge_switch': function () { | ||||
|             const checked = this.ui.dns_challenge_switch.prop('checked'); | ||||
|             if (checked) { | ||||
|                 this.ui.dns_provider.prop('required', 'required'); | ||||
|                 const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; | ||||
|                 if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ | ||||
|                     this.ui.dns_provider_credentials.prop('required', 'required'); | ||||
|                 } | ||||
|                 this.ui.dns_challenge_content.show(); | ||||
|             } else { | ||||
|                 this.ui.dns_provider.prop('required', false); | ||||
|                 this.ui.dns_provider_credentials.prop('required', false); | ||||
|                 this.ui.dns_challenge_content.hide();                 | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'change @ui.dns_provider': function () { | ||||
|             const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; | ||||
|             if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { | ||||
|                 this.ui.dns_provider_credentials.prop('required', 'required'); | ||||
|                 this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; | ||||
|                 this.ui.credentials_file_content.show(); | ||||
|             } else { | ||||
|                 this.ui.dns_provider_credentials.prop('required', false); | ||||
|                 this.ui.credentials_file_content.hide();                 | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'click @ui.add_location_btn': function (e) { | ||||
|             e.preventDefault(); | ||||
|              | ||||
|             const model = new ProxyLocationModel.Model(); | ||||
|             this.locationsCollection.add(model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|             this.ui.le_error_info.hide(); | ||||
|  | ||||
|             if (!this.ui.form[0].checkValidity()) { | ||||
|                 $('<input type="submit">').hide().appendTo(this.ui.form).click().remove(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let view = this; | ||||
|             let data = this.ui.form.serializeJSON(); | ||||
|  | ||||
|             // Add locations | ||||
|             data.locations = []; | ||||
|             this.locationsCollection.models.forEach((location) => { | ||||
|                 data.locations.push(location.toJSON()); | ||||
|             }); | ||||
|  | ||||
|             // Serialize collects path from custom locations | ||||
|             // This field must be removed from root object | ||||
|             delete data.path; | ||||
|  | ||||
|             // Manipulate | ||||
|             data.forward_port            = parseInt(data.forward_port, 10); | ||||
|             data.block_exploits          = !!data.block_exploits; | ||||
|             data.caching_enabled         = !!data.caching_enabled; | ||||
|             data.allow_websocket_upgrade = !!data.allow_websocket_upgrade; | ||||
|             data.http2_support           = !!data.http2_support; | ||||
|             data.hsts_enabled            = !!data.hsts_enabled; | ||||
|             data.hsts_subdomains         = !!data.hsts_subdomains; | ||||
|             data.ssl_forced              = !!data.ssl_forced; | ||||
|              | ||||
|             if (typeof data.meta === 'undefined') data.meta = {}; | ||||
|             data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; | ||||
|             data.meta.dns_challenge = data.meta.dns_challenge == 1; | ||||
|              | ||||
|             if(!data.meta.dns_challenge){ | ||||
|                 data.meta.dns_provider = undefined; | ||||
|                 data.meta.dns_provider_credentials = undefined; | ||||
|                 data.meta.propagation_seconds = undefined; | ||||
|             } else { | ||||
|                 if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;  | ||||
|             } | ||||
|  | ||||
|             if (typeof data.domain_names === 'string' && data.domain_names) { | ||||
|                 data.domain_names = data.domain_names.split(','); | ||||
|             } | ||||
|  | ||||
|             // Check for any domain names containing wildcards, which are not allowed with letsencrypt | ||||
|             if (data.certificate_id === 'new') {                 | ||||
|                 let domain_err = false; | ||||
|                 if (!data.meta.dns_challenge) { | ||||
|                     data.domain_names.map(function (name) { | ||||
|                         if (name.match(/\*/im)) { | ||||
|                             domain_err = true; | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 if (domain_err) { | ||||
|                     alert(i18n('ssl', 'no-wildcard-without-dns')); | ||||
|                     return; | ||||
|                 } | ||||
|             } else { | ||||
|                 data.certificate_id = parseInt(data.certificate_id, 10); | ||||
|             } | ||||
|  | ||||
|             let method = App.Api.Nginx.ProxyHosts.create; | ||||
|             let is_new = true; | ||||
|  | ||||
|             if (this.model.get('id')) { | ||||
|                 // edit | ||||
|                 is_new  = false; | ||||
|                 method  = App.Api.Nginx.ProxyHosts.update; | ||||
|                 data.id = this.model.get('id'); | ||||
|             } | ||||
|  | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|             this.ui.save.addClass('btn-loading'); | ||||
|  | ||||
|             method(data) | ||||
|                 .then(result => { | ||||
|                     view.model.set(result); | ||||
|  | ||||
|                     App.UI.closeModal(function () { | ||||
|                         if (is_new) { | ||||
|                             App.Controller.showNginxProxy(); | ||||
|                         } | ||||
|                     }); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     let more_info = ''; | ||||
|                     if(err.code === 500 && err.debug){ | ||||
|                         try{ | ||||
|                             more_info = JSON.parse(err.debug).debug.stack.join("\n"); | ||||
|                         } catch(e) {} | ||||
|                     } | ||||
|                     this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`; | ||||
|                     this.ui.le_error_info.show(); | ||||
|                     this.ui.le_error_info[0].scrollIntoView(); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                     this.ui.save.removeClass('btn-loading'); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         getLetsencryptEmail: function () { | ||||
|             return App.Cache.User.get('email'); | ||||
|         }, | ||||
|         getUseDnsChallenge: function () { | ||||
|             return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; | ||||
|         }, | ||||
|         getDnsProvider: function () { | ||||
|             return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; | ||||
|         }, | ||||
|         getDnsProviderCredentials: function () { | ||||
|             return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; | ||||
|         }, | ||||
|         getPropagationSeconds: function () { | ||||
|             return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; | ||||
|         }, | ||||
|         dns_plugins: dns_providers, | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         this.ui.ssl_forced.trigger('change'); | ||||
|         this.ui.hsts_enabled.trigger('change'); | ||||
|  | ||||
|         // Domain names | ||||
|         this.ui.domain_names.selectize({ | ||||
|             delimiter:    ',', | ||||
|             persist:      false, | ||||
|             maxOptions:   100, | ||||
|             create:       function (input) { | ||||
|                 return { | ||||
|                     value: input, | ||||
|                     text:  input | ||||
|                 }; | ||||
|             }, | ||||
|             createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ | ||||
|         }); | ||||
|  | ||||
|         // Access Lists | ||||
|         this.ui.access_list_select.selectize({ | ||||
|             valueField:       'id', | ||||
|             labelField:       'name', | ||||
|             searchField:      ['name'], | ||||
|             create:           false, | ||||
|             preload:          true, | ||||
|             allowEmptyOption: true, | ||||
|             render:           { | ||||
|                 option: function (item) { | ||||
|                     item.i18n         = App.i18n; | ||||
|                     item.formatDbDate = Helpers.formatDbDate; | ||||
|                     return accessListItemTemplate(item); | ||||
|                 } | ||||
|             }, | ||||
|             load:             function (query, callback) { | ||||
|                 App.Api.Nginx.AccessLists.getAll(['items', 'clients']) | ||||
|                     .then(rows => { | ||||
|                         callback(rows); | ||||
|                     }) | ||||
|                     .catch(err => { | ||||
|                         console.error(err); | ||||
|                         callback(); | ||||
|                     }); | ||||
|             }, | ||||
|             onLoad:           function () { | ||||
|                 view.ui.access_list_select[0].selectize.setValue(view.model.get('access_list_id')); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         // Certificates | ||||
|         this.ui.le_error_info.hide(); | ||||
|         this.ui.dns_challenge_content.hide(); | ||||
|         this.ui.credentials_file_content.hide(); | ||||
|         this.ui.letsencrypt.hide(); | ||||
|         this.ui.certificate_select.selectize({ | ||||
|             valueField:       'id', | ||||
|             labelField:       'nice_name', | ||||
|             searchField:      ['nice_name', 'domain_names'], | ||||
|             create:           false, | ||||
|             preload:          true, | ||||
|             allowEmptyOption: true, | ||||
|             render:           { | ||||
|                 option: function (item) { | ||||
|                     item.i18n         = App.i18n; | ||||
|                     item.formatDbDate = Helpers.formatDbDate; | ||||
|                     return certListItemTemplate(item); | ||||
|                 } | ||||
|             }, | ||||
|             load:             function (query, callback) { | ||||
|                 App.Api.Nginx.Certificates.getAll() | ||||
|                     .then(rows => { | ||||
|                         callback(rows); | ||||
|                     }) | ||||
|                     .catch(err => { | ||||
|                         console.error(err); | ||||
|                         callback(); | ||||
|                     }); | ||||
|             }, | ||||
|             onLoad:           function () { | ||||
|                 view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); | ||||
|             } | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     initialize: function (options) { | ||||
|         if (typeof options.model === 'undefined' || !options.model) { | ||||
|             this.model = new ProxyHostModel.Model(); | ||||
|         } | ||||
|  | ||||
|         this.locationsCollection = new ProxyLocationModel.Collection(); | ||||
|  | ||||
|         // Custom locations | ||||
|         this.showChildView('locations_regions', new CustomLocation.LocationCollectionView({ | ||||
|             collection: this.locationsCollection | ||||
|         })); | ||||
|  | ||||
|         // Check wether there are any location defined | ||||
|         if (options.model && Array.isArray(options.model.attributes.locations)) { | ||||
|             options.model.attributes.locations.forEach((location) => { | ||||
|                 let m = new ProxyLocationModel.Model(location); | ||||
|                 this.locationsCollection.add(m); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -1,60 +0,0 @@ | ||||
| <td class="text-center"> | ||||
|     <div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>"> | ||||
|         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div class="wrap"> | ||||
|         <% domain_names.map(function(host) { | ||||
|             if (host.indexOf('*') === -1) { | ||||
|                 %> | ||||
|                 <span class="tag host-link hover-green" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span> | ||||
|                 <% | ||||
|             } else { | ||||
|                 %> | ||||
|                 <span class="tag"><%- host %></span> | ||||
|                 <% | ||||
|             } | ||||
|         }); | ||||
|         %> | ||||
|     </div> | ||||
|     <div class="small text-muted"> | ||||
|         <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div class="text-monospace"><%- forward_scheme %>://<%- forward_host %>:<%- forward_port %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <div><%- certificate && certificate_id ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <div><%- access_list_id ? access_list.name : i18n('str', 'public') %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <% | ||||
|     var o = isOnline(); | ||||
|     if (!enabled) { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'disabled') %> | ||||
|     <% } else if (o === true) { %> | ||||
|         <span class="status-icon bg-success"></span> <%- i18n('str', 'online') %> | ||||
|     <% } else if (o === false) { %> | ||||
|         <span title="<%- getOfflineError() %>"><span class="status-icon bg-danger"></span> <%- i18n('str', 'offline') %></span> | ||||
|     <% } else { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'unknown') %> | ||||
|     <% } %> | ||||
| </td> | ||||
| <% if (canManage) { %> | ||||
| <td class="text-right"> | ||||
|     <div class="item-action dropdown"> | ||||
|         <a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a> | ||||
|         <div class="dropdown-menu dropdown-menu-right"> | ||||
|             <span class="dropdown-header"><%- i18n('audit-log', 'proxy-host') %> #<%- id %></span> | ||||
|             <a href="#" class="edit dropdown-item"><i class="dropdown-icon fe fe-edit"></i> <%- i18n('str', 'edit') %></a> | ||||
|             <a href="#" class="able dropdown-item"><i class="dropdown-icon fe fe-power"></i> <%- i18n('str', enabled ? 'disable' : 'enable') %></a> | ||||
|             <div class="dropdown-divider"></div> | ||||
|             <a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a> | ||||
|         </div> | ||||
|     </div> | ||||
| </td> | ||||
| <% } %> | ||||
| @@ -1,61 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const template = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         able:      'a.able', | ||||
|         edit:      'a.edit', | ||||
|         delete:    'a.delete', | ||||
|         host_link: '.host-link' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.able': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let id = this.model.get('id'); | ||||
|             App.Api.Nginx.ProxyHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) | ||||
|                 .then(() => { | ||||
|                     return App.Api.Nginx.ProxyHosts.get(id) | ||||
|                         .then(row => { | ||||
|                             this.model.set(row); | ||||
|                         }); | ||||
|                 }); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.edit': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxProxyForm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxProxyDeleteConfirm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.host_link': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let win = window.open($(e.currentTarget).attr('rel'), '_blank'); | ||||
|             win.focus(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('proxy_hosts'), | ||||
|  | ||||
|         isOnline: function () { | ||||
|             return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; | ||||
|         }, | ||||
|  | ||||
|         getOfflineError: function () { | ||||
|             return this.meta.nginx_err || ''; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
| @@ -1,14 +0,0 @@ | ||||
| <thead> | ||||
|     <th width="30"> </th> | ||||
|     <th><%- i18n('str', 'source') %></th> | ||||
|     <th><%- i18n('str', 'destination') %></th> | ||||
|     <th><%- i18n('str', 'ssl') %></th> | ||||
|     <th><%- i18n('str', 'access') %></th> | ||||
|     <th><%- i18n('str', 'status') %></th> | ||||
|     <% if (canManage) { %> | ||||
|     <th> </th> | ||||
|     <% } %> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
| @@ -1,32 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const ItemView = require('./item'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| const TableBody = Mn.CollectionView.extend({ | ||||
|     tagName:   'tbody', | ||||
|     childView: ItemView | ||||
| }); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     tagName:   'table', | ||||
|     className: 'table table-hover table-outline table-vcenter card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('proxy_hosts') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
| @@ -1,64 +0,0 @@ | ||||
| <div class="location-block card"> | ||||
|     <div class="card-body"> | ||||
|         <div class="row"> | ||||
|             <div class="col-sm-12"> | ||||
|                 <div class="form-group"> | ||||
|                     <label class="form-label"><%- i18n('locations', 'location_label') %> <span class="form-required">*</span></label> | ||||
|                     <div class="row gutter-xs"> | ||||
|                         <div class="col"> | ||||
|                             <div class="input-group"> | ||||
|                                 <span class="input-group-prepend"> | ||||
|                                     <span class="input-group-text">location</span> | ||||
|                                 </span> | ||||
|                                 <input type="text" name="path" class="form-control model" value="<%- path %>" placeholder="<%- i18n('locations', 'path') %>" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-auto"> | ||||
|                             <div class="selectgroup"> | ||||
|                                 <label class="selectgroup-item"> | ||||
|                                     <input type="checkbox" class="selectgroup-input"> | ||||
|                                     <span class="selectgroup-button"> | ||||
|                                         <i class="fe fe-settings"></i> | ||||
|                                     </span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="col-sm-3 col-md-3"> | ||||
|                 <div class="form-group"> | ||||
|                     <label class="form-label"><%- i18n('proxy-hosts', 'forward-scheme') %><span class="form-required">*</span></label> | ||||
|                     <select name="forward_scheme" class="form-control custom-select model" placeholder="http"> | ||||
|                         <option value="http" <%- forward_scheme === 'http' ? 'selected' : '' %>>http</option> | ||||
|                         <option value="https" <%- forward_scheme === 'https' ? 'selected' : '' %>>https</option> | ||||
|                     </select> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="col-sm-5 col-md-5"> | ||||
|                 <div class="form-group"> | ||||
|                     <label class="form-label"><%- i18n('proxy-hosts', 'forward-host') %><span class="form-required">*</span></label> | ||||
|                     <input type="text" name="forward_host" class="form-control text-monospace model" placeholder="" value="<%- forward_host %>" autocomplete="off" maxlength="200" required> | ||||
|                     <span style="font-size: 9px;"><%- i18n('proxy-hosts', 'custom-forward-host-help') %></span> | ||||
|                 </div> | ||||
|             </div> | ||||
|             <div class="col-sm-4 col-md-4"> | ||||
|                 <div class="form-group"> | ||||
|                     <label class="form-label"><%- i18n('proxy-hosts', 'forward-port') %> <span class="form-required">*</span></label> | ||||
|                     <input name="forward_port" type="number" class="form-control text-monospace model" placeholder="80" min="1" max="65535" value="<%- forward_port %>" required> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|         <div class="row config"> | ||||
|             <div class="col-md-12"> | ||||
|                 <div class="form-group"> | ||||
|                     <textarea name="advanced_config" rows="8" class="form-control text-monospace model" placeholder="# <%- i18n('all-hosts', 'advanced-warning') %>"><%- advanced_config %></textarea> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </div> | ||||
|  | ||||
|         <a href="#" class="card-link location-delete"> | ||||
|             <i class="fa fa-trash"></i> <%- i18n('locations', 'delete') %> | ||||
|         </a> | ||||
|     </div> | ||||
| </div>     | ||||
| @@ -1,54 +0,0 @@ | ||||
| const locationItemTemplate   = require('./location-item.ejs'); | ||||
| const Mn                     = require('backbone.marionette'); | ||||
| const App                    = require('../../main'); | ||||
|  | ||||
| const LocationView = Mn.View.extend({ | ||||
|     template: locationItemTemplate, | ||||
|     className: 'location_block', | ||||
|  | ||||
|     ui: { | ||||
|         toggle:     'input[type="checkbox"]', | ||||
|         config:     '.config', | ||||
|         delete:     '.location-delete' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'change @ui.toggle': function(el) { | ||||
|             if (el.target.checked) { | ||||
|                 this.ui.config.show(); | ||||
|             } else { | ||||
|                 this.ui.config.hide(); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'change .model': function (e) { | ||||
|             const map = {}; | ||||
|             map[e.target.name] = e.target.value; | ||||
|             this.model.set(map); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function () { | ||||
|             this.model.destroy(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     onRender: function() { | ||||
|         $(this.ui.config).hide(); | ||||
|     }, | ||||
|  | ||||
|     templateContext: function() { | ||||
|         return { | ||||
|             i18n: App.i18n | ||||
|         } | ||||
|     } | ||||
| }); | ||||
|  | ||||
| const LocationCollectionView = Mn.CollectionView.extend({ | ||||
|     className: 'locations_container', | ||||
|     childView: LocationView | ||||
| }); | ||||
|  | ||||
| module.exports = { | ||||
|     LocationCollectionView, | ||||
|     LocationView | ||||
| } | ||||
| @@ -1,28 +0,0 @@ | ||||
| <div class="card"> | ||||
|     <div class="card-status bg-success"></div> | ||||
|     <div class="card-header"> | ||||
|         <h3 class="card-title"><%- i18n('proxy-hosts', 'title') %></h3> | ||||
|         <div class="card-options"> | ||||
|             <form class="search-form" role="search"> | ||||
|                 <div class="input-icon"> | ||||
|                     <span class="input-icon-addon"> | ||||
|                       <i class="fe fe-search"></i> | ||||
|                     </span> | ||||
|                     <input name="source-query" type="text" value="" class="form-control form-control-sm" placeholder="<%- i18n('proxy-hosts', 'search') %>" aria-label="<%- i18n('proxy-hosts', 'search') %>"> | ||||
|                 </div> | ||||
|             </form> | ||||
|             <a href="#" class="btn btn-outline-secondary btn-sm ml-2 help"><i class="fe fe-help-circle"></i></a> | ||||
|             <% if (showAddButton) { %> | ||||
|             <a href="#" class="btn btn-outline-success btn-sm ml-2 add-item"><%- i18n('proxy-hosts', 'add') %></a> | ||||
|             <% } %> | ||||
|         </div> | ||||
|     </div> | ||||
|     <div class="card-body no-padding min-100"> | ||||
|         <div class="dimmer active"> | ||||
|             <div class="loader"></div> | ||||
|             <div class="dimmer-content list-region"> | ||||
|                 <!-- List Region --> | ||||
|             </div> | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,108 +0,0 @@ | ||||
| const Mn             = require('backbone.marionette'); | ||||
| const App            = require('../../main'); | ||||
| const ProxyHostModel = require('../../../models/proxy-host'); | ||||
| const ListView       = require('./list/main'); | ||||
| const ErrorView      = require('../../error/main'); | ||||
| const EmptyView      = require('../../empty/main'); | ||||
| const template       = require('./main.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     id:       'nginx-proxy', | ||||
|     template: template, | ||||
|  | ||||
|     ui: { | ||||
|         list_region: '.list-region', | ||||
|         add:         '.add-item', | ||||
|         help:        '.help', | ||||
|         dimmer:      '.dimmer', | ||||
|         search:      '.search-form', | ||||
|         query:       'input[name="source-query"]' | ||||
|     }, | ||||
|  | ||||
|     fetch: App.Api.Nginx.ProxyHosts.getAll, | ||||
|  | ||||
|     showData: function(response) { | ||||
|         this.showChildView('list_region', new ListView({ | ||||
|             collection: new ProxyHostModel.Collection(response) | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     showError: function(err) { | ||||
|         this.showChildView('list_region', new ErrorView({ | ||||
|             code:    err.code, | ||||
|             message: err.message, | ||||
|             retry:   function () { | ||||
|                 App.Controller.showNginxProxy(); | ||||
|             } | ||||
|         })); | ||||
|  | ||||
|         console.error(err); | ||||
|     }, | ||||
|  | ||||
|     showEmpty: function() { | ||||
|         let manage = App.Cache.User.canManage('proxy_hosts'); | ||||
|  | ||||
|         this.showChildView('list_region', new EmptyView({ | ||||
|             title:      App.i18n('proxy-hosts', 'empty'), | ||||
|             subtitle:   App.i18n('all-hosts', 'empty-subtitle', {manage: manage}), | ||||
|             link:       manage ? App.i18n('proxy-hosts', 'add') : null, | ||||
|             btn_color:  'success', | ||||
|             permission: 'proxy_hosts', | ||||
|             action:     function () { | ||||
|                 App.Controller.showNginxProxyForm(); | ||||
|             } | ||||
|         })); | ||||
|     }, | ||||
|  | ||||
|     regions: { | ||||
|         list_region: '@ui.list_region' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.add': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxProxyForm(); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.help': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showHelp(App.i18n('proxy-hosts', 'help-title'), App.i18n('proxy-hosts', 'help-content')); | ||||
|         }, | ||||
|  | ||||
|         'submit @ui.search': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let query = this.ui.query.val(); | ||||
|  | ||||
|             this.fetch(['owner', 'access_list', 'certificate'], query) | ||||
|                 .then(response => this.showData(response)) | ||||
|                 .catch(err => { | ||||
|                     this.showError(err); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         showAddButton: App.Cache.User.canManage('proxy_hosts') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         view.fetch(['owner', 'access_list', 'certificate']) | ||||
|             .then(response => { | ||||
|                 if (!view.isDestroyed()) { | ||||
|                     if (response && response.length) { | ||||
|                         view.showData(response); | ||||
|                     } else { | ||||
|                         view.showEmpty(); | ||||
|                     } | ||||
|                 } | ||||
|             }) | ||||
|             .catch(err => { | ||||
|                 view.showError(err); | ||||
|             }) | ||||
|             .then(() => { | ||||
|                 view.ui.dimmer.removeClass('active'); | ||||
|             }); | ||||
|     } | ||||
| }); | ||||
| @@ -1,23 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('redirection-hosts', 'delete') %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body"> | ||||
|         <form> | ||||
|             <div class="row"> | ||||
|                 <div class="col-sm-12 col-md-12"> | ||||
|                     <%= i18n('redirection-hosts', 'delete-confirm', {domains: domain_names.join(', ').toHtmlEntities()}) %> | ||||
|                     <% if (certificate_id) { %> | ||||
|                         <br><br> | ||||
|                         <%- i18n('ssl', 'delete-ssl') %> | ||||
|                     <% } %> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </form> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||
|         <button type="button" class="btn btn-danger save"><%- i18n('str', 'sure') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,32 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../main'); | ||||
| const template = require('./delete.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         form:    'form', | ||||
|         buttons: '.modal-footer button', | ||||
|         cancel:  'button.cancel', | ||||
|         save:    'button.save' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|  | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|  | ||||
|             App.Api.Nginx.RedirectionHosts.delete(this.model.get('id')) | ||||
|                 .then(() => { | ||||
|                     App.Controller.showNginxRedirection(); | ||||
|                     App.UI.closeModal(); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     alert(err.message); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                 }); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -1,253 +0,0 @@ | ||||
| <div class="modal-content"> | ||||
|     <div class="modal-header"> | ||||
|         <h5 class="modal-title"><%- i18n('redirection-hosts', 'form-title', {id: id}) %></h5> | ||||
|         <button type="button" class="close cancel" aria-label="Close" data-dismiss="modal"> </button> | ||||
|     </div> | ||||
|     <div class="modal-body has-tabs"> | ||||
|         <div class="alert alert-danger mb-0 rounded-0" id="le-error-info" role="alert"></div> | ||||
|         <form> | ||||
|             <ul class="nav nav-tabs" role="tablist"> | ||||
|                 <li role="presentation" class="nav-item"><a href="#details" aria-controls="tab1" role="tab" data-toggle="tab" class="nav-link active"><i class="fe fe-zap"></i> <%- i18n('all-hosts', 'details') %></a></li> | ||||
|                 <li role="presentation" class="nav-item"><a href="#ssl-options" aria-controls="tab2" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-shield"></i> <%- i18n('str', 'ssl') %></a></li> | ||||
|                 <li role="presentation" class="nav-item"><a href="#advanced" aria-controls="tab3" role="tab" data-toggle="tab" class="nav-link"><i class="fe fe-settings"></i> <%- i18n('all-hosts', 'advanced') %></a></li> | ||||
|             </ul> | ||||
|             <div class="tab-content"> | ||||
|                 <!-- Details --> | ||||
|                 <div role="tabpanel" class="tab-pane active" id="details"> | ||||
|                     <div class="row"> | ||||
|  | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'domain-names') %> <span class="form-required">*</span></label> | ||||
|                                 <input type="text" name="domain_names" class="form-control" id="input-domains" value="<%- domain_names.join(',') %>" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-3 col-md-3"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('redirection-hosts', 'forward-scheme') %><span class="form-required">*</span></label> | ||||
|                                 <select name="forward_scheme" class="form-control custom-select" placeholder="$scheme"> | ||||
|                                     <option value="$scheme" <%- forward_scheme === '$scheme' ? 'selected' : '' %>>auto</option> | ||||
|                                     <option value="http" <%- forward_scheme === 'http' ? 'selected' : '' %>>http</option> | ||||
|                                     <option value="https" <%- forward_scheme === 'https' ? 'selected' : '' %>>https</option> | ||||
|                                 </select> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-9 col-md-9"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('redirection-hosts', 'forward-domain') %><span class="form-required">*</span></label> | ||||
|                                 <input type="text" name="forward_domain_name" class="form-control text-monospace" placeholder="" value="<%- forward_domain_name %>" required> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('redirection-hosts', 'forward-http-status-code') %><span class="form-required">*</span></label> | ||||
|                                 <select name="forward_http_code" class="form-control custom-select" placeholder="301"> | ||||
|                                     <option value="300" <%- forward_http_code == '300' ? 'selected' : '' %>>300 Multiple choices</option> | ||||
|                                     <option value="301" <%- forward_http_code == '301' ? 'selected' : '' %>>301 Moved permanently</option> | ||||
|                                     <option value="302" <%- forward_http_code == '302' ? 'selected' : '' %>>302 Found</option> | ||||
|                                     <option value="303" <%- forward_http_code == '303' ? 'selected' : '' %>>303 See other</option> | ||||
|                                     <option value="307" <%- forward_http_code == '307' ? 'selected' : '' %>>307 Temporary redirect</option> | ||||
|                                     <option value="308" <%- forward_http_code == '308' ? 'selected' : '' %>>308 Permanent redirect</option> | ||||
|                                 </select> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="preserve_path" value="1"<%- preserve_path ? ' checked' : '' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('redirection-hosts', 'preserve-path') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="block_exploits" value="1"<%- block_exploits ? ' checked' : '' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'block-exploits') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- SSL --> | ||||
|                 <div role="tabpanel" class="tab-pane" id="ssl-options"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-sm-12 col-md-12"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'ssl-certificate') %></label> | ||||
|                                 <select name="certificate_id" class="form-control custom-select" placeholder="<%- i18n('all-hosts', 'none') %>"> | ||||
|                                     <option selected value="0" data-data="{"id":0}" <%- certificate_id ? '' : 'selected' %>><%- i18n('all-hosts', 'none') %></option> | ||||
|                                     <option selected value="new" data-data="{"id":"new"}"><%- i18n('all-hosts', 'new-cert') %></option> | ||||
|                                 </select> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="ssl_forced" value="1"<%- ssl_forced ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'force-ssl') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="http2_support" value="1"<%- http2_support ? ' checked' : '' %><%- certificate_id ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'http2-support') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="hsts_enabled" value="1"<%- hsts_enabled ? ' checked' : '' %><%- certificate_id && ssl_forced ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'hsts-enabled') %> <a href="https://en.wikipedia.org/wiki/HTTP_Strict_Transport_Security" target="_blank"><i class="fe fe-help-circle"></i></a></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-6 col-md-6"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="hsts_subdomains" value="1"<%- hsts_subdomains ? ' checked' : '' %><%- certificate_id && ssl_forced && hsts_enabled ? '' : ' disabled' %>> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%- i18n('all-hosts', 'hsts-subdomains') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- DNS challenge --> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input | ||||
|                                         type="checkbox" | ||||
|                                         class="custom-switch-input" | ||||
|                                         name="meta[dns_challenge]" | ||||
|                                         value="1" | ||||
|                                         <%- getUseDnsChallenge() ? 'checked' : '' %> | ||||
|                                     > | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%= i18n('ssl', 'dns-challenge') %></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <fieldset class="form-fieldset dns-challenge"> | ||||
|                                 <div class="text-red mb-4"><i class="fe fe-alert-triangle"></i> <%= i18n('ssl', 'certbot-warning') %></div> | ||||
|  | ||||
|                                 <!-- Certbot DNS plugin selection --> | ||||
|                                 <div class="row"> | ||||
|                                     <div class="col-sm-12 col-md-12"> | ||||
|                                         <div class="form-group"> | ||||
|                                             <label class="form-label"><%- i18n('ssl', 'dns-provider') %> <span class="form-required">*</span></label> | ||||
|                                             <select | ||||
|                                                 name="meta[dns_provider]" | ||||
|                                                 id="dns_provider" | ||||
|                                                 class="form-control custom-select" | ||||
|                                             > | ||||
|                                                 <option | ||||
|                                                     value="" | ||||
|                                                     disabled | ||||
|                                                     hidden | ||||
|                                                     <%- getDnsProvider() === null ? 'selected' : '' %> | ||||
|                                                 >Please Choose...</option> | ||||
|                                                 <% _.each(dns_plugins, function(plugin_info, plugin_name){ %> | ||||
|                                                 <option | ||||
|                                                     value="<%- plugin_name %>" | ||||
|                                                     <%- getDnsProvider() === plugin_name ? 'selected' : '' %> | ||||
|                                                 ><%- plugin_info.name %></option> | ||||
|                                                 <% }); %> | ||||
|                                             </select> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|  | ||||
|                                 <!-- Certbot credentials file content --> | ||||
|                                 <div class="row credentials-file-content"> | ||||
|                                     <div class="col-sm-12 col-md-12"> | ||||
|                                         <div class="form-group"> | ||||
|                                             <label class="form-label"><%- i18n('ssl', 'credentials-file-content') %> <span class="form-required">*</span></label> | ||||
|                                             <textarea | ||||
|                                                 name="meta[dns_provider_credentials]" | ||||
|                                                 class="form-control text-monospace" | ||||
|                                                 id="dns_provider_credentials" | ||||
|                                             ><%- getDnsProviderCredentials() %></textarea> | ||||
|                                             <div class="text-secondary small"> | ||||
|                                                 <i class="fe fe-info"></i> | ||||
|                                                 <%= i18n('ssl', 'credentials-file-content-info') %> | ||||
|                                             </div> | ||||
|                                             <div class="text-red small"> | ||||
|                                                 <i class="fe fe-alert-triangle"></i> | ||||
|                                                 <%= i18n('ssl', 'stored-as-plaintext-info') %> | ||||
|                                             </div> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|  | ||||
|                                 <!-- DNS propagation delay --> | ||||
|                                 <div class="row"> | ||||
|                                     <div class="col-sm-12 col-md-12"> | ||||
|                                         <div class="form-group mb-0"> | ||||
|                                             <label class="form-label"><%- i18n('ssl', 'propagation-seconds') %></label> | ||||
|                                             <input | ||||
|                                                 type="number" | ||||
|                                                 min="0" | ||||
|                                                 name="meta[propagation_seconds]" | ||||
|                                                 class="form-control" | ||||
|                                                 id="propagation_seconds" | ||||
|                                                 value="<%- getPropagationSeconds() %>" | ||||
|                                             > | ||||
|                                             <div class="text-secondary small"> | ||||
|                                                 <i class="fe fe-info"></i> | ||||
|                                                 <%= i18n('ssl', 'propagation-seconds-info') %> | ||||
|                                             </div> | ||||
|                                         </div> | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </fieldset> | ||||
|                         </div> | ||||
|  | ||||
|                         <!-- Lets encrypt --> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="form-label"><%- i18n('ssl', 'letsencrypt-email') %> <span class="form-required">*</span></label> | ||||
|                                 <input name="meta[letsencrypt_email]" type="email" class="form-control" placeholder="" value="<%- getLetsencryptEmail() %>" required disabled> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                         <div class="col-sm-12 col-md-12 letsencrypt"> | ||||
|                             <div class="form-group"> | ||||
|                                 <label class="custom-switch"> | ||||
|                                     <input type="checkbox" class="custom-switch-input" name="meta[letsencrypt_agree]" value="1" required disabled> | ||||
|                                     <span class="custom-switch-indicator"></span> | ||||
|                                     <span class="custom-switch-description"><%= i18n('ssl', 'letsencrypt-agree', {url: 'https://letsencrypt.org/repository/'}) %> <span class="form-required">*</span></span> | ||||
|                                 </label> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|  | ||||
|                 <!-- Advanced --> | ||||
|                 <div role="tabpanel" class="tab-pane" id="advanced"> | ||||
|                     <div class="row"> | ||||
|                         <div class="col-md-12"> | ||||
|                             <div class="form-group mb-0"> | ||||
|                                 <label class="form-label"><%- i18n('all-hosts', 'advanced-config') %></label> | ||||
|                                 <textarea name="advanced_config" rows="8" class="form-control text-monospace" placeholder="# <%- i18n('all-hosts', 'advanced-warning') %>"><%- advanced_config %></textarea> | ||||
|                             </div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             </div> | ||||
|         </form> | ||||
|     </div> | ||||
|     <div class="modal-footer"> | ||||
|         <button type="button" class="btn btn-secondary cancel" data-dismiss="modal"><%- i18n('str', 'cancel') %></button> | ||||
|         <button type="button" class="btn btn-teal save"><%- i18n('str', 'save') %></button> | ||||
|     </div> | ||||
| </div> | ||||
| @@ -1,288 +0,0 @@ | ||||
| const Mn                   = require('backbone.marionette'); | ||||
| const App                  = require('../../main'); | ||||
| const RedirectionHostModel = require('../../../models/redirection-host'); | ||||
| const template             = require('./form.ejs'); | ||||
| const certListItemTemplate = require('../certificates-list-item.ejs'); | ||||
| const Helpers              = require('../../../lib/helpers'); | ||||
| const i18n                 = require('../../i18n'); | ||||
| const dns_providers        = require('../../../../../global/certbot-dns-plugins'); | ||||
|  | ||||
|  | ||||
| require('jquery-serializejson'); | ||||
| require('selectize'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template:  template, | ||||
|     className: 'modal-dialog', | ||||
|  | ||||
|     ui: { | ||||
|         form:                     'form', | ||||
|         domain_names:             'input[name="domain_names"]', | ||||
|         buttons:                  '.modal-footer button', | ||||
|         cancel:                   'button.cancel', | ||||
|         save:                     'button.save', | ||||
|         le_error_info:            '#le-error-info', | ||||
|         certificate_select:       'select[name="certificate_id"]', | ||||
|         ssl_forced:               'input[name="ssl_forced"]', | ||||
|         hsts_enabled:             'input[name="hsts_enabled"]', | ||||
|         hsts_subdomains:          'input[name="hsts_subdomains"]', | ||||
|         http2_support:            'input[name="http2_support"]', | ||||
|         dns_challenge_switch:     'input[name="meta[dns_challenge]"]', | ||||
|         dns_challenge_content:    '.dns-challenge', | ||||
|         dns_provider:             'select[name="meta[dns_provider]"]', | ||||
|         credentials_file_content: '.credentials-file-content', | ||||
|         dns_provider_credentials: 'textarea[name="meta[dns_provider_credentials]"]', | ||||
|         propagation_seconds:      'input[name="meta[propagation_seconds]"]', | ||||
|         letsencrypt:              '.letsencrypt' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'change @ui.certificate_select': function () { | ||||
|             let id = this.ui.certificate_select.val(); | ||||
|             if (id === 'new') { | ||||
|                 this.ui.letsencrypt.show().find('input').prop('disabled', false); | ||||
|                 this.ui.dns_challenge_content.hide(); | ||||
|             } else { | ||||
|                 this.ui.letsencrypt.hide().find('input').prop('disabled', true); | ||||
|             } | ||||
|  | ||||
|             let enabled = id === 'new' || parseInt(id, 10) > 0; | ||||
|  | ||||
|             let inputs = this.ui.ssl_forced.add(this.ui.http2_support); | ||||
|             inputs | ||||
|                 .prop('disabled', !enabled) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', enabled ? 1 : 0.5); | ||||
|  | ||||
|             if (!enabled) { | ||||
|                 inputs.prop('checked', false); | ||||
|             } | ||||
|  | ||||
|             inputs.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.ssl_forced': function () { | ||||
|             let checked = this.ui.ssl_forced.prop('checked'); | ||||
|             this.ui.hsts_enabled | ||||
|                 .prop('disabled', !checked) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', checked ? 1 : 0.5); | ||||
|  | ||||
|             if (!checked) { | ||||
|                 this.ui.hsts_enabled.prop('checked', false); | ||||
|             } | ||||
|  | ||||
|             this.ui.hsts_enabled.trigger('change'); | ||||
|         }, | ||||
|  | ||||
|         'change @ui.hsts_enabled': function () { | ||||
|             let checked = this.ui.hsts_enabled.prop('checked'); | ||||
|             this.ui.hsts_subdomains | ||||
|                 .prop('disabled', !checked) | ||||
|                 .parents('.form-group') | ||||
|                 .css('opacity', checked ? 1 : 0.5); | ||||
|  | ||||
|             if (!checked) { | ||||
|                 this.ui.hsts_subdomains.prop('checked', false); | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'change @ui.dns_challenge_switch': function () { | ||||
|             const checked = this.ui.dns_challenge_switch.prop('checked'); | ||||
|             if (checked) { | ||||
|                 this.ui.dns_provider.prop('required', 'required'); | ||||
|                 const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; | ||||
|                 if(selected_provider != '' && dns_providers[selected_provider].credentials !== false){ | ||||
|                     this.ui.dns_provider_credentials.prop('required', 'required'); | ||||
|                 } | ||||
|                 this.ui.dns_challenge_content.show(); | ||||
|             } else { | ||||
|                 this.ui.dns_provider.prop('required', false); | ||||
|                 this.ui.dns_provider_credentials.prop('required', false); | ||||
|                 this.ui.dns_challenge_content.hide();                 | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'change @ui.dns_provider': function () { | ||||
|             const selected_provider = this.ui.dns_provider[0].options[this.ui.dns_provider[0].selectedIndex].value; | ||||
|             if (selected_provider != '' && dns_providers[selected_provider].credentials !== false) { | ||||
|                 this.ui.dns_provider_credentials.prop('required', 'required'); | ||||
|                 this.ui.dns_provider_credentials[0].value = dns_providers[selected_provider].credentials; | ||||
|                 this.ui.credentials_file_content.show(); | ||||
|             } else { | ||||
|                 this.ui.dns_provider_credentials.prop('required', false); | ||||
|                 this.ui.credentials_file_content.hide();                 | ||||
|             } | ||||
|         }, | ||||
|  | ||||
|         'click @ui.save': function (e) { | ||||
|             e.preventDefault(); | ||||
|             this.ui.le_error_info.hide(); | ||||
|  | ||||
|             if (!this.ui.form[0].checkValidity()) { | ||||
|                 $('<input type="submit">').hide().appendTo(this.ui.form).click().remove(); | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             let view = this; | ||||
|             let data = this.ui.form.serializeJSON(); | ||||
|  | ||||
|             // Manipulate | ||||
|             data.block_exploits     = !!data.block_exploits; | ||||
|             data.preserve_path      = !!data.preserve_path; | ||||
|             data.http2_support      = !!data.http2_support; | ||||
|             data.hsts_enabled       = !!data.hsts_enabled; | ||||
|             data.hsts_subdomains    = !!data.hsts_subdomains; | ||||
|             data.ssl_forced         = !!data.ssl_forced; | ||||
|              | ||||
|             if (typeof data.meta === 'undefined') data.meta = {}; | ||||
|             data.meta.letsencrypt_agree = data.meta.letsencrypt_agree == 1; | ||||
|             data.meta.dns_challenge = data.meta.dns_challenge == 1; | ||||
|              | ||||
|             if(!data.meta.dns_challenge){ | ||||
|                 data.meta.dns_provider = undefined; | ||||
|                 data.meta.dns_provider_credentials = undefined; | ||||
|                 data.meta.propagation_seconds = undefined; | ||||
|             } else { | ||||
|                 if(data.meta.propagation_seconds === '') data.meta.propagation_seconds = undefined;  | ||||
|             } | ||||
|  | ||||
|             if (typeof data.domain_names === 'string' && data.domain_names) { | ||||
|                 data.domain_names = data.domain_names.split(','); | ||||
|             } | ||||
|  | ||||
|             // Check for any domain names containing wildcards, which are not allowed with letsencrypt | ||||
|             if (data.certificate_id === 'new') {                 | ||||
|                 let domain_err = false; | ||||
|                 if (!data.meta.dns_challenge) { | ||||
|                     data.domain_names.map(function (name) { | ||||
|                         if (name.match(/\*/im)) { | ||||
|                             domain_err = true; | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|  | ||||
|                 if (domain_err) { | ||||
|                     alert(i18n('ssl', 'no-wildcard-without-dns')); | ||||
|                     return; | ||||
|                 }              | ||||
|             } else { | ||||
|                 data.certificate_id = parseInt(data.certificate_id, 10); | ||||
|             } | ||||
|  | ||||
|             let method = App.Api.Nginx.RedirectionHosts.create; | ||||
|             let is_new = true; | ||||
|  | ||||
|             if (this.model.get('id')) { | ||||
|                 // edit | ||||
|                 is_new  = false; | ||||
|                 method  = App.Api.Nginx.RedirectionHosts.update; | ||||
|                 data.id = this.model.get('id'); | ||||
|             } | ||||
|  | ||||
|             this.ui.buttons.prop('disabled', true).addClass('btn-disabled'); | ||||
|             this.ui.save.addClass('btn-loading'); | ||||
|  | ||||
|             method(data) | ||||
|                 .then(result => { | ||||
|                     view.model.set(result); | ||||
|  | ||||
|                     App.UI.closeModal(function () { | ||||
|                         if (is_new) { | ||||
|                             App.Controller.showNginxRedirection(); | ||||
|                         } | ||||
|                     }); | ||||
|                 }) | ||||
|                 .catch(err => { | ||||
|                     let more_info = ''; | ||||
|                     if(err.code === 500 && err.debug){ | ||||
|                         try{ | ||||
|                             more_info = JSON.parse(err.debug).debug.stack.join("\n"); | ||||
|                         } catch(e) {} | ||||
|                     } | ||||
|                     this.ui.le_error_info[0].innerHTML = `${err.message}${more_info !== '' ? `<pre class="mt-3">${more_info}</pre>`:''}`; | ||||
|                     this.ui.le_error_info.show(); | ||||
|                     this.ui.le_error_info[0].scrollIntoView(); | ||||
|                     this.ui.buttons.prop('disabled', false).removeClass('btn-disabled'); | ||||
|                     this.ui.save.removeClass('btn-loading'); | ||||
|                 }); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         getLetsencryptEmail: function () { | ||||
|             return App.Cache.User.get('email'); | ||||
|         }, | ||||
|         getUseDnsChallenge: function () { | ||||
|             return typeof this.meta.dns_challenge !== 'undefined' ? this.meta.dns_challenge : false; | ||||
|         }, | ||||
|         getDnsProvider: function () { | ||||
|             return typeof this.meta.dns_provider !== 'undefined' && this.meta.dns_provider != '' ? this.meta.dns_provider : null; | ||||
|         }, | ||||
|         getDnsProviderCredentials: function () { | ||||
|             return typeof this.meta.dns_provider_credentials !== 'undefined' ? this.meta.dns_provider_credentials : ''; | ||||
|         }, | ||||
|         getPropagationSeconds: function () { | ||||
|             return typeof this.meta.propagation_seconds !== 'undefined' ? this.meta.propagation_seconds : ''; | ||||
|         }, | ||||
|         dns_plugins: dns_providers, | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         let view = this; | ||||
|  | ||||
|         // Domain names | ||||
|         this.ui.domain_names.selectize({ | ||||
|             delimiter:    ',', | ||||
|             persist:      false, | ||||
|             maxOptions:   100, | ||||
|             create:       function (input) { | ||||
|                 return { | ||||
|                     value: input, | ||||
|                     text:  input | ||||
|                 }; | ||||
|             }, | ||||
|             createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ | ||||
|         }); | ||||
|  | ||||
|         // Certificates | ||||
|         this.ui.le_error_info.hide(); | ||||
|         this.ui.dns_challenge_content.hide(); | ||||
|         this.ui.credentials_file_content.hide(); | ||||
|         this.ui.letsencrypt.hide(); | ||||
|         this.ui.certificate_select.selectize({ | ||||
|             valueField:       'id', | ||||
|             labelField:       'nice_name', | ||||
|             searchField:      ['nice_name', 'domain_names'], | ||||
|             create:           false, | ||||
|             preload:          true, | ||||
|             allowEmptyOption: true, | ||||
|             render:           { | ||||
|                 option: function (item) { | ||||
|                     item.i18n         = App.i18n; | ||||
|                     item.formatDbDate = Helpers.formatDbDate; | ||||
|                     return certListItemTemplate(item); | ||||
|                 } | ||||
|             }, | ||||
|             load:             function (query, callback) { | ||||
|                 App.Api.Nginx.Certificates.getAll() | ||||
|                     .then(rows => { | ||||
|                         callback(rows); | ||||
|                     }) | ||||
|                     .catch(err => { | ||||
|                         console.error(err); | ||||
|                         callback(); | ||||
|                     }); | ||||
|             }, | ||||
|             onLoad:           function () { | ||||
|                 view.ui.certificate_select[0].selectize.setValue(view.model.get('certificate_id')); | ||||
|             } | ||||
|         }); | ||||
|     }, | ||||
|  | ||||
|     initialize: function (options) { | ||||
|         if (typeof options.model === 'undefined' || !options.model) { | ||||
|             this.model = new RedirectionHostModel.Model(); | ||||
|         } | ||||
|     } | ||||
| }); | ||||
| @@ -1,63 +0,0 @@ | ||||
| <td class="text-center"> | ||||
|     <div class="avatar d-block" style="background-image: url(<%- (owner && owner.avatar) || '/images/default-avatar.jpg' %>)" title="Owned by <%- (owner && owner.name) || 'a deleted user' %>"> | ||||
|         <span class="avatar-status <%- owner && !owner.is_disabled ? 'bg-green' : 'bg-red' %>"></span> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div> | ||||
|         <% domain_names.map(function(host) { | ||||
|             if (host.indexOf('*') === -1) { | ||||
|                 %> | ||||
|                 <span class="tag host-link hover-yellow" rel="http<%- certificate_id ? 's' : '' %>://<%- host %>"><%- host %></span> | ||||
|                 <% | ||||
|             } else { | ||||
|                 %> | ||||
|                 <span class="tag"><%- host %></span> | ||||
|                 <% | ||||
|             } | ||||
|         }); | ||||
|         %> | ||||
|     </div> | ||||
|     <div class="small text-muted"> | ||||
|         <%- i18n('str', 'created-on', {date: formatDbDate(created_on, 'Do MMMM YYYY')}) %> | ||||
|     </div> | ||||
| </td> | ||||
| <td> | ||||
|     <div class="text-monospace"><%- forward_http_code %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <div class="text-monospace"><%- forward_scheme == '$scheme' ? 'auto' : forward_scheme %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <div class="text-monospace"><%- forward_domain_name %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <div><%- certificate ? i18n('ssl', certificate.provider) : i18n('ssl', 'none') %></div> | ||||
| </td> | ||||
| <td> | ||||
|     <% | ||||
|     var o = isOnline(); | ||||
|     if (!enabled) { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'disabled') %> | ||||
|     <% } else if (o === true) { %> | ||||
|         <span class="status-icon bg-success"></span> <%- i18n('str', 'online') %> | ||||
|     <% } else if (o === false) { %> | ||||
|         <span title="<%- getOfflineError() %>"><span class="status-icon bg-danger"></span> <%- i18n('str', 'offline') %></span> | ||||
|     <% } else { %> | ||||
|         <span class="status-icon bg-warning"></span> <%- i18n('str', 'unknown') %> | ||||
|     <% } %> | ||||
| </td> | ||||
| <% if (canManage) { %> | ||||
| <td class="text-right"> | ||||
|     <div class="item-action dropdown"> | ||||
|         <a href="#" data-toggle="dropdown" class="icon"><i class="fe fe-more-vertical"></i></a> | ||||
|         <div class="dropdown-menu dropdown-menu-right"> | ||||
|             <span class="dropdown-header"><%- i18n('audit-log', 'redirection-host') %> #<%- id %></span> | ||||
|             <a href="#" class="edit dropdown-item"><i class="dropdown-icon fe fe-edit"></i> <%- i18n('str', 'edit') %></a> | ||||
|             <a href="#" class="able dropdown-item"><i class="dropdown-icon fe fe-power"></i> <%- i18n('str', enabled ? 'disable' : 'enable') %></a> | ||||
|             <div class="dropdown-divider"></div> | ||||
|             <a href="#" class="delete dropdown-item"><i class="dropdown-icon fe fe-trash-2"></i> <%- i18n('str', 'delete') %></a> | ||||
|         </div> | ||||
|     </div> | ||||
| </td> | ||||
| <% } %> | ||||
| @@ -1,61 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const template = require('./item.ejs'); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     template: template, | ||||
|     tagName:  'tr', | ||||
|  | ||||
|     ui: { | ||||
|         able:      'a.able', | ||||
|         edit:      'a.edit', | ||||
|         delete:    'a.delete', | ||||
|         host_link: '.host-link' | ||||
|     }, | ||||
|  | ||||
|     events: { | ||||
|         'click @ui.able': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let id = this.model.get('id'); | ||||
|             App.Api.Nginx.RedirectionHosts[this.model.get('enabled') ? 'disable' : 'enable'](id) | ||||
|                 .then(() => { | ||||
|                     return App.Api.Nginx.RedirectionHosts.get(id) | ||||
|                         .then(row => { | ||||
|                             this.model.set(row); | ||||
|                         }); | ||||
|                 }); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.edit': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxRedirectionForm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.delete': function (e) { | ||||
|             e.preventDefault(); | ||||
|             App.Controller.showNginxRedirectionDeleteConfirm(this.model); | ||||
|         }, | ||||
|  | ||||
|         'click @ui.host_link': function (e) { | ||||
|             e.preventDefault(); | ||||
|             let win = window.open($(e.currentTarget).attr('rel'), '_blank'); | ||||
|             win.focus(); | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('redirection_hosts'), | ||||
|  | ||||
|         isOnline: function () { | ||||
|             return typeof this.meta.nginx_online === 'undefined' ? null : this.meta.nginx_online; | ||||
|         }, | ||||
|  | ||||
|         getOfflineError: function () { | ||||
|             return this.meta.nginx_err || ''; | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     initialize: function () { | ||||
|         this.listenTo(this.model, 'change', this.render); | ||||
|     } | ||||
| }); | ||||
| @@ -1,15 +0,0 @@ | ||||
| <thead> | ||||
|     <th width="30"> </th> | ||||
|     <th><%- i18n('str', 'source') %></th> | ||||
|     <th><%- i18n('redirection-hosts', 'forward-http-status-code') %></th> | ||||
|     <th><%- i18n('redirection-hosts', 'forward-scheme') %></th> | ||||
|     <th><%- i18n('str', 'destination') %></th> | ||||
|     <th><%- i18n('str', 'ssl') %></th> | ||||
|     <th><%- i18n('str', 'status') %></th> | ||||
|     <% if (canManage) { %> | ||||
|         <th> </th> | ||||
|     <% } %> | ||||
| </thead> | ||||
| <tbody> | ||||
|     <!-- items --> | ||||
| </tbody> | ||||
| @@ -1,32 +0,0 @@ | ||||
| const Mn       = require('backbone.marionette'); | ||||
| const App      = require('../../../main'); | ||||
| const ItemView = require('./item'); | ||||
| const template = require('./main.ejs'); | ||||
|  | ||||
| const TableBody = Mn.CollectionView.extend({ | ||||
|     tagName:   'tbody', | ||||
|     childView: ItemView | ||||
| }); | ||||
|  | ||||
| module.exports = Mn.View.extend({ | ||||
|     tagName:   'table', | ||||
|     className: 'table table-hover table-outline table-vcenter card-table', | ||||
|     template:  template, | ||||
|  | ||||
|     regions: { | ||||
|         body: { | ||||
|             el:             'tbody', | ||||
|             replaceElement: true | ||||
|         } | ||||
|     }, | ||||
|  | ||||
|     templateContext: { | ||||
|         canManage: App.Cache.User.canManage('redirection_hosts') | ||||
|     }, | ||||
|  | ||||
|     onRender: function () { | ||||
|         this.showChildView('body', new TableBody({ | ||||
|             collection: this.collection | ||||
|         })); | ||||
|     } | ||||
| }); | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user