├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── .htaccess ├── app │ ├── css │ │ ├── app.css │ │ ├── fontawesome.css │ │ └── tailwind.css │ ├── favicon │ │ ├── android-icon-144x144.png │ │ ├── android-icon-192x192.png │ │ ├── android-icon-36x36.png │ │ ├── android-icon-48x48.png │ │ ├── android-icon-72x72.png │ │ ├── android-icon-96x96.png │ │ ├── apple-icon-114x114.png │ │ ├── apple-icon-120x120.png │ │ ├── apple-icon-144x144.png │ │ ├── apple-icon-152x152.png │ │ ├── apple-icon-180x180.png │ │ ├── apple-icon-57x57.png │ │ ├── apple-icon-60x60.png │ │ ├── apple-icon-72x72.png │ │ ├── apple-icon-76x76.png │ │ ├── apple-icon-precomposed.png │ │ ├── apple-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── favicon.ico │ │ ├── manifest.json │ │ ├── ms-icon-144x144.png │ │ ├── ms-icon-150x150.png │ │ ├── ms-icon-310x310.png │ │ └── ms-icon-70x70.png │ ├── js │ │ ├── app.js │ │ ├── axios.js │ │ └── vue.js │ └── webfonts │ │ ├── fa-brands-400.eot │ │ ├── fa-brands-400.svg │ │ ├── fa-brands-400.ttf │ │ ├── fa-brands-400.woff │ │ ├── fa-brands-400.woff2 │ │ ├── fa-regular-400.eot │ │ ├── fa-regular-400.svg │ │ ├── fa-regular-400.ttf │ │ ├── fa-regular-400.woff │ │ ├── fa-regular-400.woff2 │ │ ├── fa-solid-900.eot │ │ ├── fa-solid-900.svg │ │ ├── fa-solid-900.ttf │ │ ├── fa-solid-900.woff │ │ └── fa-solid-900.woff2 ├── class │ └── Server.php ├── controller │ ├── api │ │ ├── addserver.php │ │ ├── completesetup.php │ │ ├── countserver.php │ │ ├── deleteserver.php │ │ ├── getstatus.php │ │ ├── getvalue.php │ │ ├── getvalues.php │ │ ├── isfirstsetup.php │ │ └── listserver.php │ └── index.php ├── core │ ├── Router.php │ └── bootstrap.php ├── data │ └── .gitkeep ├── index.php ├── routes.php └── view │ ├── component │ ├── addhost.view.php │ ├── deletehost.view.php │ ├── edithost.view.php │ ├── help.view.php │ └── list.view.php │ ├── footer.view.php │ ├── header.view.php │ └── index.view.php ├── tailwind.config.js └── tailwind.css /.gitignore: -------------------------------------------------------------------------------- 1 | public/data/*.json 2 | node_modules/ 3 | node_modules/* 4 | .DS_Store 5 | .vscode/* 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Servermanager 2 | 3 | A simple file-based webapp to keep track of your hosting environment. 4 | It comes with an integrated search and uptime function. 5 | 6 | ## 1. Requirements 7 | - Apache 2.4 with mod_rewrite enabled 8 | - PHP 7.4+ with php exec enabled if you want to use the Ping function 9 | 10 | 11 | The ping method requires the exec function to be enabled. 12 | Please keep in mind that the hostname of the host will be used to ping, not the IP(s). 13 | 14 | 15 | ## 2. Installation 16 | Upload the application to your server or shared hosting and set the document root to the "public" folder. 17 | Make sure that mod_rewrite is enabled, the .htaccess file is present and the permissions are set. 18 | 19 | 20 | Since there is no authentification for the frontend and the API you should configure a htpasswd protection or restrict the IP access. 21 | 22 | 23 | ## 3. Config 24 | You can change set the following parameters in the ```app/js/app.js``` 25 | 26 | ```javascript 27 | title: 'My hosts', 28 | currency: '€', 29 | billingTerm: 'month', 30 | // Set this to true if you don't want to see the pricing input and table view 31 | // This is useful when used internally and you don't need to specifiy the price for hosts 32 | disablePricing: false, 33 | // This settings disables the ping check in the background, set this to true 34 | // if your environment doesn't support php exec or you don't need this this function 35 | disablePing: false, 36 | ``` 37 | 38 | 39 | ## 4. Demo 40 | ![](https://i.imgur.com/JZaBOjh.png) 41 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "boilerplate", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "build": "npm run buildTailwind && npm run copyVue && npm run copyAxios && npm run copyFaStyle && npm run copyFaFont", 8 | "buildTailwind": "npx tailwindcss build tailwind.css -o public/app/css/tailwind.css", 9 | "copyTailwindUI": "@powershell copy 'node_modules/@tailwindcss/ui/dist/tailwind-ui.min.css' 'public/app/css/tailwind-ui.min.css' || cp 'node_modules/@tailwindcss/ui/dist/tailwind-ui.min.css' 'public/app/css/tailwind-ui.min.css'", 10 | "copyVue": "@powershell copy 'node_modules/vue/dist/vue.js' 'public/app/js/vue.js' || cp 'node_modules/vue/dist/vue.js' 'public/app/js/vue.js'", 11 | "copyAxios": "@powershell copy 'node_modules/axios/dist/axios.js' 'public/app/js/axios.js' || cp 'node_modules/axios/dist/axios.js' 'public/app/js/axios.js'", 12 | "copyFaStyle": "@powershell copy 'node_modules/@fortawesome/fontawesome-free/css/all.css' 'public/app/css/fontawesome.css' || cp 'node_modules/@fortawesome/fontawesome-free/css/all.css' 'public/app/css/fontawesome.css'", 13 | "copyFaFont": "@powershell copy 'node_modules/@fortawesome/fontawesome-free/webfonts/*' 'public/app/webfonts/' || cp node_modules/@fortawesome/fontawesome-free/webfonts/* public/app/webfonts/", 14 | "serve": "live-server --open=public/", 15 | "update": "rm package-lock.json && rm -rf node_modules public/app/css/tailwind-ui.min.css public/app/css/tailwind.css public/app/css/fontawesome.css public/app/js/axios.js public/app/webfonts/* public/app/js/vue.js && npm update --save-dev && npm update --save && npm install && npm run build" 16 | }, 17 | "author": "", 18 | "license": "ISC", 19 | "dependencies": { 20 | "@fortawesome/fontawesome-free": "^5.12.1", 21 | "autoprefixer": "^10.1.0", 22 | "axios": "^0.21.1", 23 | "live-server": "^1.2.1", 24 | "postcss": ">=8.2.10", 25 | "tailwindcss": "^2.0.2", 26 | "vue": "^2.6.11" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/.htaccess: -------------------------------------------------------------------------------- 1 | RewriteEngine on 2 | RewriteCond %{REQUEST_FILENAME} !-f 3 | RewriteCond %{REQUEST_FILENAME} !-d 4 | RewriteCond %{REQUEST_URI} !/app 5 | RewriteRule ^(.*)$ index.php?q=$1 [L,QSA] 6 | -------------------------------------------------------------------------------- /public/app/css/app.css: -------------------------------------------------------------------------------- 1 | @import url('https://rsms.me/inter/inter.css'); 2 | html { font-family: 'Inter', sans-serif; } 3 | @supports (font-variation-settings: normal) { 4 | html { font-family: 'Inter var', sans-serif; } 5 | } 6 | 7 | tr:nth-child(even) { 8 | background: #F3F4F6; 9 | } 10 | 11 | .tooltip { 12 | --tw-bg-opacity: 1; 13 | background-color: rgba(243, 244, 246, var(--tw-bg-opacity)); 14 | --tw-border-opacity: 1; 15 | border-color: rgba(229, 231, 235, var(--tw-border-opacity)); 16 | border-radius: 0.25rem; 17 | border-width: 1px; 18 | font-size: 0.875rem; 19 | line-height: 1.25rem; 20 | margin-left: 1rem; 21 | margin-top: -2rem; 22 | padding: 0.25rem; 23 | position: absolute; 24 | --tw-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); 25 | box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); 26 | visibility: hidden; 27 | } 28 | 29 | .has-tooltip:hover .tooltip { 30 | visibility: visible; 31 | z-index: 50 32 | } -------------------------------------------------------------------------------- /public/app/favicon/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/android-icon-144x144.png -------------------------------------------------------------------------------- /public/app/favicon/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/android-icon-192x192.png -------------------------------------------------------------------------------- /public/app/favicon/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/android-icon-36x36.png -------------------------------------------------------------------------------- /public/app/favicon/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/android-icon-48x48.png -------------------------------------------------------------------------------- /public/app/favicon/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/android-icon-72x72.png -------------------------------------------------------------------------------- /public/app/favicon/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/android-icon-96x96.png -------------------------------------------------------------------------------- /public/app/favicon/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/apple-icon-114x114.png -------------------------------------------------------------------------------- /public/app/favicon/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/app/favicon/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/apple-icon-144x144.png -------------------------------------------------------------------------------- /public/app/favicon/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/app/favicon/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/app/favicon/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/apple-icon-57x57.png -------------------------------------------------------------------------------- /public/app/favicon/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/apple-icon-60x60.png -------------------------------------------------------------------------------- /public/app/favicon/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/apple-icon-72x72.png -------------------------------------------------------------------------------- /public/app/favicon/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/apple-icon-76x76.png -------------------------------------------------------------------------------- /public/app/favicon/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/apple-icon-precomposed.png -------------------------------------------------------------------------------- /public/app/favicon/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/apple-icon.png -------------------------------------------------------------------------------- /public/app/favicon/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /public/app/favicon/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/favicon-16x16.png -------------------------------------------------------------------------------- /public/app/favicon/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/favicon-32x32.png -------------------------------------------------------------------------------- /public/app/favicon/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/favicon-96x96.png -------------------------------------------------------------------------------- /public/app/favicon/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/favicon.ico -------------------------------------------------------------------------------- /public/app/favicon/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /public/app/favicon/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/app/favicon/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/ms-icon-150x150.png -------------------------------------------------------------------------------- /public/app/favicon/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/ms-icon-310x310.png -------------------------------------------------------------------------------- /public/app/favicon/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/favicon/ms-icon-70x70.png -------------------------------------------------------------------------------- /public/app/js/app.js: -------------------------------------------------------------------------------- 1 | var app = new Vue({ 2 | el: '#app', 3 | data: { 4 | // 5 | // 6 | // 7 | // Custom config 8 | // 9 | // 10 | // App title 11 | title: 'My hosts', 12 | currency: '€', 13 | billingTerm: 'month', 14 | // Set this to true if you don't want to see the pricing input and table view 15 | // This is useful when used internally and you don't need to specifiy the price for hosts 16 | disablePricing: false, 17 | //This settings disables the ping check in the background, set this to true 18 | // if your environment doesn't support php exec or you don't need this this function 19 | disablePing: false, 20 | //This setting disables fetching and displaying asn information for IP addresses 21 | disableAsn: true, 22 | // 23 | // 24 | // 25 | // 26 | // 27 | // 28 | // 29 | // 30 | // Form values 31 | hosts: [], 32 | status: null, 33 | id: null, 34 | name: null, 35 | hostname: null, 36 | location: null, 37 | tags: null, 38 | ressources: null, 39 | provider: null, 40 | ips: null, 41 | price: null, 42 | type: null, 43 | notes: null, 44 | os: null, 45 | bulkAdd: false, 46 | // State of UI objects 47 | sidebarOpen: false, 48 | modalOpen: false, 49 | addHostOpen: false, 50 | editHostOpen: false, 51 | settingsOpen: false, 52 | showHelp: null, 53 | // ID of the account to be deleted 54 | deleteHostID: null, 55 | // Search query 56 | searchQuery: null, 57 | // Sorting the list view 58 | currentSort: null, 59 | currentSortDir: null, 60 | // Misc 61 | pendingEditResponse: true, 62 | editHost: null, 63 | hostStatus: [], 64 | pendingStatus: true, 65 | errors: [] 66 | }, 67 | methods: { 68 | // Check if value is undefined, null or not set 69 | isUndefined(value) { 70 | return value === 'undefined' || value === 'null' || !value; 71 | }, 72 | // Split the tag array after each "," 73 | filterTags(value) { 74 | if (this.isUndefined(value)) { 75 | return null 76 | } else { 77 | return value.toString().split(","); 78 | } 79 | }, 80 | // Return default value("-") when a value is undefined 81 | orDefault(value) { 82 | if (this.isUndefined(value)) { 83 | return '-'; 84 | } else { 85 | return value; 86 | } 87 | }, 88 | // Toggle the modal and set deleteHostID to the given ID 89 | toggelDeleteModal(id) { 90 | this.modalOpen = !this.modalOpen; 91 | this.deleteHostID = id; 92 | }, 93 | // Switch sort direction 94 | sort(s) { 95 | if(s === this.currentSort) { 96 | this.currentSortDir = this.currentSortDir === 'asc' ? 'desc' : 'asc'; 97 | } else { 98 | this.currentSortDir = 'asc'; 99 | } 100 | this.currentSort = s; 101 | }, 102 | // Delete server by the ID 103 | deleteServer() { 104 | var params = new URLSearchParams(); 105 | params.append('id', this.deleteHostID); 106 | axios.post('/api/deleteserver', params) 107 | .then(response => { 108 | this.getServer() 109 | }) 110 | .catch(function (error) { 111 | console.log(error); 112 | }); 113 | this.modalOpen = false; 114 | this.deleteHostID = null; 115 | }, 116 | // Get all servers and assign the objects to the hosts array 117 | getServer() { 118 | axios 119 | .get('/api/listserver') 120 | .then(response => ( 121 | this.hosts = response.data 122 | )) 123 | }, 124 | // Get hosts status (ICMP ping) for the given ID 125 | // This method is called when mounted and has an interval 126 | getStatus(id) { 127 | this.hosts.forEach(host => { 128 | var params = new URLSearchParams(); 129 | params.append('id', host.id); 130 | axios.post('/api/getstatus', params) 131 | .then(response => { 132 | if (response.data == 1) { 133 | this.hostStatus[host.id] = true; 134 | return true; 135 | } else { 136 | this.hostStatus[host.id] = false; 137 | return false; 138 | } 139 | }) 140 | .catch(function (error) { 141 | console.log(error); 142 | }); 143 | this.pendingStatus = false; 144 | }); 145 | }, 146 | // Check if the var is null and return an empty string 147 | // This avoids "null" in the HTML input fields when editing a host 148 | checkForValue(value) { 149 | if (value == "null") { 150 | return ""; 151 | } else { 152 | return value; 153 | } 154 | }, 155 | // Parse `ips` value and make sure it's an object 156 | displayIPs(ips) { 157 | if (Array.isArray(ips)) { 158 | return ips; 159 | } 160 | else { 161 | let ip_array = []; 162 | ips.split(',').forEach(ip => { 163 | ip_array.push({'ip': ip.trim()}); 164 | }) 165 | return ip_array; 166 | } 167 | }, 168 | // Edit an existing server 169 | // - Open UI Sidebar 170 | // - Set response to pending while waiting 171 | // - Fetch the existing host data 172 | editServer(id) { 173 | this.editHostOpen = true; 174 | this.pendingEditResponse = true 175 | // Fetch the existing values 176 | var params = new URLSearchParams(); 177 | params.append('id', id); 178 | axios.post('/api/getvalues', params) 179 | .then(response => ( 180 | this.editHost = response.data, 181 | this.id = this.editHost.id, 182 | this.name = this.editHost.name, 183 | this.hostname = this.editHost.hostname, 184 | // Those vars might be null, so we have to check for their value 185 | this.location = this.checkForValue(this.editHost.location), 186 | this.tags = this.checkForValue(this.editHost.tags), 187 | this.ressources = this.checkForValue(this.editHost.ressources), 188 | this.provider = this.checkForValue(this.editHost.provider), 189 | this.type = this.checkForValue(this.editHost.type), 190 | this.os = this.checkForValue(this.editHost.os), 191 | this.ips = this.checkForValue(this.displayIPs(this.editHost.ips).map(x => x.ip).join(', ')), 192 | this.price = this.checkForValue(this.editHost.price), 193 | this.notes = this.checkForValue(this.editHost.notes), 194 | this.pendingEditResponse = false 195 | )) 196 | }, 197 | // Add/ edit existing server 198 | // This method will add or edit an host depending on whether an id is passed. 199 | // No ID? => Add new server 200 | // Passed ID? => Edit existing and keep the ID+ 201 | addServer(id) { 202 | // Throw error if mandatory fields are empty 203 | if (!this.name || this.name == "" || !this.hostname || this.hostname == "") { 204 | this.throwError("Please fill out all required fields"); 205 | return; 206 | } 207 | // Throw error if price is not numeric 208 | if (isNaN(this.price)) { 209 | this.throwError("The price must only contain numbers"); 210 | return; 211 | } 212 | var params = new URLSearchParams(); 213 | // Only append the ID if it was given => edit existing server 214 | if (id) { 215 | params.append('id', id); 216 | } 217 | params.append('name', this.name); 218 | params.append('hostname', this.hostname); 219 | params.append('location', this.location); 220 | params.append('tags', this.tags); 221 | params.append('ressources', this.ressources); 222 | params.append('provider', this.provider); 223 | params.append('type', this.type); 224 | params.append('os', this.os); 225 | params.append('ips', this.ips); 226 | params.append('price', this.price); 227 | params.append('notes', this.notes); 228 | params.append('disable_asn', this.disableAsn); 229 | axios.post('/api/addserver', params) 230 | .then(response => { 231 | this.getServer(); 232 | 233 | if(!this.bulkAdd) { 234 | this.addHostOpen = false; 235 | this.editHostOpen = false; 236 | this.clearForm(); 237 | } 238 | this.clearErrors(); 239 | }) 240 | .catch(function (error) { 241 | console.log(error); 242 | }); 243 | 244 | }, 245 | // Add error message to the errors array if the message doesnt exist yet 246 | throwError(message) { 247 | if (this.errors.indexOf(message) === -1) this.errors.push(message); 248 | }, 249 | // Remove all existing errors 250 | clearErrors() { 251 | this.errors = []; 252 | }, 253 | // Reset and close form 254 | cancelForm(flag) { 255 | this.clearForm(); 256 | this.clearErrors(); 257 | return !flag; 258 | }, 259 | // Clear form 260 | clearForm() { 261 | this.name = ""; 262 | this.hostname = ""; 263 | this.location = ""; 264 | this.tags = ""; 265 | this.ressources = ""; 266 | this.provider = ""; 267 | this.type = ""; 268 | this.os = ""; 269 | this.ips = ""; 270 | this.price = ""; 271 | this.notes = ""; 272 | }, 273 | searchTag(tag) { 274 | // Tag is already set, clear the input field 275 | if(this.searchQuery == tag) { 276 | this.searchQuery = null; 277 | } 278 | // Set searchQuery to the tag value 279 | else { 280 | this.searchQuery = tag; 281 | } 282 | }, 283 | isFirstSetup() { 284 | axios 285 | .get('/api/isfirstsetup') 286 | .then(response => ( 287 | this.showHelp = response.data 288 | )) 289 | }, 290 | completeSetup() { 291 | this.showHelp = !this.showHelp; 292 | axios 293 | .post('/api/completesetup') 294 | } 295 | }, 296 | computed: { 297 | // List hosts 298 | // Only return hosts that match the search query if any 299 | // Sort them if a field to sort by is selected 300 | filteredHosts() { 301 | if (this.searchQuery) { 302 | hostlist = this.hosts.filter((host) => { 303 | return this.searchQuery.toLowerCase().split(' ').every(v => 304 | host.name.toString().toLowerCase().includes(v) || 305 | host.hostname.toString().toLowerCase().includes(v) || 306 | host.tags.toString().toLowerCase().includes(v) || 307 | host.ressources.toString().toLowerCase().includes(v) || 308 | host.location.toString().toLowerCase().includes(v) || 309 | host.provider.toString().toLowerCase().includes(v) || 310 | host.ips.toString().includes(v) || 311 | host.type.toString().toLowerCase().includes(v) || 312 | host.os.toString().toLowerCase().includes(v)) 313 | //host.price.includes(v)) 314 | }) 315 | } else { 316 | hostlist = this.hosts; 317 | } 318 | if (this.currentSort === null) { 319 | return hostlist; 320 | } 321 | return hostlist.sort((a, b) => { 322 | a = a[this.currentSort]; 323 | b = b[this.currentSort]; 324 | // equal items sort equally 325 | if (a === b) { 326 | return 0; 327 | } 328 | // nulls sort after anything else 329 | else if (this.isUndefined(a)) { 330 | return 1; 331 | } 332 | else if (this.isUndefined(b)) { 333 | return -1; 334 | } 335 | // otherwise, if we're ascending, lowest sorts first 336 | else if (this.currentSortDir === 'asc') { 337 | return a < b ? -1 : 1; 338 | } 339 | // if descending, highest sorts first 340 | else { 341 | return a < b ? 1 : -1; 342 | } 343 | }); 344 | }, 345 | // Display small triangle to mark sort direction 346 | displaySortDirection() { 347 | switch (this.currentSortDir) { 348 | case "asc": 349 | return "▲"; 350 | case "desc": 351 | return "▼"; 352 | } 353 | } 354 | }, 355 | mounted() { 356 | this.isFirstSetup(); 357 | this.getServer(); 358 | // Only ping if the setting is enabled 359 | if(!this.disablePing) { 360 | this.getStatus(); 361 | } 362 | this.interval = setInterval(() => this.getServer(), 5000); 363 | if(!this.disablePing) { 364 | this.interval = setInterval(() => this.getStatus(), 8000); 365 | } 366 | document.title = this.title; 367 | } 368 | }) -------------------------------------------------------------------------------- /public/app/js/axios.js: -------------------------------------------------------------------------------- 1 | /* axios v0.21.1 | (c) 2020 by Matt Zabriskie */ 2 | (function webpackUniversalModuleDefinition(root, factory) { 3 | if(typeof exports === 'object' && typeof module === 'object') 4 | module.exports = factory(); 5 | else if(typeof define === 'function' && define.amd) 6 | define([], factory); 7 | else if(typeof exports === 'object') 8 | exports["axios"] = factory(); 9 | else 10 | root["axios"] = factory(); 11 | })(this, function() { 12 | return /******/ (function(modules) { // webpackBootstrap 13 | /******/ // The module cache 14 | /******/ var installedModules = {}; 15 | /******/ 16 | /******/ // The require function 17 | /******/ function __webpack_require__(moduleId) { 18 | /******/ 19 | /******/ // Check if module is in cache 20 | /******/ if(installedModules[moduleId]) 21 | /******/ return installedModules[moduleId].exports; 22 | /******/ 23 | /******/ // Create a new module (and put it into the cache) 24 | /******/ var module = installedModules[moduleId] = { 25 | /******/ exports: {}, 26 | /******/ id: moduleId, 27 | /******/ loaded: false 28 | /******/ }; 29 | /******/ 30 | /******/ // Execute the module function 31 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 32 | /******/ 33 | /******/ // Flag the module as loaded 34 | /******/ module.loaded = true; 35 | /******/ 36 | /******/ // Return the exports of the module 37 | /******/ return module.exports; 38 | /******/ } 39 | /******/ 40 | /******/ 41 | /******/ // expose the modules object (__webpack_modules__) 42 | /******/ __webpack_require__.m = modules; 43 | /******/ 44 | /******/ // expose the module cache 45 | /******/ __webpack_require__.c = installedModules; 46 | /******/ 47 | /******/ // __webpack_public_path__ 48 | /******/ __webpack_require__.p = ""; 49 | /******/ 50 | /******/ // Load entry module and return exports 51 | /******/ return __webpack_require__(0); 52 | /******/ }) 53 | /************************************************************************/ 54 | /******/ ([ 55 | /* 0 */ 56 | /***/ (function(module, exports, __webpack_require__) { 57 | 58 | module.exports = __webpack_require__(1); 59 | 60 | /***/ }), 61 | /* 1 */ 62 | /***/ (function(module, exports, __webpack_require__) { 63 | 64 | 'use strict'; 65 | 66 | var utils = __webpack_require__(2); 67 | var bind = __webpack_require__(3); 68 | var Axios = __webpack_require__(4); 69 | var mergeConfig = __webpack_require__(22); 70 | var defaults = __webpack_require__(10); 71 | 72 | /** 73 | * Create an instance of Axios 74 | * 75 | * @param {Object} defaultConfig The default config for the instance 76 | * @return {Axios} A new instance of Axios 77 | */ 78 | function createInstance(defaultConfig) { 79 | var context = new Axios(defaultConfig); 80 | var instance = bind(Axios.prototype.request, context); 81 | 82 | // Copy axios.prototype to instance 83 | utils.extend(instance, Axios.prototype, context); 84 | 85 | // Copy context to instance 86 | utils.extend(instance, context); 87 | 88 | return instance; 89 | } 90 | 91 | // Create the default instance to be exported 92 | var axios = createInstance(defaults); 93 | 94 | // Expose Axios class to allow class inheritance 95 | axios.Axios = Axios; 96 | 97 | // Factory for creating new instances 98 | axios.create = function create(instanceConfig) { 99 | return createInstance(mergeConfig(axios.defaults, instanceConfig)); 100 | }; 101 | 102 | // Expose Cancel & CancelToken 103 | axios.Cancel = __webpack_require__(23); 104 | axios.CancelToken = __webpack_require__(24); 105 | axios.isCancel = __webpack_require__(9); 106 | 107 | // Expose all/spread 108 | axios.all = function all(promises) { 109 | return Promise.all(promises); 110 | }; 111 | axios.spread = __webpack_require__(25); 112 | 113 | // Expose isAxiosError 114 | axios.isAxiosError = __webpack_require__(26); 115 | 116 | module.exports = axios; 117 | 118 | // Allow use of default import syntax in TypeScript 119 | module.exports.default = axios; 120 | 121 | 122 | /***/ }), 123 | /* 2 */ 124 | /***/ (function(module, exports, __webpack_require__) { 125 | 126 | 'use strict'; 127 | 128 | var bind = __webpack_require__(3); 129 | 130 | /*global toString:true*/ 131 | 132 | // utils is a library of generic helper functions non-specific to axios 133 | 134 | var toString = Object.prototype.toString; 135 | 136 | /** 137 | * Determine if a value is an Array 138 | * 139 | * @param {Object} val The value to test 140 | * @returns {boolean} True if value is an Array, otherwise false 141 | */ 142 | function isArray(val) { 143 | return toString.call(val) === '[object Array]'; 144 | } 145 | 146 | /** 147 | * Determine if a value is undefined 148 | * 149 | * @param {Object} val The value to test 150 | * @returns {boolean} True if the value is undefined, otherwise false 151 | */ 152 | function isUndefined(val) { 153 | return typeof val === 'undefined'; 154 | } 155 | 156 | /** 157 | * Determine if a value is a Buffer 158 | * 159 | * @param {Object} val The value to test 160 | * @returns {boolean} True if value is a Buffer, otherwise false 161 | */ 162 | function isBuffer(val) { 163 | return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor) 164 | && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val); 165 | } 166 | 167 | /** 168 | * Determine if a value is an ArrayBuffer 169 | * 170 | * @param {Object} val The value to test 171 | * @returns {boolean} True if value is an ArrayBuffer, otherwise false 172 | */ 173 | function isArrayBuffer(val) { 174 | return toString.call(val) === '[object ArrayBuffer]'; 175 | } 176 | 177 | /** 178 | * Determine if a value is a FormData 179 | * 180 | * @param {Object} val The value to test 181 | * @returns {boolean} True if value is an FormData, otherwise false 182 | */ 183 | function isFormData(val) { 184 | return (typeof FormData !== 'undefined') && (val instanceof FormData); 185 | } 186 | 187 | /** 188 | * Determine if a value is a view on an ArrayBuffer 189 | * 190 | * @param {Object} val The value to test 191 | * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false 192 | */ 193 | function isArrayBufferView(val) { 194 | var result; 195 | if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { 196 | result = ArrayBuffer.isView(val); 197 | } else { 198 | result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer); 199 | } 200 | return result; 201 | } 202 | 203 | /** 204 | * Determine if a value is a String 205 | * 206 | * @param {Object} val The value to test 207 | * @returns {boolean} True if value is a String, otherwise false 208 | */ 209 | function isString(val) { 210 | return typeof val === 'string'; 211 | } 212 | 213 | /** 214 | * Determine if a value is a Number 215 | * 216 | * @param {Object} val The value to test 217 | * @returns {boolean} True if value is a Number, otherwise false 218 | */ 219 | function isNumber(val) { 220 | return typeof val === 'number'; 221 | } 222 | 223 | /** 224 | * Determine if a value is an Object 225 | * 226 | * @param {Object} val The value to test 227 | * @returns {boolean} True if value is an Object, otherwise false 228 | */ 229 | function isObject(val) { 230 | return val !== null && typeof val === 'object'; 231 | } 232 | 233 | /** 234 | * Determine if a value is a plain Object 235 | * 236 | * @param {Object} val The value to test 237 | * @return {boolean} True if value is a plain Object, otherwise false 238 | */ 239 | function isPlainObject(val) { 240 | if (toString.call(val) !== '[object Object]') { 241 | return false; 242 | } 243 | 244 | var prototype = Object.getPrototypeOf(val); 245 | return prototype === null || prototype === Object.prototype; 246 | } 247 | 248 | /** 249 | * Determine if a value is a Date 250 | * 251 | * @param {Object} val The value to test 252 | * @returns {boolean} True if value is a Date, otherwise false 253 | */ 254 | function isDate(val) { 255 | return toString.call(val) === '[object Date]'; 256 | } 257 | 258 | /** 259 | * Determine if a value is a File 260 | * 261 | * @param {Object} val The value to test 262 | * @returns {boolean} True if value is a File, otherwise false 263 | */ 264 | function isFile(val) { 265 | return toString.call(val) === '[object File]'; 266 | } 267 | 268 | /** 269 | * Determine if a value is a Blob 270 | * 271 | * @param {Object} val The value to test 272 | * @returns {boolean} True if value is a Blob, otherwise false 273 | */ 274 | function isBlob(val) { 275 | return toString.call(val) === '[object Blob]'; 276 | } 277 | 278 | /** 279 | * Determine if a value is a Function 280 | * 281 | * @param {Object} val The value to test 282 | * @returns {boolean} True if value is a Function, otherwise false 283 | */ 284 | function isFunction(val) { 285 | return toString.call(val) === '[object Function]'; 286 | } 287 | 288 | /** 289 | * Determine if a value is a Stream 290 | * 291 | * @param {Object} val The value to test 292 | * @returns {boolean} True if value is a Stream, otherwise false 293 | */ 294 | function isStream(val) { 295 | return isObject(val) && isFunction(val.pipe); 296 | } 297 | 298 | /** 299 | * Determine if a value is a URLSearchParams object 300 | * 301 | * @param {Object} val The value to test 302 | * @returns {boolean} True if value is a URLSearchParams object, otherwise false 303 | */ 304 | function isURLSearchParams(val) { 305 | return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams; 306 | } 307 | 308 | /** 309 | * Trim excess whitespace off the beginning and end of a string 310 | * 311 | * @param {String} str The String to trim 312 | * @returns {String} The String freed of excess whitespace 313 | */ 314 | function trim(str) { 315 | return str.replace(/^\s*/, '').replace(/\s*$/, ''); 316 | } 317 | 318 | /** 319 | * Determine if we're running in a standard browser environment 320 | * 321 | * This allows axios to run in a web worker, and react-native. 322 | * Both environments support XMLHttpRequest, but not fully standard globals. 323 | * 324 | * web workers: 325 | * typeof window -> undefined 326 | * typeof document -> undefined 327 | * 328 | * react-native: 329 | * navigator.product -> 'ReactNative' 330 | * nativescript 331 | * navigator.product -> 'NativeScript' or 'NS' 332 | */ 333 | function isStandardBrowserEnv() { 334 | if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' || 335 | navigator.product === 'NativeScript' || 336 | navigator.product === 'NS')) { 337 | return false; 338 | } 339 | return ( 340 | typeof window !== 'undefined' && 341 | typeof document !== 'undefined' 342 | ); 343 | } 344 | 345 | /** 346 | * Iterate over an Array or an Object invoking a function for each item. 347 | * 348 | * If `obj` is an Array callback will be called passing 349 | * the value, index, and complete array for each item. 350 | * 351 | * If 'obj' is an Object callback will be called passing 352 | * the value, key, and complete object for each property. 353 | * 354 | * @param {Object|Array} obj The object to iterate 355 | * @param {Function} fn The callback to invoke for each item 356 | */ 357 | function forEach(obj, fn) { 358 | // Don't bother if no value provided 359 | if (obj === null || typeof obj === 'undefined') { 360 | return; 361 | } 362 | 363 | // Force an array if not already something iterable 364 | if (typeof obj !== 'object') { 365 | /*eslint no-param-reassign:0*/ 366 | obj = [obj]; 367 | } 368 | 369 | if (isArray(obj)) { 370 | // Iterate over array values 371 | for (var i = 0, l = obj.length; i < l; i++) { 372 | fn.call(null, obj[i], i, obj); 373 | } 374 | } else { 375 | // Iterate over object keys 376 | for (var key in obj) { 377 | if (Object.prototype.hasOwnProperty.call(obj, key)) { 378 | fn.call(null, obj[key], key, obj); 379 | } 380 | } 381 | } 382 | } 383 | 384 | /** 385 | * Accepts varargs expecting each argument to be an object, then 386 | * immutably merges the properties of each object and returns result. 387 | * 388 | * When multiple objects contain the same key the later object in 389 | * the arguments list will take precedence. 390 | * 391 | * Example: 392 | * 393 | * ```js 394 | * var result = merge({foo: 123}, {foo: 456}); 395 | * console.log(result.foo); // outputs 456 396 | * ``` 397 | * 398 | * @param {Object} obj1 Object to merge 399 | * @returns {Object} Result of all merge properties 400 | */ 401 | function merge(/* obj1, obj2, obj3, ... */) { 402 | var result = {}; 403 | function assignValue(val, key) { 404 | if (isPlainObject(result[key]) && isPlainObject(val)) { 405 | result[key] = merge(result[key], val); 406 | } else if (isPlainObject(val)) { 407 | result[key] = merge({}, val); 408 | } else if (isArray(val)) { 409 | result[key] = val.slice(); 410 | } else { 411 | result[key] = val; 412 | } 413 | } 414 | 415 | for (var i = 0, l = arguments.length; i < l; i++) { 416 | forEach(arguments[i], assignValue); 417 | } 418 | return result; 419 | } 420 | 421 | /** 422 | * Extends object a by mutably adding to it the properties of object b. 423 | * 424 | * @param {Object} a The object to be extended 425 | * @param {Object} b The object to copy properties from 426 | * @param {Object} thisArg The object to bind function to 427 | * @return {Object} The resulting value of object a 428 | */ 429 | function extend(a, b, thisArg) { 430 | forEach(b, function assignValue(val, key) { 431 | if (thisArg && typeof val === 'function') { 432 | a[key] = bind(val, thisArg); 433 | } else { 434 | a[key] = val; 435 | } 436 | }); 437 | return a; 438 | } 439 | 440 | /** 441 | * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) 442 | * 443 | * @param {string} content with BOM 444 | * @return {string} content value without BOM 445 | */ 446 | function stripBOM(content) { 447 | if (content.charCodeAt(0) === 0xFEFF) { 448 | content = content.slice(1); 449 | } 450 | return content; 451 | } 452 | 453 | module.exports = { 454 | isArray: isArray, 455 | isArrayBuffer: isArrayBuffer, 456 | isBuffer: isBuffer, 457 | isFormData: isFormData, 458 | isArrayBufferView: isArrayBufferView, 459 | isString: isString, 460 | isNumber: isNumber, 461 | isObject: isObject, 462 | isPlainObject: isPlainObject, 463 | isUndefined: isUndefined, 464 | isDate: isDate, 465 | isFile: isFile, 466 | isBlob: isBlob, 467 | isFunction: isFunction, 468 | isStream: isStream, 469 | isURLSearchParams: isURLSearchParams, 470 | isStandardBrowserEnv: isStandardBrowserEnv, 471 | forEach: forEach, 472 | merge: merge, 473 | extend: extend, 474 | trim: trim, 475 | stripBOM: stripBOM 476 | }; 477 | 478 | 479 | /***/ }), 480 | /* 3 */ 481 | /***/ (function(module, exports) { 482 | 483 | 'use strict'; 484 | 485 | module.exports = function bind(fn, thisArg) { 486 | return function wrap() { 487 | var args = new Array(arguments.length); 488 | for (var i = 0; i < args.length; i++) { 489 | args[i] = arguments[i]; 490 | } 491 | return fn.apply(thisArg, args); 492 | }; 493 | }; 494 | 495 | 496 | /***/ }), 497 | /* 4 */ 498 | /***/ (function(module, exports, __webpack_require__) { 499 | 500 | 'use strict'; 501 | 502 | var utils = __webpack_require__(2); 503 | var buildURL = __webpack_require__(5); 504 | var InterceptorManager = __webpack_require__(6); 505 | var dispatchRequest = __webpack_require__(7); 506 | var mergeConfig = __webpack_require__(22); 507 | 508 | /** 509 | * Create a new instance of Axios 510 | * 511 | * @param {Object} instanceConfig The default config for the instance 512 | */ 513 | function Axios(instanceConfig) { 514 | this.defaults = instanceConfig; 515 | this.interceptors = { 516 | request: new InterceptorManager(), 517 | response: new InterceptorManager() 518 | }; 519 | } 520 | 521 | /** 522 | * Dispatch a request 523 | * 524 | * @param {Object} config The config specific for this request (merged with this.defaults) 525 | */ 526 | Axios.prototype.request = function request(config) { 527 | /*eslint no-param-reassign:0*/ 528 | // Allow for axios('example/url'[, config]) a la fetch API 529 | if (typeof config === 'string') { 530 | config = arguments[1] || {}; 531 | config.url = arguments[0]; 532 | } else { 533 | config = config || {}; 534 | } 535 | 536 | config = mergeConfig(this.defaults, config); 537 | 538 | // Set config.method 539 | if (config.method) { 540 | config.method = config.method.toLowerCase(); 541 | } else if (this.defaults.method) { 542 | config.method = this.defaults.method.toLowerCase(); 543 | } else { 544 | config.method = 'get'; 545 | } 546 | 547 | // Hook up interceptors middleware 548 | var chain = [dispatchRequest, undefined]; 549 | var promise = Promise.resolve(config); 550 | 551 | this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { 552 | chain.unshift(interceptor.fulfilled, interceptor.rejected); 553 | }); 554 | 555 | this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { 556 | chain.push(interceptor.fulfilled, interceptor.rejected); 557 | }); 558 | 559 | while (chain.length) { 560 | promise = promise.then(chain.shift(), chain.shift()); 561 | } 562 | 563 | return promise; 564 | }; 565 | 566 | Axios.prototype.getUri = function getUri(config) { 567 | config = mergeConfig(this.defaults, config); 568 | return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, ''); 569 | }; 570 | 571 | // Provide aliases for supported request methods 572 | utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { 573 | /*eslint func-names:0*/ 574 | Axios.prototype[method] = function(url, config) { 575 | return this.request(mergeConfig(config || {}, { 576 | method: method, 577 | url: url, 578 | data: (config || {}).data 579 | })); 580 | }; 581 | }); 582 | 583 | utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { 584 | /*eslint func-names:0*/ 585 | Axios.prototype[method] = function(url, data, config) { 586 | return this.request(mergeConfig(config || {}, { 587 | method: method, 588 | url: url, 589 | data: data 590 | })); 591 | }; 592 | }); 593 | 594 | module.exports = Axios; 595 | 596 | 597 | /***/ }), 598 | /* 5 */ 599 | /***/ (function(module, exports, __webpack_require__) { 600 | 601 | 'use strict'; 602 | 603 | var utils = __webpack_require__(2); 604 | 605 | function encode(val) { 606 | return encodeURIComponent(val). 607 | replace(/%3A/gi, ':'). 608 | replace(/%24/g, '$'). 609 | replace(/%2C/gi, ','). 610 | replace(/%20/g, '+'). 611 | replace(/%5B/gi, '['). 612 | replace(/%5D/gi, ']'); 613 | } 614 | 615 | /** 616 | * Build a URL by appending params to the end 617 | * 618 | * @param {string} url The base of the url (e.g., http://www.google.com) 619 | * @param {object} [params] The params to be appended 620 | * @returns {string} The formatted url 621 | */ 622 | module.exports = function buildURL(url, params, paramsSerializer) { 623 | /*eslint no-param-reassign:0*/ 624 | if (!params) { 625 | return url; 626 | } 627 | 628 | var serializedParams; 629 | if (paramsSerializer) { 630 | serializedParams = paramsSerializer(params); 631 | } else if (utils.isURLSearchParams(params)) { 632 | serializedParams = params.toString(); 633 | } else { 634 | var parts = []; 635 | 636 | utils.forEach(params, function serialize(val, key) { 637 | if (val === null || typeof val === 'undefined') { 638 | return; 639 | } 640 | 641 | if (utils.isArray(val)) { 642 | key = key + '[]'; 643 | } else { 644 | val = [val]; 645 | } 646 | 647 | utils.forEach(val, function parseValue(v) { 648 | if (utils.isDate(v)) { 649 | v = v.toISOString(); 650 | } else if (utils.isObject(v)) { 651 | v = JSON.stringify(v); 652 | } 653 | parts.push(encode(key) + '=' + encode(v)); 654 | }); 655 | }); 656 | 657 | serializedParams = parts.join('&'); 658 | } 659 | 660 | if (serializedParams) { 661 | var hashmarkIndex = url.indexOf('#'); 662 | if (hashmarkIndex !== -1) { 663 | url = url.slice(0, hashmarkIndex); 664 | } 665 | 666 | url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; 667 | } 668 | 669 | return url; 670 | }; 671 | 672 | 673 | /***/ }), 674 | /* 6 */ 675 | /***/ (function(module, exports, __webpack_require__) { 676 | 677 | 'use strict'; 678 | 679 | var utils = __webpack_require__(2); 680 | 681 | function InterceptorManager() { 682 | this.handlers = []; 683 | } 684 | 685 | /** 686 | * Add a new interceptor to the stack 687 | * 688 | * @param {Function} fulfilled The function to handle `then` for a `Promise` 689 | * @param {Function} rejected The function to handle `reject` for a `Promise` 690 | * 691 | * @return {Number} An ID used to remove interceptor later 692 | */ 693 | InterceptorManager.prototype.use = function use(fulfilled, rejected) { 694 | this.handlers.push({ 695 | fulfilled: fulfilled, 696 | rejected: rejected 697 | }); 698 | return this.handlers.length - 1; 699 | }; 700 | 701 | /** 702 | * Remove an interceptor from the stack 703 | * 704 | * @param {Number} id The ID that was returned by `use` 705 | */ 706 | InterceptorManager.prototype.eject = function eject(id) { 707 | if (this.handlers[id]) { 708 | this.handlers[id] = null; 709 | } 710 | }; 711 | 712 | /** 713 | * Iterate over all the registered interceptors 714 | * 715 | * This method is particularly useful for skipping over any 716 | * interceptors that may have become `null` calling `eject`. 717 | * 718 | * @param {Function} fn The function to call for each interceptor 719 | */ 720 | InterceptorManager.prototype.forEach = function forEach(fn) { 721 | utils.forEach(this.handlers, function forEachHandler(h) { 722 | if (h !== null) { 723 | fn(h); 724 | } 725 | }); 726 | }; 727 | 728 | module.exports = InterceptorManager; 729 | 730 | 731 | /***/ }), 732 | /* 7 */ 733 | /***/ (function(module, exports, __webpack_require__) { 734 | 735 | 'use strict'; 736 | 737 | var utils = __webpack_require__(2); 738 | var transformData = __webpack_require__(8); 739 | var isCancel = __webpack_require__(9); 740 | var defaults = __webpack_require__(10); 741 | 742 | /** 743 | * Throws a `Cancel` if cancellation has been requested. 744 | */ 745 | function throwIfCancellationRequested(config) { 746 | if (config.cancelToken) { 747 | config.cancelToken.throwIfRequested(); 748 | } 749 | } 750 | 751 | /** 752 | * Dispatch a request to the server using the configured adapter. 753 | * 754 | * @param {object} config The config that is to be used for the request 755 | * @returns {Promise} The Promise to be fulfilled 756 | */ 757 | module.exports = function dispatchRequest(config) { 758 | throwIfCancellationRequested(config); 759 | 760 | // Ensure headers exist 761 | config.headers = config.headers || {}; 762 | 763 | // Transform request data 764 | config.data = transformData( 765 | config.data, 766 | config.headers, 767 | config.transformRequest 768 | ); 769 | 770 | // Flatten headers 771 | config.headers = utils.merge( 772 | config.headers.common || {}, 773 | config.headers[config.method] || {}, 774 | config.headers 775 | ); 776 | 777 | utils.forEach( 778 | ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], 779 | function cleanHeaderConfig(method) { 780 | delete config.headers[method]; 781 | } 782 | ); 783 | 784 | var adapter = config.adapter || defaults.adapter; 785 | 786 | return adapter(config).then(function onAdapterResolution(response) { 787 | throwIfCancellationRequested(config); 788 | 789 | // Transform response data 790 | response.data = transformData( 791 | response.data, 792 | response.headers, 793 | config.transformResponse 794 | ); 795 | 796 | return response; 797 | }, function onAdapterRejection(reason) { 798 | if (!isCancel(reason)) { 799 | throwIfCancellationRequested(config); 800 | 801 | // Transform response data 802 | if (reason && reason.response) { 803 | reason.response.data = transformData( 804 | reason.response.data, 805 | reason.response.headers, 806 | config.transformResponse 807 | ); 808 | } 809 | } 810 | 811 | return Promise.reject(reason); 812 | }); 813 | }; 814 | 815 | 816 | /***/ }), 817 | /* 8 */ 818 | /***/ (function(module, exports, __webpack_require__) { 819 | 820 | 'use strict'; 821 | 822 | var utils = __webpack_require__(2); 823 | 824 | /** 825 | * Transform the data for a request or a response 826 | * 827 | * @param {Object|String} data The data to be transformed 828 | * @param {Array} headers The headers for the request or response 829 | * @param {Array|Function} fns A single function or Array of functions 830 | * @returns {*} The resulting transformed data 831 | */ 832 | module.exports = function transformData(data, headers, fns) { 833 | /*eslint no-param-reassign:0*/ 834 | utils.forEach(fns, function transform(fn) { 835 | data = fn(data, headers); 836 | }); 837 | 838 | return data; 839 | }; 840 | 841 | 842 | /***/ }), 843 | /* 9 */ 844 | /***/ (function(module, exports) { 845 | 846 | 'use strict'; 847 | 848 | module.exports = function isCancel(value) { 849 | return !!(value && value.__CANCEL__); 850 | }; 851 | 852 | 853 | /***/ }), 854 | /* 10 */ 855 | /***/ (function(module, exports, __webpack_require__) { 856 | 857 | 'use strict'; 858 | 859 | var utils = __webpack_require__(2); 860 | var normalizeHeaderName = __webpack_require__(11); 861 | 862 | var DEFAULT_CONTENT_TYPE = { 863 | 'Content-Type': 'application/x-www-form-urlencoded' 864 | }; 865 | 866 | function setContentTypeIfUnset(headers, value) { 867 | if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { 868 | headers['Content-Type'] = value; 869 | } 870 | } 871 | 872 | function getDefaultAdapter() { 873 | var adapter; 874 | if (typeof XMLHttpRequest !== 'undefined') { 875 | // For browsers use XHR adapter 876 | adapter = __webpack_require__(12); 877 | } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { 878 | // For node use HTTP adapter 879 | adapter = __webpack_require__(12); 880 | } 881 | return adapter; 882 | } 883 | 884 | var defaults = { 885 | adapter: getDefaultAdapter(), 886 | 887 | transformRequest: [function transformRequest(data, headers) { 888 | normalizeHeaderName(headers, 'Accept'); 889 | normalizeHeaderName(headers, 'Content-Type'); 890 | if (utils.isFormData(data) || 891 | utils.isArrayBuffer(data) || 892 | utils.isBuffer(data) || 893 | utils.isStream(data) || 894 | utils.isFile(data) || 895 | utils.isBlob(data) 896 | ) { 897 | return data; 898 | } 899 | if (utils.isArrayBufferView(data)) { 900 | return data.buffer; 901 | } 902 | if (utils.isURLSearchParams(data)) { 903 | setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); 904 | return data.toString(); 905 | } 906 | if (utils.isObject(data)) { 907 | setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); 908 | return JSON.stringify(data); 909 | } 910 | return data; 911 | }], 912 | 913 | transformResponse: [function transformResponse(data) { 914 | /*eslint no-param-reassign:0*/ 915 | if (typeof data === 'string') { 916 | try { 917 | data = JSON.parse(data); 918 | } catch (e) { /* Ignore */ } 919 | } 920 | return data; 921 | }], 922 | 923 | /** 924 | * A timeout in milliseconds to abort a request. If set to 0 (default) a 925 | * timeout is not created. 926 | */ 927 | timeout: 0, 928 | 929 | xsrfCookieName: 'XSRF-TOKEN', 930 | xsrfHeaderName: 'X-XSRF-TOKEN', 931 | 932 | maxContentLength: -1, 933 | maxBodyLength: -1, 934 | 935 | validateStatus: function validateStatus(status) { 936 | return status >= 200 && status < 300; 937 | } 938 | }; 939 | 940 | defaults.headers = { 941 | common: { 942 | 'Accept': 'application/json, text/plain, */*' 943 | } 944 | }; 945 | 946 | utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { 947 | defaults.headers[method] = {}; 948 | }); 949 | 950 | utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { 951 | defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); 952 | }); 953 | 954 | module.exports = defaults; 955 | 956 | 957 | /***/ }), 958 | /* 11 */ 959 | /***/ (function(module, exports, __webpack_require__) { 960 | 961 | 'use strict'; 962 | 963 | var utils = __webpack_require__(2); 964 | 965 | module.exports = function normalizeHeaderName(headers, normalizedName) { 966 | utils.forEach(headers, function processHeader(value, name) { 967 | if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { 968 | headers[normalizedName] = value; 969 | delete headers[name]; 970 | } 971 | }); 972 | }; 973 | 974 | 975 | /***/ }), 976 | /* 12 */ 977 | /***/ (function(module, exports, __webpack_require__) { 978 | 979 | 'use strict'; 980 | 981 | var utils = __webpack_require__(2); 982 | var settle = __webpack_require__(13); 983 | var cookies = __webpack_require__(16); 984 | var buildURL = __webpack_require__(5); 985 | var buildFullPath = __webpack_require__(17); 986 | var parseHeaders = __webpack_require__(20); 987 | var isURLSameOrigin = __webpack_require__(21); 988 | var createError = __webpack_require__(14); 989 | 990 | module.exports = function xhrAdapter(config) { 991 | return new Promise(function dispatchXhrRequest(resolve, reject) { 992 | var requestData = config.data; 993 | var requestHeaders = config.headers; 994 | 995 | if (utils.isFormData(requestData)) { 996 | delete requestHeaders['Content-Type']; // Let the browser set it 997 | } 998 | 999 | var request = new XMLHttpRequest(); 1000 | 1001 | // HTTP basic authentication 1002 | if (config.auth) { 1003 | var username = config.auth.username || ''; 1004 | var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : ''; 1005 | requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); 1006 | } 1007 | 1008 | var fullPath = buildFullPath(config.baseURL, config.url); 1009 | request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true); 1010 | 1011 | // Set the request timeout in MS 1012 | request.timeout = config.timeout; 1013 | 1014 | // Listen for ready state 1015 | request.onreadystatechange = function handleLoad() { 1016 | if (!request || request.readyState !== 4) { 1017 | return; 1018 | } 1019 | 1020 | // The request errored out and we didn't get a response, this will be 1021 | // handled by onerror instead 1022 | // With one exception: request that using file: protocol, most browsers 1023 | // will return status as 0 even though it's a successful request 1024 | if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { 1025 | return; 1026 | } 1027 | 1028 | // Prepare the response 1029 | var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; 1030 | var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; 1031 | var response = { 1032 | data: responseData, 1033 | status: request.status, 1034 | statusText: request.statusText, 1035 | headers: responseHeaders, 1036 | config: config, 1037 | request: request 1038 | }; 1039 | 1040 | settle(resolve, reject, response); 1041 | 1042 | // Clean up request 1043 | request = null; 1044 | }; 1045 | 1046 | // Handle browser request cancellation (as opposed to a manual cancellation) 1047 | request.onabort = function handleAbort() { 1048 | if (!request) { 1049 | return; 1050 | } 1051 | 1052 | reject(createError('Request aborted', config, 'ECONNABORTED', request)); 1053 | 1054 | // Clean up request 1055 | request = null; 1056 | }; 1057 | 1058 | // Handle low level network errors 1059 | request.onerror = function handleError() { 1060 | // Real errors are hidden from us by the browser 1061 | // onerror should only fire if it's a network error 1062 | reject(createError('Network Error', config, null, request)); 1063 | 1064 | // Clean up request 1065 | request = null; 1066 | }; 1067 | 1068 | // Handle timeout 1069 | request.ontimeout = function handleTimeout() { 1070 | var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded'; 1071 | if (config.timeoutErrorMessage) { 1072 | timeoutErrorMessage = config.timeoutErrorMessage; 1073 | } 1074 | reject(createError(timeoutErrorMessage, config, 'ECONNABORTED', 1075 | request)); 1076 | 1077 | // Clean up request 1078 | request = null; 1079 | }; 1080 | 1081 | // Add xsrf header 1082 | // This is only done if running in a standard browser environment. 1083 | // Specifically not if we're in a web worker, or react-native. 1084 | if (utils.isStandardBrowserEnv()) { 1085 | // Add xsrf header 1086 | var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ? 1087 | cookies.read(config.xsrfCookieName) : 1088 | undefined; 1089 | 1090 | if (xsrfValue) { 1091 | requestHeaders[config.xsrfHeaderName] = xsrfValue; 1092 | } 1093 | } 1094 | 1095 | // Add headers to the request 1096 | if ('setRequestHeader' in request) { 1097 | utils.forEach(requestHeaders, function setRequestHeader(val, key) { 1098 | if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { 1099 | // Remove Content-Type if data is undefined 1100 | delete requestHeaders[key]; 1101 | } else { 1102 | // Otherwise add header to the request 1103 | request.setRequestHeader(key, val); 1104 | } 1105 | }); 1106 | } 1107 | 1108 | // Add withCredentials to request if needed 1109 | if (!utils.isUndefined(config.withCredentials)) { 1110 | request.withCredentials = !!config.withCredentials; 1111 | } 1112 | 1113 | // Add responseType to request if needed 1114 | if (config.responseType) { 1115 | try { 1116 | request.responseType = config.responseType; 1117 | } catch (e) { 1118 | // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2. 1119 | // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function. 1120 | if (config.responseType !== 'json') { 1121 | throw e; 1122 | } 1123 | } 1124 | } 1125 | 1126 | // Handle progress if needed 1127 | if (typeof config.onDownloadProgress === 'function') { 1128 | request.addEventListener('progress', config.onDownloadProgress); 1129 | } 1130 | 1131 | // Not all browsers support upload events 1132 | if (typeof config.onUploadProgress === 'function' && request.upload) { 1133 | request.upload.addEventListener('progress', config.onUploadProgress); 1134 | } 1135 | 1136 | if (config.cancelToken) { 1137 | // Handle cancellation 1138 | config.cancelToken.promise.then(function onCanceled(cancel) { 1139 | if (!request) { 1140 | return; 1141 | } 1142 | 1143 | request.abort(); 1144 | reject(cancel); 1145 | // Clean up request 1146 | request = null; 1147 | }); 1148 | } 1149 | 1150 | if (!requestData) { 1151 | requestData = null; 1152 | } 1153 | 1154 | // Send the request 1155 | request.send(requestData); 1156 | }); 1157 | }; 1158 | 1159 | 1160 | /***/ }), 1161 | /* 13 */ 1162 | /***/ (function(module, exports, __webpack_require__) { 1163 | 1164 | 'use strict'; 1165 | 1166 | var createError = __webpack_require__(14); 1167 | 1168 | /** 1169 | * Resolve or reject a Promise based on response status. 1170 | * 1171 | * @param {Function} resolve A function that resolves the promise. 1172 | * @param {Function} reject A function that rejects the promise. 1173 | * @param {object} response The response. 1174 | */ 1175 | module.exports = function settle(resolve, reject, response) { 1176 | var validateStatus = response.config.validateStatus; 1177 | if (!response.status || !validateStatus || validateStatus(response.status)) { 1178 | resolve(response); 1179 | } else { 1180 | reject(createError( 1181 | 'Request failed with status code ' + response.status, 1182 | response.config, 1183 | null, 1184 | response.request, 1185 | response 1186 | )); 1187 | } 1188 | }; 1189 | 1190 | 1191 | /***/ }), 1192 | /* 14 */ 1193 | /***/ (function(module, exports, __webpack_require__) { 1194 | 1195 | 'use strict'; 1196 | 1197 | var enhanceError = __webpack_require__(15); 1198 | 1199 | /** 1200 | * Create an Error with the specified message, config, error code, request and response. 1201 | * 1202 | * @param {string} message The error message. 1203 | * @param {Object} config The config. 1204 | * @param {string} [code] The error code (for example, 'ECONNABORTED'). 1205 | * @param {Object} [request] The request. 1206 | * @param {Object} [response] The response. 1207 | * @returns {Error} The created error. 1208 | */ 1209 | module.exports = function createError(message, config, code, request, response) { 1210 | var error = new Error(message); 1211 | return enhanceError(error, config, code, request, response); 1212 | }; 1213 | 1214 | 1215 | /***/ }), 1216 | /* 15 */ 1217 | /***/ (function(module, exports) { 1218 | 1219 | 'use strict'; 1220 | 1221 | /** 1222 | * Update an Error with the specified config, error code, and response. 1223 | * 1224 | * @param {Error} error The error to update. 1225 | * @param {Object} config The config. 1226 | * @param {string} [code] The error code (for example, 'ECONNABORTED'). 1227 | * @param {Object} [request] The request. 1228 | * @param {Object} [response] The response. 1229 | * @returns {Error} The error. 1230 | */ 1231 | module.exports = function enhanceError(error, config, code, request, response) { 1232 | error.config = config; 1233 | if (code) { 1234 | error.code = code; 1235 | } 1236 | 1237 | error.request = request; 1238 | error.response = response; 1239 | error.isAxiosError = true; 1240 | 1241 | error.toJSON = function toJSON() { 1242 | return { 1243 | // Standard 1244 | message: this.message, 1245 | name: this.name, 1246 | // Microsoft 1247 | description: this.description, 1248 | number: this.number, 1249 | // Mozilla 1250 | fileName: this.fileName, 1251 | lineNumber: this.lineNumber, 1252 | columnNumber: this.columnNumber, 1253 | stack: this.stack, 1254 | // Axios 1255 | config: this.config, 1256 | code: this.code 1257 | }; 1258 | }; 1259 | return error; 1260 | }; 1261 | 1262 | 1263 | /***/ }), 1264 | /* 16 */ 1265 | /***/ (function(module, exports, __webpack_require__) { 1266 | 1267 | 'use strict'; 1268 | 1269 | var utils = __webpack_require__(2); 1270 | 1271 | module.exports = ( 1272 | utils.isStandardBrowserEnv() ? 1273 | 1274 | // Standard browser envs support document.cookie 1275 | (function standardBrowserEnv() { 1276 | return { 1277 | write: function write(name, value, expires, path, domain, secure) { 1278 | var cookie = []; 1279 | cookie.push(name + '=' + encodeURIComponent(value)); 1280 | 1281 | if (utils.isNumber(expires)) { 1282 | cookie.push('expires=' + new Date(expires).toGMTString()); 1283 | } 1284 | 1285 | if (utils.isString(path)) { 1286 | cookie.push('path=' + path); 1287 | } 1288 | 1289 | if (utils.isString(domain)) { 1290 | cookie.push('domain=' + domain); 1291 | } 1292 | 1293 | if (secure === true) { 1294 | cookie.push('secure'); 1295 | } 1296 | 1297 | document.cookie = cookie.join('; '); 1298 | }, 1299 | 1300 | read: function read(name) { 1301 | var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); 1302 | return (match ? decodeURIComponent(match[3]) : null); 1303 | }, 1304 | 1305 | remove: function remove(name) { 1306 | this.write(name, '', Date.now() - 86400000); 1307 | } 1308 | }; 1309 | })() : 1310 | 1311 | // Non standard browser env (web workers, react-native) lack needed support. 1312 | (function nonStandardBrowserEnv() { 1313 | return { 1314 | write: function write() {}, 1315 | read: function read() { return null; }, 1316 | remove: function remove() {} 1317 | }; 1318 | })() 1319 | ); 1320 | 1321 | 1322 | /***/ }), 1323 | /* 17 */ 1324 | /***/ (function(module, exports, __webpack_require__) { 1325 | 1326 | 'use strict'; 1327 | 1328 | var isAbsoluteURL = __webpack_require__(18); 1329 | var combineURLs = __webpack_require__(19); 1330 | 1331 | /** 1332 | * Creates a new URL by combining the baseURL with the requestedURL, 1333 | * only when the requestedURL is not already an absolute URL. 1334 | * If the requestURL is absolute, this function returns the requestedURL untouched. 1335 | * 1336 | * @param {string} baseURL The base URL 1337 | * @param {string} requestedURL Absolute or relative URL to combine 1338 | * @returns {string} The combined full path 1339 | */ 1340 | module.exports = function buildFullPath(baseURL, requestedURL) { 1341 | if (baseURL && !isAbsoluteURL(requestedURL)) { 1342 | return combineURLs(baseURL, requestedURL); 1343 | } 1344 | return requestedURL; 1345 | }; 1346 | 1347 | 1348 | /***/ }), 1349 | /* 18 */ 1350 | /***/ (function(module, exports) { 1351 | 1352 | 'use strict'; 1353 | 1354 | /** 1355 | * Determines whether the specified URL is absolute 1356 | * 1357 | * @param {string} url The URL to test 1358 | * @returns {boolean} True if the specified URL is absolute, otherwise false 1359 | */ 1360 | module.exports = function isAbsoluteURL(url) { 1361 | // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). 1362 | // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed 1363 | // by any combination of letters, digits, plus, period, or hyphen. 1364 | return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); 1365 | }; 1366 | 1367 | 1368 | /***/ }), 1369 | /* 19 */ 1370 | /***/ (function(module, exports) { 1371 | 1372 | 'use strict'; 1373 | 1374 | /** 1375 | * Creates a new URL by combining the specified URLs 1376 | * 1377 | * @param {string} baseURL The base URL 1378 | * @param {string} relativeURL The relative URL 1379 | * @returns {string} The combined URL 1380 | */ 1381 | module.exports = function combineURLs(baseURL, relativeURL) { 1382 | return relativeURL 1383 | ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') 1384 | : baseURL; 1385 | }; 1386 | 1387 | 1388 | /***/ }), 1389 | /* 20 */ 1390 | /***/ (function(module, exports, __webpack_require__) { 1391 | 1392 | 'use strict'; 1393 | 1394 | var utils = __webpack_require__(2); 1395 | 1396 | // Headers whose duplicates are ignored by node 1397 | // c.f. https://nodejs.org/api/http.html#http_message_headers 1398 | var ignoreDuplicateOf = [ 1399 | 'age', 'authorization', 'content-length', 'content-type', 'etag', 1400 | 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', 1401 | 'last-modified', 'location', 'max-forwards', 'proxy-authorization', 1402 | 'referer', 'retry-after', 'user-agent' 1403 | ]; 1404 | 1405 | /** 1406 | * Parse headers into an object 1407 | * 1408 | * ``` 1409 | * Date: Wed, 27 Aug 2014 08:58:49 GMT 1410 | * Content-Type: application/json 1411 | * Connection: keep-alive 1412 | * Transfer-Encoding: chunked 1413 | * ``` 1414 | * 1415 | * @param {String} headers Headers needing to be parsed 1416 | * @returns {Object} Headers parsed into an object 1417 | */ 1418 | module.exports = function parseHeaders(headers) { 1419 | var parsed = {}; 1420 | var key; 1421 | var val; 1422 | var i; 1423 | 1424 | if (!headers) { return parsed; } 1425 | 1426 | utils.forEach(headers.split('\n'), function parser(line) { 1427 | i = line.indexOf(':'); 1428 | key = utils.trim(line.substr(0, i)).toLowerCase(); 1429 | val = utils.trim(line.substr(i + 1)); 1430 | 1431 | if (key) { 1432 | if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) { 1433 | return; 1434 | } 1435 | if (key === 'set-cookie') { 1436 | parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]); 1437 | } else { 1438 | parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; 1439 | } 1440 | } 1441 | }); 1442 | 1443 | return parsed; 1444 | }; 1445 | 1446 | 1447 | /***/ }), 1448 | /* 21 */ 1449 | /***/ (function(module, exports, __webpack_require__) { 1450 | 1451 | 'use strict'; 1452 | 1453 | var utils = __webpack_require__(2); 1454 | 1455 | module.exports = ( 1456 | utils.isStandardBrowserEnv() ? 1457 | 1458 | // Standard browser envs have full support of the APIs needed to test 1459 | // whether the request URL is of the same origin as current location. 1460 | (function standardBrowserEnv() { 1461 | var msie = /(msie|trident)/i.test(navigator.userAgent); 1462 | var urlParsingNode = document.createElement('a'); 1463 | var originURL; 1464 | 1465 | /** 1466 | * Parse a URL to discover it's components 1467 | * 1468 | * @param {String} url The URL to be parsed 1469 | * @returns {Object} 1470 | */ 1471 | function resolveURL(url) { 1472 | var href = url; 1473 | 1474 | if (msie) { 1475 | // IE needs attribute set twice to normalize properties 1476 | urlParsingNode.setAttribute('href', href); 1477 | href = urlParsingNode.href; 1478 | } 1479 | 1480 | urlParsingNode.setAttribute('href', href); 1481 | 1482 | // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils 1483 | return { 1484 | href: urlParsingNode.href, 1485 | protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', 1486 | host: urlParsingNode.host, 1487 | search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', 1488 | hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', 1489 | hostname: urlParsingNode.hostname, 1490 | port: urlParsingNode.port, 1491 | pathname: (urlParsingNode.pathname.charAt(0) === '/') ? 1492 | urlParsingNode.pathname : 1493 | '/' + urlParsingNode.pathname 1494 | }; 1495 | } 1496 | 1497 | originURL = resolveURL(window.location.href); 1498 | 1499 | /** 1500 | * Determine if a URL shares the same origin as the current location 1501 | * 1502 | * @param {String} requestURL The URL to test 1503 | * @returns {boolean} True if URL shares the same origin, otherwise false 1504 | */ 1505 | return function isURLSameOrigin(requestURL) { 1506 | var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL; 1507 | return (parsed.protocol === originURL.protocol && 1508 | parsed.host === originURL.host); 1509 | }; 1510 | })() : 1511 | 1512 | // Non standard browser envs (web workers, react-native) lack needed support. 1513 | (function nonStandardBrowserEnv() { 1514 | return function isURLSameOrigin() { 1515 | return true; 1516 | }; 1517 | })() 1518 | ); 1519 | 1520 | 1521 | /***/ }), 1522 | /* 22 */ 1523 | /***/ (function(module, exports, __webpack_require__) { 1524 | 1525 | 'use strict'; 1526 | 1527 | var utils = __webpack_require__(2); 1528 | 1529 | /** 1530 | * Config-specific merge-function which creates a new config-object 1531 | * by merging two configuration objects together. 1532 | * 1533 | * @param {Object} config1 1534 | * @param {Object} config2 1535 | * @returns {Object} New object resulting from merging config2 to config1 1536 | */ 1537 | module.exports = function mergeConfig(config1, config2) { 1538 | // eslint-disable-next-line no-param-reassign 1539 | config2 = config2 || {}; 1540 | var config = {}; 1541 | 1542 | var valueFromConfig2Keys = ['url', 'method', 'data']; 1543 | var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy', 'params']; 1544 | var defaultToConfig2Keys = [ 1545 | 'baseURL', 'transformRequest', 'transformResponse', 'paramsSerializer', 1546 | 'timeout', 'timeoutMessage', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName', 1547 | 'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'decompress', 1548 | 'maxContentLength', 'maxBodyLength', 'maxRedirects', 'transport', 'httpAgent', 1549 | 'httpsAgent', 'cancelToken', 'socketPath', 'responseEncoding' 1550 | ]; 1551 | var directMergeKeys = ['validateStatus']; 1552 | 1553 | function getMergedValue(target, source) { 1554 | if (utils.isPlainObject(target) && utils.isPlainObject(source)) { 1555 | return utils.merge(target, source); 1556 | } else if (utils.isPlainObject(source)) { 1557 | return utils.merge({}, source); 1558 | } else if (utils.isArray(source)) { 1559 | return source.slice(); 1560 | } 1561 | return source; 1562 | } 1563 | 1564 | function mergeDeepProperties(prop) { 1565 | if (!utils.isUndefined(config2[prop])) { 1566 | config[prop] = getMergedValue(config1[prop], config2[prop]); 1567 | } else if (!utils.isUndefined(config1[prop])) { 1568 | config[prop] = getMergedValue(undefined, config1[prop]); 1569 | } 1570 | } 1571 | 1572 | utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) { 1573 | if (!utils.isUndefined(config2[prop])) { 1574 | config[prop] = getMergedValue(undefined, config2[prop]); 1575 | } 1576 | }); 1577 | 1578 | utils.forEach(mergeDeepPropertiesKeys, mergeDeepProperties); 1579 | 1580 | utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) { 1581 | if (!utils.isUndefined(config2[prop])) { 1582 | config[prop] = getMergedValue(undefined, config2[prop]); 1583 | } else if (!utils.isUndefined(config1[prop])) { 1584 | config[prop] = getMergedValue(undefined, config1[prop]); 1585 | } 1586 | }); 1587 | 1588 | utils.forEach(directMergeKeys, function merge(prop) { 1589 | if (prop in config2) { 1590 | config[prop] = getMergedValue(config1[prop], config2[prop]); 1591 | } else if (prop in config1) { 1592 | config[prop] = getMergedValue(undefined, config1[prop]); 1593 | } 1594 | }); 1595 | 1596 | var axiosKeys = valueFromConfig2Keys 1597 | .concat(mergeDeepPropertiesKeys) 1598 | .concat(defaultToConfig2Keys) 1599 | .concat(directMergeKeys); 1600 | 1601 | var otherKeys = Object 1602 | .keys(config1) 1603 | .concat(Object.keys(config2)) 1604 | .filter(function filterAxiosKeys(key) { 1605 | return axiosKeys.indexOf(key) === -1; 1606 | }); 1607 | 1608 | utils.forEach(otherKeys, mergeDeepProperties); 1609 | 1610 | return config; 1611 | }; 1612 | 1613 | 1614 | /***/ }), 1615 | /* 23 */ 1616 | /***/ (function(module, exports) { 1617 | 1618 | 'use strict'; 1619 | 1620 | /** 1621 | * A `Cancel` is an object that is thrown when an operation is canceled. 1622 | * 1623 | * @class 1624 | * @param {string=} message The message. 1625 | */ 1626 | function Cancel(message) { 1627 | this.message = message; 1628 | } 1629 | 1630 | Cancel.prototype.toString = function toString() { 1631 | return 'Cancel' + (this.message ? ': ' + this.message : ''); 1632 | }; 1633 | 1634 | Cancel.prototype.__CANCEL__ = true; 1635 | 1636 | module.exports = Cancel; 1637 | 1638 | 1639 | /***/ }), 1640 | /* 24 */ 1641 | /***/ (function(module, exports, __webpack_require__) { 1642 | 1643 | 'use strict'; 1644 | 1645 | var Cancel = __webpack_require__(23); 1646 | 1647 | /** 1648 | * A `CancelToken` is an object that can be used to request cancellation of an operation. 1649 | * 1650 | * @class 1651 | * @param {Function} executor The executor function. 1652 | */ 1653 | function CancelToken(executor) { 1654 | if (typeof executor !== 'function') { 1655 | throw new TypeError('executor must be a function.'); 1656 | } 1657 | 1658 | var resolvePromise; 1659 | this.promise = new Promise(function promiseExecutor(resolve) { 1660 | resolvePromise = resolve; 1661 | }); 1662 | 1663 | var token = this; 1664 | executor(function cancel(message) { 1665 | if (token.reason) { 1666 | // Cancellation has already been requested 1667 | return; 1668 | } 1669 | 1670 | token.reason = new Cancel(message); 1671 | resolvePromise(token.reason); 1672 | }); 1673 | } 1674 | 1675 | /** 1676 | * Throws a `Cancel` if cancellation has been requested. 1677 | */ 1678 | CancelToken.prototype.throwIfRequested = function throwIfRequested() { 1679 | if (this.reason) { 1680 | throw this.reason; 1681 | } 1682 | }; 1683 | 1684 | /** 1685 | * Returns an object that contains a new `CancelToken` and a function that, when called, 1686 | * cancels the `CancelToken`. 1687 | */ 1688 | CancelToken.source = function source() { 1689 | var cancel; 1690 | var token = new CancelToken(function executor(c) { 1691 | cancel = c; 1692 | }); 1693 | return { 1694 | token: token, 1695 | cancel: cancel 1696 | }; 1697 | }; 1698 | 1699 | module.exports = CancelToken; 1700 | 1701 | 1702 | /***/ }), 1703 | /* 25 */ 1704 | /***/ (function(module, exports) { 1705 | 1706 | 'use strict'; 1707 | 1708 | /** 1709 | * Syntactic sugar for invoking a function and expanding an array for arguments. 1710 | * 1711 | * Common use case would be to use `Function.prototype.apply`. 1712 | * 1713 | * ```js 1714 | * function f(x, y, z) {} 1715 | * var args = [1, 2, 3]; 1716 | * f.apply(null, args); 1717 | * ``` 1718 | * 1719 | * With `spread` this example can be re-written. 1720 | * 1721 | * ```js 1722 | * spread(function(x, y, z) {})([1, 2, 3]); 1723 | * ``` 1724 | * 1725 | * @param {Function} callback 1726 | * @returns {Function} 1727 | */ 1728 | module.exports = function spread(callback) { 1729 | return function wrap(arr) { 1730 | return callback.apply(null, arr); 1731 | }; 1732 | }; 1733 | 1734 | 1735 | /***/ }), 1736 | /* 26 */ 1737 | /***/ (function(module, exports) { 1738 | 1739 | 'use strict'; 1740 | 1741 | /** 1742 | * Determines whether the payload is an error thrown by Axios 1743 | * 1744 | * @param {*} payload The value to test 1745 | * @returns {boolean} True if the payload is an error thrown by Axios, otherwise false 1746 | */ 1747 | module.exports = function isAxiosError(payload) { 1748 | return (typeof payload === 'object') && (payload.isAxiosError === true); 1749 | }; 1750 | 1751 | 1752 | /***/ }) 1753 | /******/ ]) 1754 | }); 1755 | ; 1756 | //# sourceMappingURL=axios.map -------------------------------------------------------------------------------- /public/app/webfonts/fa-brands-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/webfonts/fa-brands-400.eot -------------------------------------------------------------------------------- /public/app/webfonts/fa-brands-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/webfonts/fa-brands-400.ttf -------------------------------------------------------------------------------- /public/app/webfonts/fa-brands-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/webfonts/fa-brands-400.woff -------------------------------------------------------------------------------- /public/app/webfonts/fa-brands-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/webfonts/fa-brands-400.woff2 -------------------------------------------------------------------------------- /public/app/webfonts/fa-regular-400.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/webfonts/fa-regular-400.eot -------------------------------------------------------------------------------- /public/app/webfonts/fa-regular-400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/webfonts/fa-regular-400.ttf -------------------------------------------------------------------------------- /public/app/webfonts/fa-regular-400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/webfonts/fa-regular-400.woff -------------------------------------------------------------------------------- /public/app/webfonts/fa-regular-400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/webfonts/fa-regular-400.woff2 -------------------------------------------------------------------------------- /public/app/webfonts/fa-solid-900.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/webfonts/fa-solid-900.eot -------------------------------------------------------------------------------- /public/app/webfonts/fa-solid-900.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/webfonts/fa-solid-900.ttf -------------------------------------------------------------------------------- /public/app/webfonts/fa-solid-900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/webfonts/fa-solid-900.woff -------------------------------------------------------------------------------- /public/app/webfonts/fa-solid-900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iandk/servermanager/0a5c752baed0963a4c060c013a6a474534e8cbaa/public/app/webfonts/fa-solid-900.woff2 -------------------------------------------------------------------------------- /public/class/Server.php: -------------------------------------------------------------------------------- 1 | id = uniqid(); 15 | } 16 | // No ID was given, therefore update the existing host 17 | else { 18 | $this->id = $_POST['id']; 19 | } 20 | // Fetch ASN information if enabled 21 | if($_POST['disable_asn'] == "false") { 22 | $this->ips = $this->storeIps($_POST['ips']); 23 | } 24 | else { 25 | $this->ips = $_POST['ips']; 26 | } 27 | 28 | // Write data to json file 29 | $this->writeToFile(); 30 | return $this->id; 31 | } 32 | 33 | // Prepare IP/ASN objects 34 | function storeIps(string $ips): array { 35 | $ip_list = preg_split("/[\s,]+/", $ips); 36 | $result = []; 37 | foreach ($ip_list as &$ip) { 38 | array_push($result,array( 39 | "ip" => $ip, 40 | "asn" => $this->getAsnForIp($ip) 41 | )); 42 | } 43 | return $result; 44 | } 45 | 46 | // Write the host details to the file 47 | function writeToFile() { 48 | $jsonData = array( 49 | "id" => $this->id, 50 | "name" => $_POST['name'], 51 | "hostname" => $_POST['hostname'], 52 | "location" => $_POST['location'], 53 | "tags" => $_POST['tags'], 54 | "ressources" => $_POST['ressources'], 55 | "provider" => $_POST['provider'], 56 | "ips" => $this->ips, 57 | "price" => $_POST['price'], 58 | "type" => $_POST['type'], 59 | "os" => $_POST['os'], 60 | "notes" => $_POST['notes'] 61 | ); 62 | // Create new json file to store data 63 | $file = fopen("data/" . $this->id . ".json", "w"); 64 | fwrite($file, json_encode($jsonData)); 65 | } 66 | 67 | // Delete the server with the given name 68 | function deleteServer() { 69 | $filename = "data/" . $_POST['id'] . ".json"; 70 | if (file_exists($filename)) { 71 | unlink($filename); 72 | return true; 73 | } 74 | else { 75 | return false; 76 | } 77 | } 78 | 79 | // Count all available servers 80 | function countServer() { 81 | $files = scandir("data/"); 82 | // -2 since "." and ".." are also counted 83 | return json_encode(count($files)-2); 84 | } 85 | 86 | // List all server names 87 | function listServer() { 88 | $listServer = []; 89 | if ($handle = opendir('data/')) { 90 | while (false !== ($entry = readdir($handle))) { 91 | if ($entry != "." && $entry != ".." && $entry != ".gitkeep"&& $entry != ".firstsetup") { 92 | // Read file 93 | $file = json_decode(file_get_contents('data/' . $entry)); 94 | // Add to array 95 | array_push($listServer, $file); 96 | } 97 | } 98 | closedir($handle); 99 | } 100 | return json_encode($listServer); 101 | } 102 | 103 | function getValue($id, $value) { 104 | 105 | # Fallback to $_POST if no vars are passed 106 | if(!isset($id)) { 107 | $id = $_POST['id']; 108 | } 109 | if (!isset($value)) { 110 | $value = $_POST['value']; 111 | } 112 | 113 | $filename = "data/" . $id . ".json"; 114 | if(file_exists($filename)) { 115 | $file = fopen($filename, "r"); 116 | $filecontent = json_decode(fread($file, filesize($filename)), true); 117 | if($filecontent[$value]) { 118 | return $filecontent[$value]; 119 | } 120 | else { 121 | return false; 122 | } 123 | } 124 | else { 125 | return false; 126 | } 127 | } 128 | 129 | function getValues() { 130 | $filename = "data/" . $_POST['id'] . ".json"; 131 | if(file_exists($filename)) { 132 | $file = file_get_contents($filename); 133 | return $file; 134 | } 135 | else { 136 | return false; 137 | } 138 | } 139 | 140 | function getStatus() { 141 | $ip = $this->getValue($_POST['id'], "hostname"); 142 | exec("`which ping` -W 2 -c 1 $ip", $output, $status); 143 | if ($status == 0) { 144 | return true; 145 | } 146 | else { 147 | return false; 148 | } 149 | } 150 | 151 | function isFirstSetup() { 152 | $filename = "data/.firstsetup"; 153 | if (!file_exists($filename)) { 154 | return true; 155 | } else { 156 | return false; 157 | } 158 | } 159 | 160 | function completeSetup() { 161 | // Create .firstsetup file 162 | $filename = "data/.firstsetup"; 163 | if (!file_exists($filename)) { 164 | $contents = 'Just a dummy file '; 165 | file_put_contents($filename, $contents); 166 | } 167 | } 168 | 169 | // Fetch ASN for an IP address 170 | function getAsnForIp(string $ip): string { 171 | if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_NO_PRIV_RANGE)) { 172 | $parts = explode('.',$ip); 173 | $dnslookup = implode('.', array_reverse($parts)) . '.origin.asn.cymru.com.'; 174 | } 175 | elseif (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6 | FILTER_FLAG_NO_PRIV_RANGE)) { 176 | $addr = inet_pton($ip); 177 | $unpack = unpack('H*hex', $addr); 178 | $hex = $unpack['hex']; 179 | $dnslookup = implode('.', array_reverse(str_split($hex))) . '.origin6.asn.cymru.com.'; 180 | } 181 | 182 | if (isset($dnslookup)) { 183 | $record = dns_get_record($dnslookup, DNS_TXT); 184 | if (isset($record['0']['txt'])) { 185 | // example: 3356 | 4.0.0.0/9 | US | arin | 1992-12-01 186 | $result = explode(" | ", $record['0']['txt']); 187 | $asn_id = array_shift($result); 188 | $asn_name = $this->getAsnName(intval($asn_id)); 189 | return $asn_id . " - " . $asn_name; 190 | } 191 | } 192 | 193 | return "private IP"; 194 | } 195 | 196 | // Fetch ASN description for an ASN id 197 | public function getAsnName(int $asn): string { 198 | //AS3356.asn.cymru.com 199 | $dnslookup = 'AS' . $asn . '.asn.cymru.com.'; 200 | $record = dns_get_record($dnslookup, DNS_TXT); 201 | if (isset($record['0']['txt'])) { 202 | // example: 3356 | US | arin | 2000-03-10 | LEVEL3, US 203 | $result = explode(" | ", $record['0']['txt']); 204 | $name = end($result); 205 | return $name; 206 | } 207 | 208 | return "n/a"; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /public/controller/api/addserver.php: -------------------------------------------------------------------------------- 1 | addServer(); 7 | -------------------------------------------------------------------------------- /public/controller/api/completesetup.php: -------------------------------------------------------------------------------- 1 | completeSetup(); 6 | -------------------------------------------------------------------------------- /public/controller/api/countserver.php: -------------------------------------------------------------------------------- 1 | countServer(); -------------------------------------------------------------------------------- /public/controller/api/deleteserver.php: -------------------------------------------------------------------------------- 1 | deleteServer(); 7 | 8 | -------------------------------------------------------------------------------- /public/controller/api/getstatus.php: -------------------------------------------------------------------------------- 1 | getStatus(); 8 | 9 | -------------------------------------------------------------------------------- /public/controller/api/getvalue.php: -------------------------------------------------------------------------------- 1 | getValue($id = null, $value = null); 8 | -------------------------------------------------------------------------------- /public/controller/api/getvalues.php: -------------------------------------------------------------------------------- 1 | getValues(); 6 | -------------------------------------------------------------------------------- /public/controller/api/isfirstsetup.php: -------------------------------------------------------------------------------- 1 | isFirstSetup(); 6 | -------------------------------------------------------------------------------- /public/controller/api/listserver.php: -------------------------------------------------------------------------------- 1 | listServer(); -------------------------------------------------------------------------------- /public/controller/index.php: -------------------------------------------------------------------------------- 1 | [], 18 | 'POST' => [] 19 | ]; 20 | 21 | public static function load($file) { 22 | $router = new static; 23 | require $file; 24 | return $router; 25 | } 26 | 27 | 28 | public function get($uri, $controller) { 29 | $this->routes['GET'][$uri] = $controller; 30 | } 31 | 32 | public function post($uri, $controller) { 33 | $this->routes['POST'][$uri] = $controller; 34 | } 35 | 36 | public function direct($uri, $requestType) 37 | { 38 | if (array_key_exists($uri, $this->routes[$requestType])) { 39 | return $this->routes[$requestType][$uri]; 40 | } 41 | throw new Exception('No route defined for this URI.'); 42 | } 43 | } -------------------------------------------------------------------------------- /public/core/bootstrap.php: -------------------------------------------------------------------------------- 1 | direct(Request::uri(), Request::method( )); 7 | -------------------------------------------------------------------------------- /public/routes.php: -------------------------------------------------------------------------------- 1 | get('', 'controller/index.php'); 6 | 7 | 8 | # API routes 9 | 10 | 11 | $router->get('api', 'controller/api/api.php'); 12 | $router->post('api', 'controller/api/api.php'); 13 | 14 | $router->get('api/listserver', 'controller/api/listserver.php'); 15 | $router->post('api/listserver', 'controller/api/listserver.php'); 16 | 17 | $router->get('api/addserver', 'controller/api/addserver.php'); 18 | $router->post('api/addserver', 'controller/api/addserver.php'); 19 | 20 | $router->get('api/deleteserver', 'controller/api/deleteserver.php'); 21 | $router->post('api/deleteserver', 'controller/api/deleteserver.php'); 22 | 23 | $router->get('api/countserver', 'controller/api/countserver.php'); 24 | $router->post('api/countserver', 'controller/api/countserver.php'); 25 | 26 | $router->get('api/getvalue', 'controller/api/getvalue.php'); 27 | $router->post('api/getvalue', 'controller/api/getvalue.php'); 28 | 29 | $router->get('api/getvalues', 'controller/api/getvalues.php'); 30 | $router->post('api/getvalues', 'controller/api/getvalues.php'); 31 | 32 | $router->get('api/getstatus', 'controller/api/getstatus.php'); 33 | $router->post('api/getstatus', 'controller/api/getstatus.php'); 34 | 35 | 36 | $router->get('api/isfirstsetup', 'controller/api/isfirstsetup.php'); 37 | $router->post('api/isfirstsetup', 'controller/api/isfirstsetup.php'); 38 | 39 | $router->get('api/completesetup', 'controller/api/completesetup.php'); 40 | $router->post('api/completesetup', 'controller/api/completesetup.php'); -------------------------------------------------------------------------------- /public/view/component/addhost.view.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |

15 | Add server 16 |

17 |
18 | 25 |
26 |
27 |
28 |
29 |
30 | 31 |
32 |
33 |
34 |
35 | 36 | 37 | 38 |
39 |
40 |

41 | {{ error }} 42 |

43 |
44 |
45 |
46 | 52 |
53 |
54 |
55 |
56 |
57 | 58 |
59 | 60 |
61 | 62 |
63 |
64 |
65 | 66 |
67 | 68 |
69 |
70 |
71 | 72 |
73 | 74 |
75 |
76 |
77 | 78 |
79 | 80 |
81 |
82 |
83 |
84 | 85 |
86 | 87 |
88 |
89 |
90 | 91 |
92 | 93 |
94 |
95 |
96 | 97 |
98 | 99 |
100 |
101 |
102 | 103 |
104 | 105 |
106 |
107 |
108 | 109 |
110 | 111 |
112 |
113 |
114 | 116 |
117 |
118 | 119 | {{ currency }} 120 | 121 |
122 | 123 |
124 |
125 |
126 | 127 |
128 | 130 |
131 |
132 |
133 | 136 |
137 | 138 | 141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | 151 | 154 |
155 |
156 |
157 |
158 |
159 |
160 |
-------------------------------------------------------------------------------- /public/view/component/deletehost.view.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 14 | 18 | 19 | 20 | 21 | 31 | 68 |
69 |
70 |
-------------------------------------------------------------------------------- /public/view/component/edithost.view.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |

15 | Edit server 16 |

17 |
18 | 24 |
25 |
26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 | 35 | 36 | 37 |
38 |
39 |

40 | {{ error }} 41 |

42 |
43 |
44 |
45 | 51 |
52 |
53 |
54 |
55 |
56 | 57 |
58 | 59 |
60 | 61 |
62 |
63 |
64 | 65 |
66 | 67 |
68 |
69 |
70 | 71 |
72 | 73 |
74 |
75 |
76 | 77 |
78 | 79 |
80 |
81 |
82 | 83 |
84 | 85 |
86 |
87 |
88 |
89 | 90 |
91 | 92 |
93 |
94 |
95 | 96 |
97 | 98 |
99 |
100 |
101 | 102 |
103 | 104 |
105 |
106 |
107 | 108 |
109 | 110 |
111 |
112 |
113 | 114 |
115 | 116 |
117 |
118 |
119 | 120 |
121 |
122 | 123 | {{ currency }} 124 | 125 |
126 | 127 |
128 |
129 |
130 | 131 |
132 | 134 |
135 |
136 |
137 |
138 |
139 |
140 | 141 | Waiting for response.. 142 |
143 |
144 |
145 | 148 | 151 |
152 |
153 |
154 |
155 |
156 |
157 |
-------------------------------------------------------------------------------- /public/view/component/help.view.php: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 | 8 | 9 | 10 | 96 |
97 |
98 |
-------------------------------------------------------------------------------- /public/view/component/list.view.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | {{ title }} 8 | 9 |
10 |
11 | 12 |
13 | 16 |
17 | 21 |
22 |
23 | 24 |
25 |
26 |
27 | 28 |
29 |
30 |
31 |
32 | 35 |
36 |
37 |

38 | Oh snap, no results! 39 | 40 | Please add a host or try a different search term. 41 | 42 |

43 |
44 |
45 |
46 |
47 |
48 | 49 | 50 | 51 | 54 | 57 | 60 | 63 | 66 | 69 | 72 | 75 | 78 | 81 | 82 | 83 | 84 | 85 | 111 | 121 | 124 | 127 | 128 | 131 | 132 | 137 | 140 | 141 | 144 | 145 | 149 | 161 | 162 | 163 |
52 | Name {{ displaySortDirection }} 53 | 55 | Tags 56 | 58 | Resources 59 | 61 | Location {{ displaySortDirection }} 62 | 64 | Provider {{ displaySortDirection }} 65 | 67 | IPs 68 | 70 | Type {{ displaySortDirection }} 71 | 73 | OS {{ displaySortDirection }} 74 | 76 | {{ currency }}/{{ billingTerm }} {{ displaySortDirection }} 77 | 79 | Edit 80 |
86 |
87 |
88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
101 |
102 |
103 | {{ host.name }} 104 |
105 |
106 | {{ host.hostname }} 107 |
108 |
109 |
110 |
112 |
113 | 116 |
117 | none 118 |
119 |
120 |
122 |
{{ orDefault(host.ressources) }}
123 |
125 |
{{ orDefault(host.location) }}
126 |
129 |
{{ orDefault(host.provider) }}
130 |
133 |
134 | {{ orDefault(ip.asn) }}{{ orDefault(ip.ip) }} 135 |
136 |
138 |
{{ orDefault(host.type) }}
139 |
142 |
{{ orDefault(host.os) }}
143 |
146 |
{{ host.price }}{{ currency }}
147 |
-
148 |
150 | 155 | 160 |
164 |
165 |
166 |
167 |
-------------------------------------------------------------------------------- /public/view/footer.view.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/view/header.view.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | My hosts 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 |
-------------------------------------------------------------------------------- /public/view/index.view.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | 5 | 6 | 7 | 8 |
9 |
-------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | purge: [], 3 | theme: { 4 | extend: {}, 5 | }, 6 | variants: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } 11 | -------------------------------------------------------------------------------- /tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; --------------------------------------------------------------------------------