Start working on migrating frontend to Vue 3
This commit is contained in:
		
							
								
								
									
										23
									
								
								web/app/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								web/app/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| .DS_Store | ||||
| node_modules | ||||
| /dist | ||||
|  | ||||
|  | ||||
| # local env files | ||||
| .env.local | ||||
| .env.*.local | ||||
|  | ||||
| # Log files | ||||
| npm-debug.log* | ||||
| yarn-debug.log* | ||||
| yarn-error.log* | ||||
| pnpm-debug.log* | ||||
|  | ||||
| # Editor directories and files | ||||
| .idea | ||||
| .vscode | ||||
| *.suo | ||||
| *.ntvs* | ||||
| *.njsproj | ||||
| *.sln | ||||
| *.sw? | ||||
							
								
								
									
										24
									
								
								web/app/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								web/app/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| # app | ||||
|  | ||||
| ## Project setup | ||||
| ``` | ||||
| npm install | ||||
| ``` | ||||
|  | ||||
| ### Compiles and hot-reloads for development | ||||
| ``` | ||||
| npm run serve | ||||
| ``` | ||||
|  | ||||
| ### Compiles and minifies for production | ||||
| ``` | ||||
| npm run build | ||||
| ``` | ||||
|  | ||||
| ### Lints and fixes files | ||||
| ``` | ||||
| npm run lint | ||||
| ``` | ||||
|  | ||||
| ### Customize configuration | ||||
| See [Configuration Reference](https://cli.vuejs.org/config/). | ||||
							
								
								
									
										5
									
								
								web/app/babel.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								web/app/babel.config.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| module.exports = { | ||||
|   presets: [ | ||||
|     '@vue/cli-plugin-babel/preset' | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										27924
									
								
								web/app/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										27924
									
								
								web/app/package-lock.json
									
									
									
										generated
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										46
									
								
								web/app/package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										46
									
								
								web/app/package.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,46 @@ | ||||
| { | ||||
|   "name": "app", | ||||
|   "version": "0.1.0", | ||||
|   "private": true, | ||||
|   "scripts": { | ||||
|     "serve": "vue-cli-service serve", | ||||
|     "build": "vue-cli-service build", | ||||
|     "lint": "vue-cli-service lint" | ||||
|   }, | ||||
|   "dependencies": { | ||||
|     "@tailwindcss/postcss7-compat": "^2.0.2", | ||||
|     "autoprefixer": "^9.8.6", | ||||
|     "core-js": "^3.6.5", | ||||
|     "postcss": "^7.0.35", | ||||
|     "tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.0.2", | ||||
|     "vue": "^3.0.0" | ||||
|   }, | ||||
|   "devDependencies": { | ||||
|     "@vue/cli-plugin-babel": "~4.5.0", | ||||
|     "@vue/cli-plugin-eslint": "~4.5.0", | ||||
|     "@vue/cli-service": "~4.5.0", | ||||
|     "@vue/compiler-sfc": "^3.0.0", | ||||
|     "babel-eslint": "^10.1.0", | ||||
|     "eslint": "^6.7.2", | ||||
|     "eslint-plugin-vue": "^7.0.0-0" | ||||
|   }, | ||||
|   "eslintConfig": { | ||||
|     "root": true, | ||||
|     "env": { | ||||
|       "node": true | ||||
|     }, | ||||
|     "extends": [ | ||||
|       "plugin:vue/vue3-essential", | ||||
|       "eslint:recommended" | ||||
|     ], | ||||
|     "parserOptions": { | ||||
|       "parser": "babel-eslint" | ||||
|     }, | ||||
|     "rules": {} | ||||
|   }, | ||||
|   "browserslist": [ | ||||
|     "> 1%", | ||||
|     "last 2 versions", | ||||
|     "not dead" | ||||
|   ] | ||||
| } | ||||
							
								
								
									
										5
									
								
								web/app/postcss.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								web/app/postcss.config.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| module.exports = { | ||||
| 	plugins: [ | ||||
| 		require('tailwindcss') | ||||
| 	], | ||||
| }; | ||||
							
								
								
									
										
											BIN
										
									
								
								web/app/public/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/app/public/favicon.ico
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 15 KiB | 
							
								
								
									
										17
									
								
								web/app/public/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								web/app/public/index.html
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| <!DOCTYPE html> | ||||
| <html lang=""> | ||||
|   <head> | ||||
|     <meta charset="utf-8"> | ||||
|     <meta http-equiv="X-UA-Compatible" content="IE=edge"> | ||||
|     <meta name="viewport" content="width=device-width,initial-scale=1.0"> | ||||
|     <link rel="icon" href="<%= BASE_URL %>favicon.ico"> | ||||
|     <title>Health Dashboard | Gatus</title> | ||||
|   </head> | ||||
|   <body> | ||||
|     <noscript> | ||||
|       <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> | ||||
|     </noscript> | ||||
|     <div id="app"></div> | ||||
|     <!-- built files will be auto injected --> | ||||
|   </body> | ||||
| </html> | ||||
							
								
								
									
										52
									
								
								web/app/src/App.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								web/app/src/App.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| <template> | ||||
|   <Services :serviceStatuses="serviceStatuses" :maximumNumberOfResults="20" :showStatusOnHover="true" /> | ||||
|   <Social /> | ||||
|   <Settings @refreshStatuses="fetchStatuses" /> | ||||
| </template> | ||||
|  | ||||
|  | ||||
| <script> | ||||
| import Social from './components/Social.vue' | ||||
| import Settings from './components/Settings.vue' | ||||
| import Services from './components/Services.vue'; | ||||
|  | ||||
| export default { | ||||
|   name: 'App', | ||||
|   components: { | ||||
|     Services, | ||||
|     Social, | ||||
|     Settings | ||||
|   }, | ||||
|   methods: { | ||||
|     fetchStatuses() { | ||||
|       console.log("[App][fetchStatuses] Fetching statuses"); | ||||
|       fetch("http://localhost:8080/api/v1/statuses") | ||||
|           .then(response => response.json()) | ||||
|           .then(data => { | ||||
|             if (JSON.stringify(this.serviceStatuses) !== JSON.stringify(data)) { | ||||
|               console.log(data); | ||||
|               this.serviceStatuses = data; | ||||
|             } | ||||
|           }); | ||||
|     } | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       serviceStatuses: {} | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.fetchStatuses(); | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
|  | ||||
| <style> | ||||
|   html, body { | ||||
|     background-color: #f7f9fb; | ||||
|   } | ||||
|   html { | ||||
|     height: 100%; | ||||
|   } | ||||
| </style> | ||||
							
								
								
									
										
											BIN
										
									
								
								web/app/src/assets/github.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/app/src/assets/github.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 4.2 KiB | 
							
								
								
									
										
											BIN
										
									
								
								web/app/src/assets/logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								web/app/src/assets/logo.png
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| After Width: | Height: | Size: 51 KiB | 
							
								
								
									
										128
									
								
								web/app/src/components/Service.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								web/app/src/components/Service.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,128 @@ | ||||
| <template> | ||||
|   <div class='container px-3 py-3 border-l border-r border-t rounded-none'> | ||||
|     <div class='flex flex-wrap mb-2'> | ||||
|       <div class='w-3/4'> | ||||
|         <span class='font-bold'>{{ data.name }}</span> <span class='text-gray-500 font-light'>- {{ data.results[data.results.length - 1].hostname }}</span> | ||||
|       </div> | ||||
|       <div class='w-1/4 text-right'> | ||||
|         <span class='font-light status-min-max-ms'> | ||||
|           {{ (minResponseTime === maxResponseTime ? minResponseTime : (minResponseTime + "-" + maxResponseTime)) }}ms | ||||
|         </span> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div> | ||||
|       <div class='status-over-time flex flex-row'> | ||||
|         <slot v-for="filler in 20 - data.results.length" :key="filler"> | ||||
|           <span class="status rounded border border-dashed"> </span> | ||||
|         </slot> | ||||
|         <slot v-for="result in data.results" :key="result"> | ||||
|           <span v-if="result.success" class="status rounded bg-success">✓</span> | ||||
|           <span v-else class="status rounded bg-red-600">X</span> | ||||
|         </slot> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div class='flex flex-wrap status-time-ago'> | ||||
|       <div class='w-1/2'> | ||||
|         {{ generatePrettyTimeAgo(data.results[0].timestamp) }} | ||||
|       </div> | ||||
|       <div class='w-1/2 text-right'> | ||||
|         {{ generatePrettyTimeAgo(data.results[data.results.length - 1].timestamp) }} | ||||
|       </div> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   name: 'Service', | ||||
|   props: { | ||||
|     data: Object | ||||
|   }, | ||||
|   methods: { | ||||
|     updateMinAndMaxResponseTimes() { | ||||
|       let minResponseTime = null; | ||||
|       let maxResponseTime = null; | ||||
|       for (let i in this.data.results) { | ||||
|         const responseTime = parseInt(this.data.results[i].duration/1000000); | ||||
|         if (minResponseTime == null || minResponseTime > responseTime) { | ||||
|           minResponseTime = responseTime; | ||||
|         } | ||||
|         if (maxResponseTime == null || maxResponseTime < responseTime) { | ||||
|           maxResponseTime = responseTime; | ||||
|         } | ||||
|       } | ||||
|       if (this.minResponseTime !== minResponseTime) { | ||||
|         this.minResponseTime = minResponseTime; | ||||
|       } | ||||
|       if (this.maxResponseTime !== maxResponseTime) { | ||||
|         this.maxResponseTime = maxResponseTime; | ||||
|       } | ||||
|     }, | ||||
|     generatePrettyTimeAgo(t) { | ||||
|       let differenceInMs = new Date().getTime() - new Date(t).getTime(); | ||||
|       if (differenceInMs > 3600000) { | ||||
|         let hours = (differenceInMs/3600000).toFixed(0); | ||||
|         return hours + " hour" + (hours !== "1" ? "s" : "") + " ago"; | ||||
|       } | ||||
|       if (differenceInMs > 60000) { | ||||
|         let minutes = (differenceInMs/60000).toFixed(0); | ||||
|         return minutes + " minute" + (minutes !== "1" ? "s" : "") + " ago"; | ||||
|       } | ||||
|       return (differenceInMs/1000).toFixed(0) + " seconds ago"; | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     data: function () { | ||||
|       this.updateMinAndMaxResponseTimes(); | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.updateMinAndMaxResponseTimes() | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       minResponseTime: 0, | ||||
|       maxResponseTime: 0 | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
|  | ||||
| <style> | ||||
| .status { | ||||
|   cursor: pointer; | ||||
|   transition: all 500ms ease-in-out; | ||||
|   overflow-x: hidden; | ||||
|   color: white; | ||||
|   width: 5%; | ||||
|   font-size: 75%; | ||||
|   font-weight: 700; | ||||
|   text-align: center; | ||||
| } | ||||
|  | ||||
| .status:hover { | ||||
|   opacity: 0.7; | ||||
|   transition: opacity 100ms ease-in-out; | ||||
|   color: black; | ||||
| } | ||||
|  | ||||
| .status-over-time { | ||||
|   overflow: auto; | ||||
| } | ||||
|  | ||||
| .status-over-time > span:not(:first-child) { | ||||
|   margin-left: 2px; | ||||
| } | ||||
|  | ||||
| .status-time-ago { | ||||
|   color: #6a737d; | ||||
|   opacity: 0.5; | ||||
|   margin-top: 5px; | ||||
| } | ||||
|  | ||||
| .status-min-max-ms { | ||||
|   overflow-x: hidden; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										80
									
								
								web/app/src/components/ServiceGroup.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								web/app/src/components/ServiceGroup.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,80 @@ | ||||
| <template> | ||||
|   <div :class="services.length === 0 ? 'mt-3' : 'mt-4'"> | ||||
|     <slot v-if="name !== 'undefined'"> | ||||
|       <div class="service-group container pt-2 border"> | ||||
|         <h5 class='text-monospace text-gray-400 text-xl font-medium pb-2 px-3'> | ||||
|           <span v-if="healthy" class='text-green-600'>✓</span> | ||||
|           <span v-else class='text-yellow-400'>~</span> | ||||
|           {{ name }} | ||||
|         </h5> | ||||
|       </div> | ||||
|     </slot> | ||||
|     <div :class="name === 'undefined' ? '' : 'service-group-content'"> | ||||
|       <slot v-for="service in services" :key="service"> | ||||
|         <Service :data="service"/> | ||||
|       </slot> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
|  | ||||
| <script> | ||||
| import Service from './Service.vue'; | ||||
|  | ||||
| export default { | ||||
|   name: 'ServiceGroup', | ||||
|   components: { | ||||
|     Service | ||||
|   }, | ||||
|   props: { | ||||
|     name: String, | ||||
|     services: Array | ||||
|   }, | ||||
|   methods: { | ||||
|     healthCheck() { | ||||
|       if (this.services) { | ||||
|         for (let i in this.services) { | ||||
|           for (let j in this.services[i].results) { | ||||
|             if (!this.services[i].results[j].success) { | ||||
|               // Set the service group to unhealthy (only if it's currently healthy) | ||||
|               if (this.healthy) { | ||||
|                 this.healthy = false; | ||||
|               } | ||||
|               return; | ||||
|             } | ||||
|           } | ||||
|         } | ||||
|       } | ||||
|       // Set the service group to healthy (only if it's currently unhealthy) | ||||
|       if (!this.healthy) { | ||||
|         this.healthy = true; | ||||
|       } | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     services: function () { | ||||
|       this.healthCheck(); | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.healthCheck(); | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       healthy: true | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
|  | ||||
| <style> | ||||
| .service-group { | ||||
|   cursor: pointer; | ||||
|   user-select: none; | ||||
| } | ||||
|  | ||||
| .service-group h5:hover { | ||||
|   color: #1b1e21 !important; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										100
									
								
								web/app/src/components/Services.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								web/app/src/components/Services.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,100 @@ | ||||
| <template> | ||||
|   <div class="container mx-auto rounded shadow-xl border my-3 p-5 text-left" id="global"> | ||||
|     <div class="mb-2"> | ||||
|       <div class="flex flex-wrap"> | ||||
|         <div class="w-2/3 text-left my-auto"> | ||||
|           <div class="title font-light">Health Status</div> | ||||
|         </div> | ||||
|         <div class="w-1/3 flex justify-end"> | ||||
|           <img src="../assets/logo.png" alt="Gatus" style="min-width: 50px; max-width: 200px; width: 20%;"/> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|     <div id="results"> | ||||
|       <slot v-for="serviceGroup in serviceGroups" :key="serviceGroup"> | ||||
|         <ServiceGroup :services="serviceGroup.services" :name="serviceGroup.name" /> | ||||
|       </slot> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
|  | ||||
| <script> | ||||
| import ServiceGroup from './ServiceGroup.vue'; | ||||
|  | ||||
| export default { | ||||
|   name: 'Services', | ||||
|   components: { | ||||
|     ServiceGroup | ||||
|   }, | ||||
|   props: { | ||||
|     maximumNumberOfResults: Number, | ||||
|     showStatusOnHover: Boolean, | ||||
|     serviceStatuses: Object | ||||
|   }, | ||||
|   methods: { | ||||
|     process() { | ||||
|       let outputByGroup = {}; | ||||
|       for (let serviceStatusIndex in this.serviceStatuses) { | ||||
|         let serviceStatus = this.serviceStatuses[serviceStatusIndex]; | ||||
|         // create an empty entry if this group is new | ||||
|         if (!outputByGroup[serviceStatus.group] || outputByGroup[serviceStatus.group].length === 0) { | ||||
|           outputByGroup[serviceStatus.group] = []; | ||||
|         } | ||||
|         outputByGroup[serviceStatus.group].push(serviceStatus); | ||||
|       } | ||||
|       let serviceGroups = []; | ||||
|       for (let name in outputByGroup) { | ||||
|         if (name !== 'undefined') { | ||||
|           serviceGroups.push({ name: name, services: outputByGroup[name]}) | ||||
|         } | ||||
|       } | ||||
|       // Add all services that don't have a group at the end | ||||
|       if (outputByGroup['undefined']) { | ||||
|         serviceGroups.push({name: 'undefined', services: outputByGroup['undefined']}) | ||||
|       } | ||||
|       this.serviceGroups = serviceGroups; | ||||
|     } | ||||
|   }, | ||||
|   watch: { | ||||
|     serviceStatuses: function () { | ||||
|       this.process(); | ||||
|     } | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       userClickedStatus: false, | ||||
|       serviceGroups: [] | ||||
|     } | ||||
|   } | ||||
| } | ||||
| </script> | ||||
|  | ||||
|  | ||||
| <style> | ||||
| #global { | ||||
|   max-width: 1140px; | ||||
| } | ||||
|  | ||||
| #results div.container:first-child { | ||||
|   border-top-left-radius: 3px; | ||||
|   border-top-right-radius: 3px; | ||||
| } | ||||
|  | ||||
| #results div.container:last-child { | ||||
|   border-bottom-left-radius: 3px; | ||||
|   border-bottom-right-radius: 3px; | ||||
|   border-bottom-width: 1px; | ||||
|   border-color: #dee2e6; | ||||
|   border-style: solid; | ||||
| } | ||||
|  | ||||
| #results .service-group-content > div:nth-child(1) { | ||||
|   border-top-left-radius: 0; | ||||
|   border-top-right-radius: 0; | ||||
| } | ||||
|  | ||||
| .title { | ||||
|   font-size: 2.5rem; | ||||
| } | ||||
| </style> | ||||
							
								
								
									
										66
									
								
								web/app/src/components/Settings.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								web/app/src/components/Settings.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,66 @@ | ||||
| <template> | ||||
|   <div id="settings"> | ||||
|     <div class="flex bg-gray-200 rounded border border-gray-300 shadow"> | ||||
|       <div class="text-sm text-gray-600 rounded-xl py-1 px-2"> | ||||
|         ↻ | ||||
|       </div> | ||||
|       <select class="text-center text-gray-500 text-sm" id="refresh-rate" ref="refreshInterval" @change="handleChangeRefreshInterval"> | ||||
|         <option value="10">10s</option> | ||||
|         <option value="30" selected>30s</option> | ||||
|         <option value="60">1m</option> | ||||
|         <option value="120">2m</option> | ||||
|         <option value="300">5m</option> | ||||
|         <option value="600">10m</option> | ||||
|       </select> | ||||
|     </div> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   name: 'Settings', | ||||
|   props: {}, | ||||
|   methods: { | ||||
|     setRefreshInterval(seconds) { | ||||
|       let that = this; | ||||
|       this.refreshIntervalHandler = setInterval(function() { | ||||
|         that.refreshStatuses(); | ||||
|       }, seconds * 1000); | ||||
|     }, | ||||
|     refreshStatuses() { | ||||
|       this.$emit('refreshStatuses') | ||||
|     }, | ||||
|     handleChangeRefreshInterval() { | ||||
|       this.refreshStatuses(); | ||||
|       clearInterval(this.refreshIntervalHandler); | ||||
|       this.setRefreshInterval(this.$refs.refreshInterval.value); | ||||
|     } | ||||
|   }, | ||||
|   created() { | ||||
|     this.setRefreshInterval(this.refreshInterval); | ||||
|   }, | ||||
|   data() { | ||||
|     return { | ||||
|       refreshInterval: 30, | ||||
|       refreshIntervalHandler: 0, | ||||
|     } | ||||
|   }, | ||||
| } | ||||
|  | ||||
| // props.refreshInterval = 30 | ||||
| //$("#refresh-rate").val(30); | ||||
| </script> | ||||
|  | ||||
|  | ||||
| <style scoped> | ||||
|   #settings { | ||||
|     position: fixed; | ||||
|     left: 5px; | ||||
|     bottom: 5px; | ||||
|     padding: 5px; | ||||
|   } | ||||
|   #settings select:focus { | ||||
|     box-shadow: none; | ||||
|   } | ||||
| </style> | ||||
							
								
								
									
										32
									
								
								web/app/src/components/Social.vue
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								web/app/src/components/Social.vue
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| <template> | ||||
|   <div id="social"> | ||||
|     <a href="https://github.com/TwinProduction/gatus" target="_blank" title="Gatus on GitHub"> | ||||
|       <img src="../assets/github.png" alt="GitHub" width="32" height="auto" /> | ||||
|     </a> | ||||
|   </div> | ||||
| </template> | ||||
|  | ||||
|  | ||||
| <script> | ||||
| export default { | ||||
|   name: 'Social' | ||||
| } | ||||
| </script> | ||||
|  | ||||
|  | ||||
| <style scoped> | ||||
|   #social { | ||||
|     position: fixed; | ||||
|     right: 5px; | ||||
|     bottom: 5px; | ||||
|     padding: 5px; | ||||
|     margin: 0; | ||||
|     z-index: 100; | ||||
|   } | ||||
|   #social img { | ||||
|     opacity: 0.3; | ||||
|   } | ||||
|   #social img:hover { | ||||
|     opacity: 1; | ||||
|   } | ||||
| </style> | ||||
							
								
								
									
										11
									
								
								web/app/src/index.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								web/app/src/index.css
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| @tailwind base; | ||||
| @tailwind components; | ||||
| @tailwind utilities; | ||||
|  | ||||
| .bg-success { | ||||
|     background-color: #28a745; | ||||
| } | ||||
|  | ||||
| .text-monospace { | ||||
|     font-family: Consolas, monospace; | ||||
| } | ||||
							
								
								
									
										5
									
								
								web/app/src/main.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								web/app/src/main.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| import { createApp } from 'vue' | ||||
| import App from './App.vue' | ||||
| import './index.css' | ||||
|  | ||||
| createApp(App).mount('#app') | ||||
							
								
								
									
										11
									
								
								web/app/tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								web/app/tailwind.config.js
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | ||||
| module.exports = { | ||||
|   purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], | ||||
|   darkMode: false, // or 'media' or 'class' | ||||
|   theme: { | ||||
|     extend: {}, | ||||
|   }, | ||||
|   variants: { | ||||
|     extend: {}, | ||||
|   }, | ||||
|   plugins: [], | ||||
| } | ||||
		Reference in New Issue
	
	Block a user