v1.0.20 (#65)
This commit is contained in:
		| @@ -53,12 +53,13 @@ | ||||
| 	let upgradeDisabled = false; | ||||
| 	let upgradeDone = false; | ||||
| 	let showAck = false; | ||||
| 	let globalFeatureFlag = browser && localStorage.getItem('globalFeatureFlag') | ||||
| 	const options = { | ||||
| 		duration: 2000 | ||||
| 	}; | ||||
| 	onMount(async () => { | ||||
| 		upgradeAvailable = await checkUpgrade(); | ||||
| 		browser && localStorage.removeItem('token') | ||||
| 		browser && localStorage.removeItem('token'); | ||||
| 		if (!localStorage.getItem('automaticErrorReportsAck')) { | ||||
| 			showAck = true; | ||||
| 			if (latest?.coolify[branch]?.settings?.sendErrors) { | ||||
| @@ -224,6 +225,31 @@ | ||||
| 					</div> | ||||
| 				</Tooltip> | ||||
| 				<div class="flex-1" /> | ||||
| 				{#if globalFeatureFlag} | ||||
| 				<Tooltip position="right" label="Server(s)"> | ||||
| 					<div | ||||
| 						class="p-2 hover:bg-warmGray-700 rounded hover:text-red-500 mb-4 transition-all duration-100 cursor-pointer" | ||||
| 						on:click={() => goto('/servers')} | ||||
| 						class:text-red-500={$page.path === '/servers' || $page.path.startsWith('/servers')} | ||||
| 						class:bg-warmGray-700={$page.path === '/servers' || $page.path.startsWith('/servers')} | ||||
| 					> | ||||
| 						<svg | ||||
| 							class="w-8" | ||||
| 							xmlns="http://www.w3.org/2000/svg" | ||||
| 							fill="none" | ||||
| 							viewBox="0 0 24 24" | ||||
| 							stroke="currentColor" | ||||
| 						> | ||||
| 							<path | ||||
| 								stroke-linecap="round" | ||||
| 								stroke-linejoin="round" | ||||
| 								stroke-width="2" | ||||
| 								d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" | ||||
| 							/> | ||||
| 						</svg> | ||||
| 					</div> | ||||
| 				</Tooltip> | ||||
| 				{/if} | ||||
| 				<Tooltip position="right" label="Settings"> | ||||
| 					<button | ||||
| 						class="p-2 hover:bg-warmGray-700 rounded hover:text-yellow-500 transition-all duration-100 cursor-pointer" | ||||
|   | ||||
							
								
								
									
										25
									
								
								src/routes/api/v1/servers/cleanups/caches.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/routes/api/v1/servers/cleanups/caches.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import { saveServerLog } from '$lib/api/applications/logging'; | ||||
| import { execShellAsync } from '$lib/api/common'; | ||||
| import type { Request } from '@sveltejs/kit'; | ||||
|  | ||||
|  | ||||
| export async function post(request: Request) { | ||||
|     try { | ||||
|         const output = await execShellAsync('docker builder prune -af') | ||||
|         return { | ||||
|             status: 200, | ||||
|             body: { | ||||
|                 message: 'OK', | ||||
|                 output: output.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,"").split('\n').pop() | ||||
|             } | ||||
|         } | ||||
|     } catch (error) { | ||||
|         await saveServerLog(error); | ||||
|         return { | ||||
|             status: 500, | ||||
|             body: { | ||||
|                 error: error.message || error | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/routes/api/v1/servers/cleanups/containers.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/routes/api/v1/servers/cleanups/containers.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import { saveServerLog } from '$lib/api/applications/logging'; | ||||
| import { execShellAsync } from '$lib/api/common'; | ||||
| import type { Request } from '@sveltejs/kit'; | ||||
|  | ||||
|  | ||||
| export async function post(request: Request) { | ||||
|     try { | ||||
|         const output = await execShellAsync('docker container prune -f') | ||||
|         return { | ||||
|             status: 200, | ||||
|             body: { | ||||
|                 message: 'OK', | ||||
|                 output: output.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,"").split('\n').pop() | ||||
|             } | ||||
|         } | ||||
|     } catch (error) { | ||||
|         await saveServerLog(error); | ||||
|         return { | ||||
|             status: 500, | ||||
|             body: { | ||||
|                 error: error.message || error | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/routes/api/v1/servers/cleanups/images.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/routes/api/v1/servers/cleanups/images.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import { saveServerLog } from '$lib/api/applications/logging'; | ||||
| import { execShellAsync } from '$lib/api/common'; | ||||
| import type { Request } from '@sveltejs/kit'; | ||||
|  | ||||
|  | ||||
| export async function post(request: Request) { | ||||
|     try { | ||||
|         const output = await execShellAsync('docker image prune -af') | ||||
|         return { | ||||
|             status: 200, | ||||
|             body: { | ||||
|                 message: 'OK', | ||||
|                 output: output.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,"").split('\n').pop() | ||||
|             } | ||||
|         } | ||||
|     } catch (error) { | ||||
|         await saveServerLog(error); | ||||
|         return { | ||||
|             status: 500, | ||||
|             body: { | ||||
|                 error: error.message || error | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										25
									
								
								src/routes/api/v1/servers/cleanups/volumes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/routes/api/v1/servers/cleanups/volumes.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,25 @@ | ||||
| import { saveServerLog } from '$lib/api/applications/logging'; | ||||
| import { execShellAsync } from '$lib/api/common'; | ||||
| import type { Request } from '@sveltejs/kit'; | ||||
|  | ||||
|  | ||||
| export async function post(request: Request) { | ||||
|     try { | ||||
|         const output = await execShellAsync('docker volume prune -f') | ||||
|         return { | ||||
|             status: 200, | ||||
|             body: { | ||||
|                 message: 'OK', | ||||
|                 output: output.replace(/^(?=\n)$|^\s*|\s*$|\n\n+/gm,"").split('\n').pop() | ||||
|             } | ||||
|         } | ||||
|     } catch (error) { | ||||
|         await saveServerLog(error); | ||||
|         return { | ||||
|             status: 500, | ||||
|             body: { | ||||
|                 error: error.message || error | ||||
|             } | ||||
|         }; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										34
									
								
								src/routes/api/v1/servers/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								src/routes/api/v1/servers/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | ||||
| import { saveServerLog } from '$lib/api/applications/logging'; | ||||
| import { execShellAsync } from '$lib/api/common'; | ||||
| import { docker } from '$lib/api/docker'; | ||||
| import type { Request } from '@sveltejs/kit'; | ||||
| import systeminformation from 'systeminformation' | ||||
|  | ||||
| export async function get(request: Request) { | ||||
| 	try { | ||||
| 		const df = await execShellAsync( | ||||
| 			`docker system df  --format '{{ json . }}'` | ||||
| 		); | ||||
| 		const dockerReclaimable = df | ||||
| 			.split('\n') | ||||
| 			.filter((n) => n) | ||||
| 			.map((s) => JSON.parse(s)) | ||||
| 		 | ||||
| 		return { | ||||
| 			status: 200, | ||||
| 			body: { | ||||
| 				hostname: await (await systeminformation.osInfo()).hostname, | ||||
| 				filesystems: await (await systeminformation.fsSize()).filter(fs => !fs.fs.match('/dev/loop') || !fs.fs.match('/var/lib/docker/')), | ||||
| 				dockerReclaimable | ||||
| 			} | ||||
| 		} | ||||
| 	} catch (error) { | ||||
| 		await saveServerLog(error); | ||||
| 		return { | ||||
| 			status: 500, | ||||
| 			body: { | ||||
| 				error: error.message || error | ||||
| 			} | ||||
| 		}; | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										173
									
								
								src/routes/api/v1/services/deploy/wordpress/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										173
									
								
								src/routes/api/v1/services/deploy/wordpress/index.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,173 @@ | ||||
| import type { Request } from '@sveltejs/kit'; | ||||
| import yaml from 'js-yaml'; | ||||
| import generator from 'generate-password'; | ||||
| import { promises as fs } from 'fs'; | ||||
| import { docker } from '$lib/api/docker'; | ||||
| import { baseServiceConfiguration } from '$lib/api/applications/common'; | ||||
| import { cleanupTmp, execShellAsync } from '$lib/api/common'; | ||||
|  | ||||
| export async function post(request: Request) { | ||||
|     let { baseURL, remoteDB, database, wordpressExtraConfiguration } = request.body; | ||||
|     const traefikURL = baseURL; | ||||
|     baseURL = `https://${baseURL}`; | ||||
|     console.log({ baseURL, remoteDB, database, wordpressExtraConfiguration }) | ||||
|  | ||||
|     const workdir = '/tmp/wordpress'; | ||||
|     const deployId = `wp-${generator.generate({ length: 5, numbers: true, strict: true })}` | ||||
|     const defaultDatabaseName = generator.generate({ length: 12, numbers: true, strict: true }) | ||||
|     const defaultDatabaseHost = `${deployId}-mysql` | ||||
|     const defaultDatabaseUser = generator.generate({ length: 12, numbers: true, strict: true }) | ||||
|     const defaultDatabasePassword = generator.generate({ length: 24, numbers: true, strict: true }) | ||||
|     const defaultDatabaseRootPassword = generator.generate({ length: 24, numbers: true, strict: true }) | ||||
|     const defaultDatabaseRootUser = generator.generate({ length: 12, numbers: true, strict: true }) | ||||
|     let secrets = [ | ||||
|         { name: 'WORDPRESS_DB_HOST', value: defaultDatabaseHost }, | ||||
|         { name: 'WORDPRESS_DB_USER', value: defaultDatabaseUser }, | ||||
|         { name: 'WORDPRESS_DB_PASSWORD', value: defaultDatabasePassword }, | ||||
|         { name: 'WORDPRESS_DB_NAME', value: defaultDatabaseName }, | ||||
|         { name: 'WORDPRESS_CONFIG_EXTRA', value: wordpressExtraConfiguration } | ||||
|     ]; | ||||
|  | ||||
|     const generateEnvsMySQL = { | ||||
|         MYSQL_ROOT_PASSWORD: defaultDatabaseRootPassword, | ||||
|         MYSQL_ROOT_USER: defaultDatabaseRootUser, | ||||
|         MYSQL_USER: defaultDatabaseUser, | ||||
|         MYSQL_PASSWORD: defaultDatabasePassword, | ||||
|         MYSQL_DATABASE: defaultDatabaseName | ||||
|     }; | ||||
|     const image = 'bitnami/mysql:8.0'; | ||||
|     const volume = `${deployId}-mysql-data:/bitnami/mysql/data`; | ||||
|  | ||||
|     if (remoteDB) { | ||||
|         secrets = [ | ||||
|             { name: 'WORDPRESS_DB_HOST', value: database.host }, | ||||
|             { name: 'WORDPRESS_DB_USER', value: database.user }, | ||||
|             { name: 'WORDPRESS_DB_PASSWORD', value: database.password }, | ||||
|             { name: 'WORDPRESS_DB_NAME', value: database.name }, | ||||
|             { name: 'WORDPRESS_TABLE_PREFIX', value: database.tablePrefix }, | ||||
|             { name: 'WORDPRESS_CONFIG_EXTRA', value: wordpressExtraConfiguration } | ||||
|         ] | ||||
|     } | ||||
|  | ||||
|     const generateEnvsWordpress = {}; | ||||
|     for (const secret of secrets) generateEnvsWordpress[secret.name] = secret.value; | ||||
|     let stack = { | ||||
|         version: '3.8', | ||||
|         services: { | ||||
|             [deployId]: { | ||||
|                 image: 'wordpress', | ||||
|                 networks: [`${docker.network}`], | ||||
|                 environment: generateEnvsWordpress, | ||||
|                 volumes: [`${deployId}-wordpress-data:/var/www/html`], | ||||
|                 deploy: { | ||||
|                     ...baseServiceConfiguration, | ||||
|                     labels: [ | ||||
|                         'managedBy=coolify', | ||||
|                         'type=service', | ||||
|                         'serviceName=' + deployId, | ||||
|                         'configuration=' + | ||||
|                         JSON.stringify({ | ||||
|                             deployId, | ||||
|                             baseURL, | ||||
|                             generateEnvsWordpress | ||||
|                         }), | ||||
|                         'traefik.enable=true', | ||||
|                         'traefik.http.services.' + deployId + '.loadbalancer.server.port=80', | ||||
|                         'traefik.http.routers.' + deployId + '.entrypoints=websecure', | ||||
|                         'traefik.http.routers.' + | ||||
|                         deployId + | ||||
|                         '.rule=Host(`' + | ||||
|                         traefikURL + | ||||
|                         '`) && PathPrefix(`/`)', | ||||
|                         'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt', | ||||
|                         'traefik.http.routers.' + deployId + '.middlewares=global-compress' | ||||
|                     ] | ||||
|                 } | ||||
|             }, | ||||
|             [`${deployId}-mysql`]: { | ||||
|                 image, | ||||
|                 networks: [`${docker.network}`], | ||||
|                 environment: generateEnvsMySQL, | ||||
|                 volumes: [volume], | ||||
|                 deploy: { | ||||
|                     ...baseServiceConfiguration, | ||||
|                     labels: [ | ||||
|                         'managedBy=coolify', | ||||
|                         'type=service', | ||||
|                         'serviceName=' + deployId, | ||||
|                     ] | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         networks: { | ||||
|             [`${docker.network}`]: { | ||||
|                 external: true | ||||
|             } | ||||
|         }, | ||||
|         volumes: { | ||||
|             [`${deployId}-wordpress-data`]: { | ||||
|                 external: true | ||||
|             }, | ||||
|             [`${deployId}-mysql-data`]: { | ||||
|                 external: true | ||||
|             } | ||||
|         }, | ||||
|     }; | ||||
|     if (remoteDB) { | ||||
|         stack = { | ||||
|             version: '3.8', | ||||
|             services: { | ||||
|                 [deployId]: { | ||||
|                     image: 'wordpress', | ||||
|                     networks: [`${docker.network}`], | ||||
|                     environment: generateEnvsWordpress, | ||||
|                     volumes: [`${deployId}-wordpress-data:/var/www/html`], | ||||
|                     deploy: { | ||||
|                         ...baseServiceConfiguration, | ||||
|                         labels: [ | ||||
|                             'managedBy=coolify', | ||||
|                             'type=service', | ||||
|                             'serviceName=' + deployId, | ||||
|                             'configuration=' + | ||||
|                             JSON.stringify({ | ||||
|                                 deployId, | ||||
|                                 baseURL, | ||||
|                                 generateEnvsWordpress | ||||
|                             }), | ||||
|                             'traefik.enable=true', | ||||
|                             'traefik.http.services.' + deployId + '.loadbalancer.server.port=80', | ||||
|                             'traefik.http.routers.' + deployId + '.entrypoints=websecure', | ||||
|                             'traefik.http.routers.' + | ||||
|                             deployId + | ||||
|                             '.rule=Host(`' + | ||||
|                             traefikURL + | ||||
|                             '`) && PathPrefix(`/`)', | ||||
|                             'traefik.http.routers.' + deployId + '.tls.certresolver=letsencrypt', | ||||
|                             'traefik.http.routers.' + deployId + '.middlewares=global-compress' | ||||
|                         ] | ||||
|                     } | ||||
|                 } | ||||
|             }, | ||||
|             networks: { | ||||
|                 [`${docker.network}`]: { | ||||
|                     external: true | ||||
|                 } | ||||
|             }, | ||||
|             volumes: { | ||||
|                 [`${deployId}-wordpress-data`]: { | ||||
|                     external: true | ||||
|                 } | ||||
|             }, | ||||
|         }; | ||||
|     } | ||||
|     console.log(stack) | ||||
|     await execShellAsync(`mkdir -p ${workdir}`); | ||||
|     await fs.writeFile(`${workdir}/stack.yml`, yaml.dump(stack)); | ||||
|     await execShellAsync(`docker stack rm ${deployId}`); | ||||
|     await execShellAsync(`cat ${workdir}/stack.yml | docker stack deploy --prune -c - ${deployId}`); | ||||
|     cleanupTmp(workdir); | ||||
|     return { | ||||
|         status: 200, | ||||
|         body: { message: 'OK' } | ||||
|     }; | ||||
| } | ||||
| @@ -2,6 +2,14 @@ | ||||
| 	import { goto } from '$app/navigation'; | ||||
| 	import { dashboard } from '$store'; | ||||
| 	import { fade } from 'svelte/transition'; | ||||
| 	async function openConfiguration(service) { | ||||
| 		if (service.serviceName === 'wordpress') { | ||||
| 			goto(`/service/${service.configuration.deployId}/configuration`); | ||||
| 		} else { | ||||
| 			goto(`/service/${service.serviceName}/configuration`); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| </script> | ||||
|  | ||||
| <div | ||||
| @@ -34,7 +42,7 @@ | ||||
| 					<div | ||||
| 						in:fade={{ duration: 200 }} | ||||
| 						class="px-4 pb-4" | ||||
| 						on:click={() => goto(`/service/${service.serviceName}/configuration`)} | ||||
| 						on:click={() => openConfiguration(service)} | ||||
| 					> | ||||
| 						<div | ||||
| 							class="relative rounded-xl p-6 bg-warmGray-800 border-2 border-dashed border-transparent hover:border-blue-500 text-white shadow-md cursor-pointer ease-in-out hover:scale-105 duration-100 group" | ||||
| @@ -49,7 +57,7 @@ | ||||
| 										/> | ||||
| 										<div class="text-white font-bold">Plausible Analytics</div> | ||||
| 									</div> | ||||
| 									{:else if service.serviceName == 'nocodb'} | ||||
| 								{:else if service.serviceName == 'nocodb'} | ||||
| 									<div> | ||||
| 										<img | ||||
| 											alt="nocodedb" | ||||
| @@ -58,15 +66,27 @@ | ||||
| 										/> | ||||
| 										<div class="text-white font-bold">NocoDB</div> | ||||
| 									</div> | ||||
| 									{:else if service.serviceName == 'code-server'} | ||||
| 								{:else if service.serviceName == 'code-server'} | ||||
| 									<div> | ||||
| 										<svg class="w-10 absolute top-0 left-0 -m-6" viewBox="0 0 128 128"> | ||||
| 											<path d="M3.656 45.043s-3.027-2.191.61-5.113l8.468-7.594s2.426-2.559 4.989-.328l78.175 59.328v28.45s-.039 4.468-5.757 3.976zm0 0" fill="#2489ca"></path><path d="M23.809 63.379L3.656 81.742s-2.07 1.543 0 4.305l9.356 8.527s2.222 2.395 5.508-.328l21.359-16.238zm0 0" fill="#1070b3"></path><path d="M59.184 63.531l36.953-28.285-.239-28.297S94.32.773 89.055 3.99L39.879 48.851zm0 0" fill="#0877b9"></path><path d="M90.14 123.797c2.145 2.203 4.747 1.48 4.747 1.48l28.797-14.222c3.687-2.52 3.171-5.645 3.171-5.645V20.465c0-3.735-3.812-5.024-3.812-5.024L98.082 3.38c-5.453-3.379-9.027.61-9.027.61s4.593-3.317 6.843 2.96v112.317c0 .773-.164 1.53-.492 2.214-.656 1.332-2.086 2.57-5.504 2.051zm0 0" fill="#3c99d4"></path> | ||||
| 											</svg>  | ||||
| 											<path | ||||
| 												d="M3.656 45.043s-3.027-2.191.61-5.113l8.468-7.594s2.426-2.559 4.989-.328l78.175 59.328v28.45s-.039 4.468-5.757 3.976zm0 0" | ||||
| 												fill="#2489ca" | ||||
| 											/><path | ||||
| 												d="M23.809 63.379L3.656 81.742s-2.07 1.543 0 4.305l9.356 8.527s2.222 2.395 5.508-.328l21.359-16.238zm0 0" | ||||
| 												fill="#1070b3" | ||||
| 											/><path | ||||
| 												d="M59.184 63.531l36.953-28.285-.239-28.297S94.32.773 89.055 3.99L39.879 48.851zm0 0" | ||||
| 												fill="#0877b9" | ||||
| 											/><path | ||||
| 												d="M90.14 123.797c2.145 2.203 4.747 1.48 4.747 1.48l28.797-14.222c3.687-2.52 3.171-5.645 3.171-5.645V20.465c0-3.735-3.812-5.024-3.812-5.024L98.082 3.38c-5.453-3.379-9.027.61-9.027.61s4.593-3.317 6.843 2.96v112.317c0 .773-.164 1.53-.492 2.214-.656 1.332-2.086 2.57-5.504 2.051zm0 0" | ||||
| 												fill="#3c99d4" | ||||
| 											/> | ||||
| 										</svg> | ||||
|  | ||||
| 										<div class="text-white font-bold">VSCode Server</div> | ||||
| 									</div> | ||||
| 									{:else if service.serviceName == 'minio'} | ||||
| 								{:else if service.serviceName == 'minio'} | ||||
| 									<div> | ||||
| 										<img | ||||
| 											alt="minio" | ||||
| @@ -76,6 +96,21 @@ | ||||
|  | ||||
| 										<div class="text-white font-bold">MinIO</div> | ||||
| 									</div> | ||||
| 								{:else if service.serviceName.match(/wp-/)} | ||||
| 									<svg class="w-10 absolute top-0 left-0 -m-6" viewBox="0 0 128 128"> | ||||
| 										<path | ||||
| 											fill-rule="evenodd" | ||||
| 											clip-rule="evenodd" | ||||
| 											fill="white" | ||||
| 											d="M64.094 126.224c34.275-.052 62.021-27.933 62.021-62.325 0-33.833-27.618-61.697-60.613-62.286C30.85.995 1.894 29.113 1.885 63.21c-.01 35.079 27.612 63.064 62.209 63.014zM63.993 4.63c32.907-.011 59.126 26.725 59.116 60.28-.011 31.679-26.925 58.18-59.092 58.187-32.771.007-59.125-26.563-59.124-59.608.002-32.193 26.766-58.848 59.1-58.859zM39.157 35.896c.538 1.793-.968 2.417-2.569 2.542-1.685.13-3.369.257-5.325.406 6.456 19.234 12.815 38.183 19.325 57.573.464-.759.655-.973.739-1.223 3.574-10.682 7.168-21.357 10.651-32.069.318-.977.16-2.271-.188-3.275-1.843-5.32-4.051-10.524-5.667-15.908-1.105-3.686-2.571-6.071-6.928-5.644-.742.073-1.648-1.524-2.479-2.349 1.005-.6 2.003-1.704 3.017-1.719a849.593 849.593 0 0126.618.008c1.018.017 2.016 1.15 3.021 1.765-.88.804-1.639 2.01-2.668 2.321-1.651.498-3.482.404-5.458.58l19.349 57.56c2.931-9.736 5.658-18.676 8.31-27.639 2.366-8.001.956-15.473-3.322-22.52-1.286-2.119-2.866-4.175-3.595-6.486-.828-2.629-1.516-5.622-1.077-8.259.745-4.469 4.174-6.688 8.814-7.113C74.333.881 34.431 9.317 19.728 34.922c5.66-.261 11.064-.604 16.472-.678 1.022-.013 2.717.851 2.957 1.652zm10.117 77.971c-.118.345-.125.729-.218 1.302 10.943 3.034 21.675 2.815 32.659-.886l-16.78-45.96c-5.37 15.611-10.52 30.575-15.661 45.544zm-8.456-2.078l-25.281-69.35c-11.405 22.278-2.729 56.268 25.281 69.35zm76.428-44.562c.802-10.534-2.832-25.119-5.97-27.125-.35 3.875-.106 8.186-1.218 12.114-2.617 9.255-5.817 18.349-8.899 27.468-3.35 9.912-6.832 19.779-10.257 29.666 16.092-9.539 24.935-23.618 26.344-42.123z" | ||||
| 										/> | ||||
| 									</svg> | ||||
| 									<div class="text-white font-bold text-center"> | ||||
| 										Wordpress<span | ||||
| 											class="flex text-xs items-center justify-center text-warmGray-300  group-hover:text-white" | ||||
| 											>({service.configuration.baseURL.replace('https://', '')})</span | ||||
| 										> | ||||
| 									</div> | ||||
| 								{/if} | ||||
| 							</div> | ||||
| 						</div> | ||||
|   | ||||
							
								
								
									
										101
									
								
								src/routes/servers/index.svelte
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/routes/servers/index.svelte
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| <script context="module"> | ||||
| 	/** | ||||
| 	 * @type {import('@sveltejs/kit').Load} | ||||
| 	 */ | ||||
| 	export async function load({ fetch }) { | ||||
| 		try { | ||||
| 			const { hostname, filesystems,dockerReclaimable } = await (await fetch(`/api/v1/servers`)).json(); | ||||
| 			return { | ||||
| 				props: { | ||||
| 					hostname, | ||||
| 					filesystems, | ||||
|                     dockerReclaimable | ||||
| 				} | ||||
| 			}; | ||||
| 		} catch (error) { | ||||
| 			return { | ||||
| 				props: { | ||||
| 					hostname: null, | ||||
| 					filesystems: null, | ||||
|                     dockerReclaimable: null | ||||
| 				} | ||||
| 			}; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| </script> | ||||
|  | ||||
| <script> | ||||
| 	export let hostname; | ||||
| 	export let filesystems; | ||||
|     export let dockerReclaimable; | ||||
| 	import { browser } from '$app/env'; | ||||
| 	import { session } from '$app/stores'; | ||||
| 	import { request } from '$lib/request'; | ||||
| 	import { toast } from '@zerodevx/svelte-toast'; | ||||
| 	import { fade } from 'svelte/transition'; | ||||
|     async function refetch() { | ||||
|         const data = await request('/api/v1/servers', $session) | ||||
|         filesystems = data.filesystems | ||||
|         dockerReclaimable = data.dockerReclaimable | ||||
|     } | ||||
| 	async function cleanupVolumes() { | ||||
| 		const { output } = await request('/api/v1/servers/cleanups/volumes', $session, { | ||||
| 			body: {} | ||||
| 		}); | ||||
| 		browser && toast.push(output); | ||||
|         await refetch() | ||||
| 	} | ||||
|     async function cleanupImages() { | ||||
| 		const { output } = await request('/api/v1/servers/cleanups/images', $session, { | ||||
| 			body: {} | ||||
| 		}); | ||||
| 		browser && toast.push(output); | ||||
|         await refetch() | ||||
| 	} | ||||
|     async function cleanupBuildCache() { | ||||
| 		const { output } = await request('/api/v1/servers/cleanups/caches', $session, { | ||||
| 			body: {} | ||||
| 		}); | ||||
| 		browser && toast.push(output); | ||||
|         await refetch() | ||||
| 	} | ||||
| 	async function cleanupContainers() { | ||||
| 		const { output } = await request('/api/v1/servers/cleanups/containers', $session, { | ||||
| 			body: {} | ||||
| 		}); | ||||
| 		browser && toast.push(output); | ||||
|         await refetch() | ||||
| 	} | ||||
|  | ||||
| </script> | ||||
|  | ||||
| <div class="min-h-full text-white" in:fade={{ duration: 100 }}> | ||||
| 	<div class="py-5 text-left px-6 text-3xl tracking-tight font-bold flex items-center"> | ||||
| 		<div>Servers</div> | ||||
| 	</div> | ||||
| </div> | ||||
|  | ||||
| <div in:fade={{ duration: 100 }}> | ||||
| 	<div class="max-w-4xl mx-auto px-6 pb-4 h-64 "> | ||||
| 		<div class="text-center font-bold text-xl">{hostname}</div> | ||||
| 		<div class="font-bold">Filesystem Usage</div> | ||||
| 		{#each filesystems as filesystem} | ||||
| 			<!-- <div>{JSON.stringify(filesystem)}</div> --> | ||||
| 			<div class="text-xs"> | ||||
| 				{filesystem.mount}:  {(filesystem.available / 1024 / 1024).toFixed()}MB ({filesystem.use}%) free of {(filesystem.size /1024 /1024).toFixed()}MB | ||||
| 			</div> | ||||
| 		{/each} | ||||
|         <div class="font-bold">Docker Reclaimable</div> | ||||
|         {#each dockerReclaimable as reclaimable} | ||||
|         <div class="text-xs"> | ||||
|             {reclaimable.Type}: {reclaimable.Reclaimable} of {reclaimable.Size} | ||||
|         </div> | ||||
|         {/each} | ||||
|  | ||||
| 		<button class="button hover:bg-warmGray-700 bg-warmGray-800 rounded p-2 font-bold" on:click={cleanupVolumes}>Cleanup unused volumes</button> | ||||
|         <button class="button hover:bg-warmGray-700 bg-warmGray-800 rounded p-2 font-bold" on:click={cleanupImages}>Cleanup unused images</button> | ||||
|         <button class="button hover:bg-warmGray-700 bg-warmGray-800 rounded p-2 font-bold" on:click={cleanupBuildCache}>Cleanup build caches</button> | ||||
| 		<button class="button hover:bg-warmGray-700 bg-warmGray-800 rounded p-2 font-bold" on:click={cleanupContainers}>Cleanup containers</button> | ||||
| 	</div> | ||||
| </div> | ||||
| @@ -13,6 +13,7 @@ | ||||
| 	let service = {}; | ||||
| 	async function loadServiceConfig() { | ||||
| 		if ($page.params.name) { | ||||
|  | ||||
| 			try { | ||||
| 				service = await request(`/api/v1/services/${$page.params.name}`, $session); | ||||
| 			} catch (error) { | ||||
| @@ -39,6 +40,8 @@ | ||||
| 				<div>VSCode Server</div> | ||||
| 			{:else if $page.params.name === 'minio'} | ||||
| 				<div>MinIO</div> | ||||
| 			{:else if $page.params.name.match(/wp-/)} | ||||
| 				<div>Wordpress<span class="flex text-xs items-center justify-center">({service.config.baseURL.replace('https://','')})</span></div> | ||||
| 			{/if} | ||||
|  | ||||
| 			<div class="px-4"> | ||||
| @@ -76,6 +79,15 @@ | ||||
| 						class="w-7 mx-auto" | ||||
| 						src="https://cdn.coollabs.io/assets/coolify/services/minio/MINIO_Bird.png" | ||||
| 					/> | ||||
| 				{:else if $page.params.name.match(/wp-/)} | ||||
| 					<svg class="w-8 mx-auto" viewBox="0 0 128 128"> | ||||
| 						<path | ||||
| 							fill-rule="evenodd" | ||||
| 							clip-rule="evenodd" | ||||
| 							fill="white" | ||||
| 							d="M64.094 126.224c34.275-.052 62.021-27.933 62.021-62.325 0-33.833-27.618-61.697-60.613-62.286C30.85.995 1.894 29.113 1.885 63.21c-.01 35.079 27.612 63.064 62.209 63.014zM63.993 4.63c32.907-.011 59.126 26.725 59.116 60.28-.011 31.679-26.925 58.18-59.092 58.187-32.771.007-59.125-26.563-59.124-59.608.002-32.193 26.766-58.848 59.1-58.859zM39.157 35.896c.538 1.793-.968 2.417-2.569 2.542-1.685.13-3.369.257-5.325.406 6.456 19.234 12.815 38.183 19.325 57.573.464-.759.655-.973.739-1.223 3.574-10.682 7.168-21.357 10.651-32.069.318-.977.16-2.271-.188-3.275-1.843-5.32-4.051-10.524-5.667-15.908-1.105-3.686-2.571-6.071-6.928-5.644-.742.073-1.648-1.524-2.479-2.349 1.005-.6 2.003-1.704 3.017-1.719a849.593 849.593 0 0126.618.008c1.018.017 2.016 1.15 3.021 1.765-.88.804-1.639 2.01-2.668 2.321-1.651.498-3.482.404-5.458.58l19.349 57.56c2.931-9.736 5.658-18.676 8.31-27.639 2.366-8.001.956-15.473-3.322-22.52-1.286-2.119-2.866-4.175-3.595-6.486-.828-2.629-1.516-5.622-1.077-8.259.745-4.469 4.174-6.688 8.814-7.113C74.333.881 34.431 9.317 19.728 34.922c5.66-.261 11.064-.604 16.472-.678 1.022-.013 2.717.851 2.957 1.652zm10.117 77.971c-.118.345-.125.729-.218 1.302 10.943 3.034 21.675 2.815 32.659-.886l-16.78-45.96c-5.37 15.611-10.52 30.575-15.661 45.544zm-8.456-2.078l-25.281-69.35c-11.405 22.278-2.729 56.268 25.281 69.35zm76.428-44.562c.802-10.534-2.832-25.119-5.97-27.125-.35 3.875-.106 8.186-1.218 12.114-2.617 9.255-5.817 18.349-8.899 27.468-3.35 9.912-6.832 19.779-10.257 29.666 16.092-9.539 24.935-23.618 26.344-42.123z" | ||||
| 						/> | ||||
| 					</svg> | ||||
| 				{/if} | ||||
| 			</div> | ||||
| 			<a target="_blank" class="icon mx-2" href={service.config.baseURL}> | ||||
| @@ -106,6 +118,8 @@ | ||||
| 				<CodeServer {service} /> | ||||
| 			{:else if $page.params.name === 'minio'} | ||||
| 				<MinIo {service} /> | ||||
| 			{:else if $page.params.name.match(/wp-/)} | ||||
| 				<div class="font-bold">Nothing to show here. Enjoy using WordPress!</div> | ||||
| 			{/if} | ||||
| 		</div> | ||||
| 	</div> | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| 	import { fade } from 'svelte/transition'; | ||||
|  | ||||
| 	import { toast } from '@zerodevx/svelte-toast'; | ||||
| 	import { newService } from '$store'; | ||||
| 	import { newService, newWordpressService } from '$store'; | ||||
| 	import { page, session } from '$app/stores'; | ||||
| 	import { request } from '$lib/request'; | ||||
| 	import { goto } from '$app/navigation'; | ||||
| @@ -25,7 +25,7 @@ | ||||
| 	$: deployableNocoDB = $newService.baseURL === '' || $newService.baseURL === null; | ||||
| 	$: deployableCodeServer = $newService.baseURL === '' || $newService.baseURL === null; | ||||
| 	$: deployableMinIO = $newService.baseURL === '' || $newService.baseURL === null; | ||||
|  | ||||
| 	$: deployableWordpress = false | ||||
| 	let loading = false; | ||||
| 	async function deployPlausible() { | ||||
| 		try { | ||||
| @@ -115,6 +115,28 @@ | ||||
| 			loading = false; | ||||
| 		} | ||||
| 	} | ||||
| 	async function deployWordpress() { | ||||
| 		try { | ||||
| 			loading = true; | ||||
| 			await request(`/api/v1/services/deploy/${$page.params.type}`, $session, { | ||||
| 				body: { | ||||
| 					...$newWordpressService | ||||
| 				} | ||||
| 			}); | ||||
| 			if (browser) { | ||||
| 				toast.push( | ||||
| 					'Service deployment queued.<br><br><br>It could take 2-5 minutes to be ready, be patient and grab a coffee/tea!', | ||||
| 					{ duration: 4000 } | ||||
| 				); | ||||
| 				goto(`/dashboard/services`, { replaceState: true }); | ||||
| 			} | ||||
| 		} catch (error) { | ||||
| 			console.log(error); | ||||
| 			browser && toast.push('Oops something went wrong. See console.log.'); | ||||
| 		} finally { | ||||
| 			loading = false; | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| </script> | ||||
|  | ||||
| @@ -129,13 +151,15 @@ | ||||
| 			<span class="text-blue-500 px-2 capitalize">VSCode Server</span> | ||||
| 		{:else if $page.params.type === 'minio'} | ||||
| 			<span class="text-blue-500 px-2 capitalize">MinIO</span> | ||||
| 		{:else if $page.params.type === 'wordpress'} | ||||
| 			<span class="text-blue-500 px-2 capitalize">Wordpress</span> | ||||
| 		{/if} | ||||
| 	</div> | ||||
| </div> | ||||
| {#if loading} | ||||
| 	<Loading /> | ||||
| {:else if $page.params.type === 'plausible'} | ||||
| 	<div class="space-y-2 max-w-4xl mx-auto px-6 flex-col text-center" in:fade={{ duration: 100 }}> | ||||
| 	<div class="space-y-2 max-w-xl mx-auto px-6 flex-col text-center" in:fade={{ duration: 100 }}> | ||||
| 		<div class="grid grid-flow-row"> | ||||
| 			<label for="Domain" | ||||
| 				>Domain <TooltipInfo | ||||
| @@ -298,4 +322,188 @@ | ||||
| 			Deploy | ||||
| 		</button> | ||||
| 	</div> | ||||
| {:else if $page.params.type === 'wordpress'} | ||||
| 	<div class="space-y-2 max-w-xl mx-auto px-6 flex-col text-center" in:fade={{ duration: 100 }}> | ||||
| 		<div class="grid grid-flow-row pb-5"> | ||||
| 			<label for="Domain" | ||||
| 				>Domain <TooltipInfo | ||||
| 					position="right" | ||||
| 					label={`You could reach your Wordpress instance here.`} | ||||
| 				/></label | ||||
| 			> | ||||
| 			<input | ||||
| 				id="Domain" | ||||
| 				class:border-red-500={$newWordpressService.baseURL == null || $newWordpressService.baseURL == ''} | ||||
| 				bind:value={$newWordpressService.baseURL} | ||||
| 				placeholder="wordpress.coollabs.io" | ||||
| 			/> | ||||
| 		</div> | ||||
| 		<div class=""> | ||||
| 			<div class="px-4 sm:px-6"> | ||||
| 				<ul class="divide-y divide-warmGray-800"> | ||||
| 					<li class="py-4 flex items-center justify-between text-left"> | ||||
| 						<div class="flex flex-col"> | ||||
| 							<p class="text-base font-bold text-warmGray-100">Use remote MySQL database?</p> | ||||
| 							<p class="text-sm font-medium text-warmGray-400"> | ||||
| 								If not, Coolify will create a local database for you. | ||||
| 							</p> | ||||
| 						</div> | ||||
| 						<button | ||||
| 							type="button" | ||||
| 							on:click={() => ($newWordpressService.remoteDB = !$newWordpressService.remoteDB)} | ||||
| 							aria-pressed="true" | ||||
| 							class="relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200" | ||||
| 							class:bg-green-600={$newWordpressService.remoteDB} | ||||
| 							class:bg-warmGray-700={!$newWordpressService.remoteDB} | ||||
| 						> | ||||
| 							<span class="sr-only">Use setting</span> | ||||
| 							<span | ||||
| 								class="pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transition ease-in-out duration-200 transform" | ||||
| 								class:translate-x-5={$newWordpressService.remoteDB} | ||||
| 								class:translate-x-0={!$newWordpressService.remoteDB} | ||||
| 							> | ||||
| 								<span | ||||
| 									class=" ease-in duration-200 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity" | ||||
| 									class:opacity-0={$newWordpressService.remoteDB} | ||||
| 									class:opacity-100={!$newWordpressService.remoteDB} | ||||
| 									aria-hidden="true" | ||||
| 								> | ||||
| 									<svg class="bg-white h-3 w-3 text-red-600" fill="none" viewBox="0 0 12 12"> | ||||
| 										<path | ||||
| 											d="M4 8l2-2m0 0l2-2M6 6L4 4m2 2l2 2" | ||||
| 											stroke="currentColor" | ||||
| 											stroke-width="2" | ||||
| 											stroke-linecap="round" | ||||
| 											stroke-linejoin="round" | ||||
| 										/> | ||||
| 									</svg> | ||||
| 								</span> | ||||
| 								<span | ||||
| 									class="ease-out duration-100 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity" | ||||
| 									aria-hidden="true" | ||||
| 									class:opacity-100={$newWordpressService.remoteDB} | ||||
| 									class:opacity-0={!$newWordpressService.remoteDB} | ||||
| 								> | ||||
| 									<svg | ||||
| 										class="bg-white h-3 w-3 text-green-600" | ||||
| 										fill="currentColor" | ||||
| 										viewBox="0 0 12 12" | ||||
| 									> | ||||
| 										<path | ||||
| 											d="M3.707 5.293a1 1 0 00-1.414 1.414l1.414-1.414zM5 8l-.707.707a1 1 0 001.414 0L5 8zm4.707-3.293a1 1 0 00-1.414-1.414l1.414 1.414zm-7.414 2l2 2 1.414-1.414-2-2-1.414 1.414zm3.414 2l4-4-1.414-1.414-4 4 1.414 1.414z" | ||||
| 										/> | ||||
| 									</svg> | ||||
| 								</span> | ||||
| 							</span> | ||||
| 						</button> | ||||
| 					</li> | ||||
| 				</ul> | ||||
| 			</div> | ||||
| 			{#if $newWordpressService.remoteDB} | ||||
| 				<div class="grid grid-flow-row pb-5"> | ||||
| 					<label for="database.host" | ||||
| 						>DB Host <TooltipInfo | ||||
| 							position="right" | ||||
| 							label={`IP address of a remote Mysql instance.`} | ||||
| 						/></label | ||||
| 					> | ||||
| 					<input | ||||
| 						id="database.host" | ||||
| 						class:border-red-500={$newWordpressService.database.host == null || | ||||
| 							$newWordpressService.database.host == ''} | ||||
| 						bind:value={$newWordpressService.database.host} | ||||
| 						placeholder="10.10.10.10:3306" | ||||
| 					/> | ||||
| 				</div> | ||||
| 				<div class="grid grid-flow-row pb-5"> | ||||
| 					<label for="database.user" | ||||
| 						>DB User <TooltipInfo position="right" label={`Database user.`} /></label | ||||
| 					> | ||||
| 					<input | ||||
| 						id="database.user" | ||||
| 						class:border-red-500={$newWordpressService.database.user == null || | ||||
| 							$newWordpressService.database.user == ''} | ||||
| 						bind:value={$newWordpressService.database.user} | ||||
| 						placeholder="wordpressuser" | ||||
| 					/> | ||||
| 				</div> | ||||
| 				<div class="grid grid-flow-row pb-5"> | ||||
| 					<label for="database.password" | ||||
| 						>DB Password <TooltipInfo | ||||
| 							position="right" | ||||
| 							label={`Database password for the database user.`} | ||||
| 						/></label | ||||
| 					> | ||||
| 					<input | ||||
| 						id="database.password" | ||||
| 						class:border-red-500={$newWordpressService.database.password == null || | ||||
| 							$newWordpressService.database.password == ''} | ||||
| 						bind:value={$newWordpressService.database.password} | ||||
| 						placeholder="supersecretuserpasswordforwordpress" | ||||
| 					/> | ||||
| 				</div> | ||||
| 				<div class="grid grid-flow-row pb-5"> | ||||
| 					<label for="database.name" | ||||
| 						>DB Name<TooltipInfo | ||||
| 							position="right" | ||||
| 							label={`Database name`} | ||||
| 						/></label | ||||
| 					> | ||||
| 					<input | ||||
| 						id="database.name" | ||||
| 						class:border-red-500={$newWordpressService.database.name == null || | ||||
| 							$newWordpressService.database.name == ''} | ||||
| 						bind:value={$newWordpressService.database.name} | ||||
| 						placeholder="wordpress" | ||||
| 					/> | ||||
| 				</div> | ||||
| 				<div class="grid grid-flow-row pb-5"> | ||||
| 					<label for="database.tablePrefix" | ||||
| 						>DB Table Prefix <TooltipInfo | ||||
| 							position="right" | ||||
| 							label={`Table prefix for wordpress`} | ||||
| 						/></label | ||||
| 					> | ||||
| 					<input | ||||
| 						id="database.tablePrefix" | ||||
| 						class:border-red-500={$newWordpressService.database.tablePrefix == null || | ||||
| 							$newWordpressService.database.tablePrefix == ''} | ||||
| 						bind:value={$newWordpressService.database.tablePrefix} | ||||
| 						placeholder="wordpress" | ||||
| 					/> | ||||
| 				</div> | ||||
| 			{/if} | ||||
| 			<div class="grid grid-flow-row py-5"> | ||||
| 				<label for="wordpressExtraConfiguration" | ||||
| 					>Wordpress Configuration Extra <TooltipInfo | ||||
| 						position="right" | ||||
| 						label={`Database password for the database user.`} | ||||
| 					/></label | ||||
| 				> | ||||
| 				<textarea | ||||
| 					class="h-32" | ||||
| 					id="wordpressExtraConfiguration" | ||||
| 					bind:value={$newWordpressService.wordpressExtraConfiguration} | ||||
| 					placeholder="// Example Extra Configuration | ||||
| define('WP_ALLOW_MULTISITE', true ); | ||||
| define('MULTISITE', true); | ||||
| define('SUBDOMAIN_INSTALL', false);" | ||||
| 				/> | ||||
| 			</div> | ||||
| 		</div> | ||||
|  | ||||
| 		<button | ||||
| 			disabled={deployableWordpress} | ||||
| 			class:cursor-not-allowed={deployableWordpress} | ||||
| 			class:bg-blue-500={!deployableWordpress} | ||||
| 			class:hover:bg-blue-400={!deployableWordpress} | ||||
| 			class:hover:bg-transparent={deployableWordpress} | ||||
| 			class:text-warmGray-700={deployableWordpress} | ||||
| 			class:text-white={!deployableWordpress} | ||||
| 			class="button p-2 w-64 bg-blue-500 hover:bg-blue-400 text-white" | ||||
| 			on:click={deployWordpress} | ||||
| 		> | ||||
| 			Deploy | ||||
| 		</button> | ||||
| 	</div> | ||||
| {/if} | ||||
|   | ||||
| @@ -10,11 +10,11 @@ | ||||
| 		Select a service | ||||
| 	</div> | ||||
| </div> | ||||
| <div class="text-center space-y-2 max-w-4xl mx-auto px-6" in:fade={{ duration: 100 }}> | ||||
| <div class="text-center space-y-2 max-w-7xl mx-auto px-6" in:fade={{ duration: 100 }}> | ||||
| 	{#if $page.path === '/service/new'} | ||||
| 		<div class="flex justify-center space-x-4 font-bold pb-6"> | ||||
| 		<div class="flex justify-center  font-bold pb-6 flex-wrap"> | ||||
| 			<div | ||||
| 				class="text-center flex-col items-center cursor-pointer ease-in-out  hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-blue-500 p-2 rounded bg-warmGray-800 w-48" | ||||
| 				class="text-center flex-col items-center cursor-pointer ease-in-out hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-blue-500 p-2 m-2 rounded bg-warmGray-800 w-48" | ||||
| 				on:click={() => goto('/service/new/plausible')} | ||||
| 			> | ||||
| 				<img | ||||
| @@ -25,7 +25,7 @@ | ||||
| 				<div class="text-white">Plausible Analytics</div> | ||||
| 			</div> | ||||
| 			<div | ||||
| 				class="text-center flex-col items-center cursor-pointer ease-in-out  hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-white p-2 rounded bg-warmGray-800 w-48" | ||||
| 				class="text-center flex-col items-center cursor-pointer ease-in-out hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-blue-500 p-2 m-2 rounded bg-warmGray-800 w-48" | ||||
| 				on:click={() => goto('/service/new/nocodb')} | ||||
| 			> | ||||
| 				<img | ||||
| @@ -37,7 +37,7 @@ | ||||
| 				<div class="text-white">NocoDB</div> | ||||
| 			</div> | ||||
| 			<div | ||||
| 				class="text-center flex-col items-center cursor-pointer ease-in-out  hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-green-500 p-2 rounded bg-warmGray-800 w-48" | ||||
| 				class="text-center flex-col items-center cursor-pointer ease-in-out hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-blue-500 p-2 m-2 rounded bg-warmGray-800 w-48" | ||||
| 				on:click={() => goto('/service/new/code-server')} | ||||
| 			> | ||||
| 				<svg class="w-14 mx-auto pb-2" viewBox="0 0 128 128"> | ||||
| @@ -59,7 +59,7 @@ | ||||
| 				<div class="text-white">VSCode Server</div> | ||||
| 			</div> | ||||
| 			<div | ||||
| 				class="text-center flex-col items-center cursor-pointer ease-in-out  hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-red-500 p-2 rounded bg-warmGray-800 w-48" | ||||
| 				class="text-center flex-col items-center cursor-pointer ease-in-out hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-blue-500 p-2 m-2 rounded bg-warmGray-800 w-48" | ||||
| 				on:click={() => goto('/service/new/minio')} | ||||
| 			> | ||||
| 				<img | ||||
| @@ -70,6 +70,21 @@ | ||||
| 				<div class="flex-1" /> | ||||
| 				<div class="text-white">MinIO</div> | ||||
| 			</div> | ||||
| 			<div | ||||
| 			class="text-center flex-col items-center cursor-pointer ease-in-out hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-blue-500 p-2 m-2 rounded bg-warmGray-800 w-48" | ||||
| 			on:click={() => goto('/service/new/wordpress')} | ||||
| 		> | ||||
| 		<svg class="w-14 mx-auto pb-2" viewBox="0 0 128 128"> | ||||
| 			<path | ||||
| 				fill-rule="evenodd" | ||||
| 				clip-rule="evenodd" | ||||
| 				fill="white" | ||||
| 				d="M64.094 126.224c34.275-.052 62.021-27.933 62.021-62.325 0-33.833-27.618-61.697-60.613-62.286C30.85.995 1.894 29.113 1.885 63.21c-.01 35.079 27.612 63.064 62.209 63.014zM63.993 4.63c32.907-.011 59.126 26.725 59.116 60.28-.011 31.679-26.925 58.18-59.092 58.187-32.771.007-59.125-26.563-59.124-59.608.002-32.193 26.766-58.848 59.1-58.859zM39.157 35.896c.538 1.793-.968 2.417-2.569 2.542-1.685.13-3.369.257-5.325.406 6.456 19.234 12.815 38.183 19.325 57.573.464-.759.655-.973.739-1.223 3.574-10.682 7.168-21.357 10.651-32.069.318-.977.16-2.271-.188-3.275-1.843-5.32-4.051-10.524-5.667-15.908-1.105-3.686-2.571-6.071-6.928-5.644-.742.073-1.648-1.524-2.479-2.349 1.005-.6 2.003-1.704 3.017-1.719a849.593 849.593 0 0126.618.008c1.018.017 2.016 1.15 3.021 1.765-.88.804-1.639 2.01-2.668 2.321-1.651.498-3.482.404-5.458.58l19.349 57.56c2.931-9.736 5.658-18.676 8.31-27.639 2.366-8.001.956-15.473-3.322-22.52-1.286-2.119-2.866-4.175-3.595-6.486-.828-2.629-1.516-5.622-1.077-8.259.745-4.469 4.174-6.688 8.814-7.113C74.333.881 34.431 9.317 19.728 34.922c5.66-.261 11.064-.604 16.472-.678 1.022-.013 2.717.851 2.957 1.652zm10.117 77.971c-.118.345-.125.729-.218 1.302 10.943 3.034 21.675 2.815 32.659-.886l-16.78-45.96c-5.37 15.611-10.52 30.575-15.661 45.544zm-8.456-2.078l-25.281-69.35c-11.405 22.278-2.729 56.268 25.281 69.35zm76.428-44.562c.802-10.534-2.832-25.119-5.97-27.125-.35 3.875-.106 8.186-1.218 12.114-2.617 9.255-5.817 18.349-8.899 27.468-3.35 9.912-6.832 19.779-10.257 29.666 16.092-9.539 24.935-23.618 26.344-42.123z" | ||||
| 			/> | ||||
| 		</svg> | ||||
| 			<div class="flex-1" /> | ||||
| 			<div class="text-white">Wordpress</div> | ||||
| 		</div> | ||||
| 		</div> | ||||
| 	{/if} | ||||
| </div> | ||||
|   | ||||
| @@ -83,7 +83,7 @@ | ||||
| 							> | ||||
| 								<span class="sr-only">Use setting</span> | ||||
| 								<span | ||||
| 									class="pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transition ease-in-out duration-200" | ||||
| 									class="pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transition ease-in-out duration-200 transform" | ||||
| 									class:translate-x-5={settings?.allowRegistration} | ||||
| 									class:translate-x-0={!settings?.allowRegistration} | ||||
| 								> | ||||
| @@ -143,7 +143,7 @@ | ||||
| 							> | ||||
| 								<span class="sr-only">Use setting</span> | ||||
| 								<span | ||||
| 									class="pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transition ease-in-out duration-200" | ||||
| 									class="pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transition ease-in-out duration-200 transform" | ||||
| 									class:translate-x-5={settings?.sendErrors} | ||||
| 									class:translate-x-0={!settings?.sendErrors} | ||||
| 								> | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Andras Bacsai
					Andras Bacsai