Compare commits

...

6 Commits

Author SHA1 Message Date
Andras Bacsai
04a5b1bd4f Fix for PR 2021-06-07 21:49:52 +02:00
Andras Bacsai
31b3f58b2c v1.0.16 (#51) 2021-06-07 21:33:11 +02:00
Andras Bacsai
9c173d1de0 bump version 2021-05-22 15:33:13 +02:00
Andras Bacsai
e11b6d74ed v1.0.15 (#49)
Webhook quickfix.
2021-05-22 15:28:22 +02:00
Andras Bacsai
c7efe899fa v1.0.14 (#48)
# Features
- Basic Python support

# Fixes
- Fix default start command
2021-05-22 15:18:58 +02:00
Andras Bacsai
adcd68c1ab v1.0.13 (#46) 2021-05-16 21:54:44 +02:00
78 changed files with 1813 additions and 860 deletions

View File

@@ -1,4 +1,3 @@
# Coolify # Coolify
An open-source, hassle-free, self-hostable Heroku & Netlify alternative. An open-source, hassle-free, self-hostable Heroku & Netlify alternative.
@@ -7,8 +6,7 @@ An open-source, hassle-free, self-hostable Heroku & Netlify alternative.
[Small video](https://cdn.coollabs.io/assets/coolify/video/coolify.webm) [Small video](https://cdn.coollabs.io/assets/coolify/video/coolify.webm)
## Installation
## Installation
Installation is automated with the following command: Installation is automated with the following command:
@@ -16,49 +14,52 @@ Installation is automated with the following command:
/bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)" /bin/bash -c "$(curl -fsSL https://get.coollabs.io/coolify/install.sh)"
``` ```
## Features ## Features
You can deploy any of the following applications, databases and services easily. You can deploy any of the following applications, databases and services easily.
(constantly growing lists) (constantly growing lists)
### Applications ### Applications
With Github integration With Github integration
- Static sites - Static sites
- NodeJS - NodeJS
- VueJS - VueJS
- NuxtJS - NuxtJS
- NextJS
- React/Preact - React/Preact
- NextJS - NextJS
- Gatsby - Gatsby
- Svelte - Svelte
- PHP - PHP
- Rust - Rust
- or any custom dockerfile - or any custom dockerfile
### Databases ### Databases
- MongoDB - MongoDB
- MySQL - MySQL
- PostgreSQL - PostgreSQL
- CouchDB - CouchDB
- Redis
### Services ### Services
- [Plausible Analytics](https://plausible.io) - [Plausible Analytics](https://plausible.io)
## Support ## Support
- Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai) - Twitter: [@andrasbacsai](https://twitter.com/andrasbacsai)
- Telegram: [@andrasbacsai](https://t.me/andrasbacsai) - Telegram: [@andrasbacsai](https://t.me/andrasbacsai)
- Email: [andras@coollabs.io](mailto:andras@coollabs.io) - Email: [andras@coollabs.io](mailto:andras@coollabs.io)
- Discord: [Invitation](https://discord.com/invite/bvS3WhR) - Discord: [Invitation](https://discord.gg/xhBCC7eGKw)
## Roadmap ## Roadmap
[See the Roadmap here](https://github.com/coollabsio/coolify/projects/1) [See the Roadmap here](https://github.com/coollabsio/coolify/projects/1)
## License ## License
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Please see the [LICENSE](/LICENSE) file in our repository for the full text. This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Please see the [LICENSE](/LICENSE) file in our repository for the full text.

View File

@@ -1,7 +1,7 @@
{ {
"name": "coolify", "name": "coolify",
"description": "An open-source, hassle-free, self-hostable Heroku & Netlify alternative.", "description": "An open-source, hassle-free, self-hostable Heroku & Netlify alternative.",
"version": "1.0.12", "version": "1.0.16",
"license": "AGPL-3.0", "license": "AGPL-3.0",
"scripts": { "scripts": {
"dev:docker:start": "docker-compose -f docker-compose-dev.yml up -d", "dev:docker:start": "docker-compose -f docker-compose-dev.yml up -d",
@@ -14,8 +14,8 @@
"format": "prettier --write ." "format": "prettier --write ."
}, },
"devDependencies": { "devDependencies": {
"@sveltejs/adapter-node": "^1.0.0-next.20", "@sveltejs/adapter-node": "^1.0.0-next.24",
"@sveltejs/kit": "1.0.0-next.107", "@sveltejs/kit": "1.0.0-next.113",
"@types/dockerode": "^3.2.3", "@types/dockerode": "^3.2.3",
"@typescript-eslint/eslint-plugin": "^4.23.0", "@typescript-eslint/eslint-plugin": "^4.23.0",
"@typescript-eslint/parser": "^4.23.0", "@typescript-eslint/parser": "^4.23.0",
@@ -31,7 +31,7 @@
"prettier-plugin-svelte": "^2.3.0", "prettier-plugin-svelte": "^2.3.0",
"svelte": "^3.38.2", "svelte": "^3.38.2",
"svelte-preprocess": "^4.7.3", "svelte-preprocess": "^4.7.3",
"tailwindcss": "canary", "tailwindcss": "2.2.0-canary.8",
"tslib": "^2.2.0", "tslib": "^2.2.0",
"typescript": "^4.2.4", "typescript": "^4.2.4",
"vite": "^2.3.2" "vite": "^2.3.2"

450
pnpm-lock.yaml generated
View File

@@ -2,11 +2,11 @@ lockfileVersion: 5.3
specifiers: specifiers:
'@iarna/toml': ^2.2.5 '@iarna/toml': ^2.2.5
'@sveltejs/adapter-node': ^1.0.0-next.20 '@sveltejs/adapter-node': ^1.0.0-next.24
'@sveltejs/kit': 1.0.0-next.107 '@sveltejs/kit': 1.0.0-next.113
'@types/dockerode': ^3.2.3 '@types/dockerode': ^3.2.3
'@typescript-eslint/eslint-plugin': ^4.22.1 '@typescript-eslint/eslint-plugin': ^4.23.0
'@typescript-eslint/parser': ^4.22.1 '@typescript-eslint/parser': ^4.23.0
'@zerodevx/svelte-toast': ^0.3.0 '@zerodevx/svelte-toast': ^0.3.0
autoprefixer: ^10.2.5 autoprefixer: ^10.2.5
commander: ^7.2.0 commander: ^7.2.0
@@ -23,21 +23,21 @@ specifiers:
generate-password: ^1.6.0 generate-password: ^1.6.0
js-yaml: ^4.1.0 js-yaml: ^4.1.0
jsonwebtoken: ^8.5.1 jsonwebtoken: ^8.5.1
mongoose: ^5.12.7 mongoose: ^5.12.9
postcss: ^8.2.14 postcss: ^8.2.15
postcss-load-config: ^3.0.1 postcss-load-config: ^3.0.1
prettier: ~2.3.0 prettier: ~2.3.0
prettier-plugin-svelte: ^2.2.0 prettier-plugin-svelte: ^2.3.0
shelljs: ^0.8.4 shelljs: ^0.8.4
svelte: ^3.38.2 svelte: ^3.38.2
svelte-kit-cookie-session: ^0.4.3 svelte-kit-cookie-session: ^0.4.3
svelte-preprocess: ^4.7.3 svelte-preprocess: ^4.7.3
svelte-select: ^3.17.0 svelte-select: ^3.17.0
tailwindcss: canary tailwindcss: 2.2.0-canary.8
tslib: ^2.2.0 tslib: ^2.2.0
typescript: ^4.2.4 typescript: ^4.2.4
unique-names-generator: ^4.5.0 unique-names-generator: ^4.5.0
vite: ^2.2.4 vite: ^2.3.2
dependencies: dependencies:
'@iarna/toml': 2.2.5 '@iarna/toml': 2.2.5
@@ -51,34 +51,34 @@ dependencies:
generate-password: 1.6.0 generate-password: 1.6.0
js-yaml: 4.1.0 js-yaml: 4.1.0
jsonwebtoken: 8.5.1 jsonwebtoken: 8.5.1
mongoose: 5.12.7 mongoose: 5.12.9
shelljs: 0.8.4 shelljs: 0.8.4
svelte-kit-cookie-session: 0.4.3 svelte-kit-cookie-session: 0.4.3
svelte-select: 3.17.0 svelte-select: 3.17.0
unique-names-generator: 4.5.0 unique-names-generator: 4.5.0
devDependencies: devDependencies:
'@sveltejs/adapter-node': 1.0.0-next.20 '@sveltejs/adapter-node': 1.0.0-next.24
'@sveltejs/kit': 1.0.0-next.107_svelte@3.38.2 '@sveltejs/kit': 1.0.0-next.113_svelte@3.38.2
'@types/dockerode': 3.2.3 '@types/dockerode': 3.2.3
'@typescript-eslint/eslint-plugin': 4.22.1_b96c9280c6ac79e4dfcda76a733858e3 '@typescript-eslint/eslint-plugin': 4.23.0_7cfaee78cda985ff9bc3d8b5cb92a136
'@typescript-eslint/parser': 4.22.1_eslint@7.26.0+typescript@4.2.4 '@typescript-eslint/parser': 4.23.0_eslint@7.26.0+typescript@4.2.4
autoprefixer: 10.2.5_postcss@8.2.14 autoprefixer: 10.2.5_postcss@8.2.15
cssnano: 5.0.2_postcss@8.2.14 cssnano: 5.0.2_postcss@8.2.15
dotenv-extended: 2.9.0 dotenv-extended: 2.9.0
eslint: 7.26.0 eslint: 7.26.0
eslint-config-prettier: 8.3.0_eslint@7.26.0 eslint-config-prettier: 8.3.0_eslint@7.26.0
eslint-plugin-svelte3: 3.2.0_eslint@7.26.0+svelte@3.38.2 eslint-plugin-svelte3: 3.2.0_eslint@7.26.0+svelte@3.38.2
postcss: 8.2.14 postcss: 8.2.15
postcss-load-config: 3.0.1 postcss-load-config: 3.0.1
prettier: 2.3.0 prettier: 2.3.0
prettier-plugin-svelte: 2.2.0_prettier@2.3.0+svelte@3.38.2 prettier-plugin-svelte: 2.3.0_prettier@2.3.0+svelte@3.38.2
svelte: 3.38.2 svelte: 3.38.2
svelte-preprocess: 4.7.3_bc60392b3f6116f56fa9e4496adda117 svelte-preprocess: 4.7.3_5944bffd99aeae72e0daf7602e6be64e
tailwindcss: 2.2.0-canary.6_b46308bb95614802e1b6d1dce4e25ea6 tailwindcss: 2.2.0-canary.8_797cfc3ceddb49f111b184f6879a433f
tslib: 2.2.0 tslib: 2.2.0
typescript: 4.2.4 typescript: 4.2.4
vite: 2.2.4 vite: 2.3.2
packages: packages:
@@ -158,28 +158,28 @@ packages:
picomatch: 2.2.3 picomatch: 2.2.3
dev: true dev: true
/@sveltejs/adapter-node/1.0.0-next.20: /@sveltejs/adapter-node/1.0.0-next.24:
resolution: {integrity: sha512-ornNW289XN4TyY3tRXVGH38FxrqLsw1MHn4oq92T7U6EqnSbw/22waMZnPBQUxcj5GxZsXuuFjeBjn1BWt0x2A==} resolution: {integrity: sha512-33IbIYsIOq3LapSlW519oWuFvEHh8EHqU4FsOg1UZaF2EE5CkOh23SXjv0H9RT0iLCxvhifqiFN+vb/vlDRSWw==}
dev: true dev: true
/@sveltejs/kit/1.0.0-next.107_svelte@3.38.2: /@sveltejs/kit/1.0.0-next.113_svelte@3.38.2:
resolution: {integrity: sha512-HxomGMWbuWTU8lahk98hqIL/xjW6HzNTAvTZVMnnmlMHN1N8x7+abkz4Y09Zhu8lZkZpFuqBj2UlsgPW4uJXtw==} resolution: {integrity: sha512-X7feyAD9ZWQ79W5MXgF0t3JBsA+pTqCrnl2l8y8PNCP7pEJwizPCfmFAjwx7luz3OZUA2FTFBB7bqG7K1IUaYg==}
engines: {node: '>= 12.17.0'} engines: {node: ^12.20 || ^14.13.1 || >= 16}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
svelte: ^3.38.2 svelte: ^3.38.2
dependencies: dependencies:
'@sveltejs/vite-plugin-svelte': 1.0.0-next.10_svelte@3.38.2+vite@2.3.2 '@sveltejs/vite-plugin-svelte': 1.0.0-next.10_svelte@3.38.2+vite@2.3.6
cheap-watch: 1.0.3 cheap-watch: 1.0.3
sade: 1.7.4 sade: 1.7.4
svelte: 3.38.2 svelte: 3.38.2
vite: 2.3.2 vite: 2.3.6
transitivePeerDependencies: transitivePeerDependencies:
- rollup - rollup
- supports-color - supports-color
dev: true dev: true
/@sveltejs/vite-plugin-svelte/1.0.0-next.10_svelte@3.38.2+vite@2.3.2: /@sveltejs/vite-plugin-svelte/1.0.0-next.10_svelte@3.38.2+vite@2.3.6:
resolution: {integrity: sha512-ImvxbhPePm2hWNTKBSA3LHAYGwiEjHjvvgfPLXm4R87sfZ+BMXql9jBmDpzUC/URBLT4BB3Jxos/i523qkJBHg==} resolution: {integrity: sha512-ImvxbhPePm2hWNTKBSA3LHAYGwiEjHjvvgfPLXm4R87sfZ+BMXql9jBmDpzUC/URBLT4BB3Jxos/i523qkJBHg==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
peerDependencies: peerDependencies:
@@ -195,7 +195,7 @@ packages:
source-map: 0.7.3 source-map: 0.7.3
svelte: 3.38.2 svelte: 3.38.2
svelte-hmr: 0.14.3_svelte@3.38.2 svelte-hmr: 0.14.3_svelte@3.38.2
vite: 2.3.2 vite: 2.3.6
transitivePeerDependencies: transitivePeerDependencies:
- rollup - rollup
- supports-color - supports-color
@@ -246,8 +246,8 @@ packages:
'@types/node': 15.0.1 '@types/node': 15.0.1
dev: true dev: true
/@typescript-eslint/eslint-plugin/4.22.1_b96c9280c6ac79e4dfcda76a733858e3: /@typescript-eslint/eslint-plugin/4.23.0_7cfaee78cda985ff9bc3d8b5cb92a136:
resolution: {integrity: sha512-kVTAghWDDhsvQ602tHBc6WmQkdaYbkcTwZu+7l24jtJiYvm9l+/y/b2BZANEezxPDiX5MK2ZecE+9BFi/YJryw==} resolution: {integrity: sha512-tGK1y3KIvdsQEEgq6xNn1DjiFJtl+wn8JJQiETtCbdQxw1vzjXyAaIkEmO2l6Nq24iy3uZBMFQjZ6ECf1QdgGw==}
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
peerDependencies: peerDependencies:
'@typescript-eslint/parser': ^4.0.0 '@typescript-eslint/parser': ^4.0.0
@@ -257,9 +257,9 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/experimental-utils': 4.22.1_eslint@7.26.0+typescript@4.2.4 '@typescript-eslint/experimental-utils': 4.23.0_eslint@7.26.0+typescript@4.2.4
'@typescript-eslint/parser': 4.22.1_eslint@7.26.0+typescript@4.2.4 '@typescript-eslint/parser': 4.23.0_eslint@7.26.0+typescript@4.2.4
'@typescript-eslint/scope-manager': 4.22.1 '@typescript-eslint/scope-manager': 4.23.0
debug: 4.3.1 debug: 4.3.1
eslint: 7.26.0 eslint: 7.26.0
functional-red-black-tree: 1.0.1 functional-red-black-tree: 1.0.1
@@ -272,16 +272,16 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/experimental-utils/4.22.1_eslint@7.26.0+typescript@4.2.4: /@typescript-eslint/experimental-utils/4.23.0_eslint@7.26.0+typescript@4.2.4:
resolution: {integrity: sha512-svYlHecSMCQGDO2qN1v477ax/IDQwWhc7PRBiwAdAMJE7GXk5stF4Z9R/8wbRkuX/5e9dHqbIWxjeOjckK3wLQ==} resolution: {integrity: sha512-WAFNiTDnQfrF3Z2fQ05nmCgPsO5o790vOhmWKXbbYQTO9erE1/YsFot5/LnOUizLzU2eeuz6+U/81KV5/hFTGA==}
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
peerDependencies: peerDependencies:
eslint: '*' eslint: '*'
dependencies: dependencies:
'@types/json-schema': 7.0.7 '@types/json-schema': 7.0.7
'@typescript-eslint/scope-manager': 4.22.1 '@typescript-eslint/scope-manager': 4.23.0
'@typescript-eslint/types': 4.22.1 '@typescript-eslint/types': 4.23.0
'@typescript-eslint/typescript-estree': 4.22.1_typescript@4.2.4 '@typescript-eslint/typescript-estree': 4.23.0_typescript@4.2.4
eslint: 7.26.0 eslint: 7.26.0
eslint-scope: 5.1.1 eslint-scope: 5.1.1
eslint-utils: 2.1.0 eslint-utils: 2.1.0
@@ -290,8 +290,8 @@ packages:
- typescript - typescript
dev: true dev: true
/@typescript-eslint/parser/4.22.1_eslint@7.26.0+typescript@4.2.4: /@typescript-eslint/parser/4.23.0_eslint@7.26.0+typescript@4.2.4:
resolution: {integrity: sha512-l+sUJFInWhuMxA6rtirzjooh8cM/AATAe3amvIkqKFeMzkn85V+eLzb1RyuXkHak4dLfYzOmF6DXPyflJvjQnw==} resolution: {integrity: sha512-wsvjksHBMOqySy/Pi2Q6UuIuHYbgAMwLczRl4YanEPKW5KVxI9ZzDYh3B5DtcZPQTGRWFJrfcbJ6L01Leybwug==}
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
peerDependencies: peerDependencies:
eslint: ^5.0.0 || ^6.0.0 || ^7.0.0 eslint: ^5.0.0 || ^6.0.0 || ^7.0.0
@@ -300,9 +300,9 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/scope-manager': 4.22.1 '@typescript-eslint/scope-manager': 4.23.0
'@typescript-eslint/types': 4.22.1 '@typescript-eslint/types': 4.23.0
'@typescript-eslint/typescript-estree': 4.22.1_typescript@4.2.4 '@typescript-eslint/typescript-estree': 4.23.0_typescript@4.2.4
debug: 4.3.1 debug: 4.3.1
eslint: 7.26.0 eslint: 7.26.0
typescript: 4.2.4 typescript: 4.2.4
@@ -310,21 +310,21 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/scope-manager/4.22.1: /@typescript-eslint/scope-manager/4.23.0:
resolution: {integrity: sha512-d5bAiPBiessSmNi8Amq/RuLslvcumxLmyhf1/Xa9IuaoFJ0YtshlJKxhlbY7l2JdEk3wS0EnmnfeJWSvADOe0g==} resolution: {integrity: sha512-ZZ21PCFxPhI3n0wuqEJK9omkw51wi2bmeKJvlRZPH5YFkcawKOuRMQMnI8mH6Vo0/DoHSeZJnHiIx84LmVQY+w==}
engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1}
dependencies: dependencies:
'@typescript-eslint/types': 4.22.1 '@typescript-eslint/types': 4.23.0
'@typescript-eslint/visitor-keys': 4.22.1 '@typescript-eslint/visitor-keys': 4.23.0
dev: true dev: true
/@typescript-eslint/types/4.22.1: /@typescript-eslint/types/4.23.0:
resolution: {integrity: sha512-2HTkbkdAeI3OOcWbqA8hWf/7z9c6gkmnWNGz0dKSLYLWywUlkOAQ2XcjhlKLj5xBFDf8FgAOF5aQbnLRvgNbCw==} resolution: {integrity: sha512-oqkNWyG2SLS7uTWLZf6Sr7Dm02gA5yxiz1RP87tvsmDsguVATdpVguHr4HoGOcFOpCvx9vtCSCyQUGfzq28YCw==}
engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1}
dev: true dev: true
/@typescript-eslint/typescript-estree/4.22.1_typescript@4.2.4: /@typescript-eslint/typescript-estree/4.23.0_typescript@4.2.4:
resolution: {integrity: sha512-p3We0pAPacT+onSGM+sPR+M9CblVqdA9F1JEdIqRVlxK5Qth4ochXQgIyb9daBomyQKAXbygxp1aXQRV0GC79A==} resolution: {integrity: sha512-5Sty6zPEVZF5fbvrZczfmLCOcby3sfrSPu30qKoY1U3mca5/jvU5cwsPb/CO6Q3ByRjixTMIVsDkqwIxCf/dMw==}
engines: {node: ^10.12.0 || >=12.0.0} engines: {node: ^10.12.0 || >=12.0.0}
peerDependencies: peerDependencies:
typescript: '*' typescript: '*'
@@ -332,8 +332,8 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/types': 4.22.1 '@typescript-eslint/types': 4.23.0
'@typescript-eslint/visitor-keys': 4.22.1 '@typescript-eslint/visitor-keys': 4.23.0
debug: 4.3.1 debug: 4.3.1
globby: 11.0.3 globby: 11.0.3
is-glob: 4.0.1 is-glob: 4.0.1
@@ -344,11 +344,11 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/visitor-keys/4.22.1: /@typescript-eslint/visitor-keys/4.23.0:
resolution: {integrity: sha512-WPkOrIRm+WCLZxXQHCi+WG8T2MMTUFR70rWjdWYddLT7cEfb2P4a3O/J2U1FBVsSFTocXLCoXWY6MZGejeStvQ==} resolution: {integrity: sha512-5PNe5cmX9pSifit0H+nPoQBXdbNzi5tOEec+3riK+ku4e3er37pKxMKDH5Ct5Y4fhWxcD4spnlYjxi9vXbSpwg==}
engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1} engines: {node: ^8.10.0 || ^10.13.0 || >=11.10.1}
dependencies: dependencies:
'@typescript-eslint/types': 4.22.1 '@typescript-eslint/types': 4.23.0
eslint-visitor-keys: 2.0.0 eslint-visitor-keys: 2.0.0
dev: true dev: true
@@ -463,18 +463,13 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/at-least-node/1.0.0:
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
engines: {node: '>= 4.0.0'}
dev: true
/auto-parse/1.8.0: /auto-parse/1.8.0:
resolution: {integrity: sha512-Uri4uC+K5cSi5hjM4snFrqPrjqUpwxeSW5EMTPvN7Ju3PlDzmXXDr5tjdzxPvvwgT3J7bmMDJ3Rm625nbrc72A==} resolution: {integrity: sha512-Uri4uC+K5cSi5hjM4snFrqPrjqUpwxeSW5EMTPvN7Ju3PlDzmXXDr5tjdzxPvvwgT3J7bmMDJ3Rm625nbrc72A==}
dependencies: dependencies:
typpy: 2.3.11 typpy: 2.3.11
dev: true dev: true
/autoprefixer/10.2.5_postcss@8.2.14: /autoprefixer/10.2.5_postcss@8.2.15:
resolution: {integrity: sha512-7H4AJZXvSsn62SqZyJCP+1AWwOuoYpUfK6ot9vm0e87XD6mT8lDywc9D9OTJPMULyGcvmIxzTAMeG2Cc+YX+fA==} resolution: {integrity: sha512-7H4AJZXvSsn62SqZyJCP+1AWwOuoYpUfK6ot9vm0e87XD6mT8lDywc9D9OTJPMULyGcvmIxzTAMeG2Cc+YX+fA==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
hasBin: true hasBin: true
@@ -486,7 +481,7 @@ packages:
colorette: 1.2.2 colorette: 1.2.2
fraction.js: 4.0.13 fraction.js: 4.0.13
normalize-range: 0.1.2 normalize-range: 0.1.2
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
@@ -738,13 +733,13 @@ packages:
resolution: {integrity: sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==} resolution: {integrity: sha512-/loXYOch1qU1biStIFsHH8SxTmOseh1IJqFvy8IujXOm1h+QjUdDhkzOrR5HG8K8mlxREj0yfi8ewCHx0eMxzA==}
dev: true dev: true
/css-declaration-sorter/6.0.0_postcss@8.2.14: /css-declaration-sorter/6.0.0_postcss@8.2.15:
resolution: {integrity: sha512-S0TE4E0ha5+tBHdLWPc5n+S8E4dFBS5xScPvgHkLNZwWvX4ISoFGhGeerLC9uS1cKA/sC+K2wHq6qEbcagT/fg==} resolution: {integrity: sha512-S0TE4E0ha5+tBHdLWPc5n+S8E4dFBS5xScPvgHkLNZwWvX4ISoFGhGeerLC9uS1cKA/sC+K2wHq6qEbcagT/fg==}
engines: {node: '>= 10'} engines: {node: '>= 10'}
peerDependencies: peerDependencies:
postcss: ^8.0.9 postcss: ^8.0.9
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
timsort: 0.3.0 timsort: 0.3.0
dev: true dev: true
@@ -781,63 +776,63 @@ packages:
hasBin: true hasBin: true
dev: true dev: true
/cssnano-preset-default/5.0.1_postcss@8.2.14: /cssnano-preset-default/5.0.1_postcss@8.2.15:
resolution: {integrity: sha512-cfmfThYODGqhpQKDq9H0MTAqkMvZ3dGbOUTBKw0xWZiIycMqHid22LsJXJl4r1qX4qzDeKxcSyQ/Xb5Mu3Z//Q==} resolution: {integrity: sha512-cfmfThYODGqhpQKDq9H0MTAqkMvZ3dGbOUTBKw0xWZiIycMqHid22LsJXJl4r1qX4qzDeKxcSyQ/Xb5Mu3Z//Q==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
css-declaration-sorter: 6.0.0_postcss@8.2.14 css-declaration-sorter: 6.0.0_postcss@8.2.15
cssnano-utils: 2.0.0_postcss@8.2.14 cssnano-utils: 2.0.0_postcss@8.2.15
postcss: 8.2.14 postcss: 8.2.15
postcss-calc: 8.0.0_postcss@8.2.14 postcss-calc: 8.0.0_postcss@8.2.15
postcss-colormin: 5.0.0_postcss@8.2.14 postcss-colormin: 5.0.0_postcss@8.2.15
postcss-convert-values: 5.0.0_postcss@8.2.14 postcss-convert-values: 5.0.0_postcss@8.2.15
postcss-discard-comments: 5.0.0_postcss@8.2.14 postcss-discard-comments: 5.0.0_postcss@8.2.15
postcss-discard-duplicates: 5.0.0_postcss@8.2.14 postcss-discard-duplicates: 5.0.0_postcss@8.2.15
postcss-discard-empty: 5.0.0_postcss@8.2.14 postcss-discard-empty: 5.0.0_postcss@8.2.15
postcss-discard-overridden: 5.0.0_postcss@8.2.14 postcss-discard-overridden: 5.0.0_postcss@8.2.15
postcss-merge-longhand: 5.0.1_postcss@8.2.14 postcss-merge-longhand: 5.0.1_postcss@8.2.15
postcss-merge-rules: 5.0.0_postcss@8.2.14 postcss-merge-rules: 5.0.0_postcss@8.2.15
postcss-minify-font-values: 5.0.0_postcss@8.2.14 postcss-minify-font-values: 5.0.0_postcss@8.2.15
postcss-minify-gradients: 5.0.0_postcss@8.2.14 postcss-minify-gradients: 5.0.0_postcss@8.2.15
postcss-minify-params: 5.0.0_postcss@8.2.14 postcss-minify-params: 5.0.0_postcss@8.2.15
postcss-minify-selectors: 5.0.0_postcss@8.2.14 postcss-minify-selectors: 5.0.0_postcss@8.2.15
postcss-normalize-charset: 5.0.0_postcss@8.2.14 postcss-normalize-charset: 5.0.0_postcss@8.2.15
postcss-normalize-display-values: 5.0.0_postcss@8.2.14 postcss-normalize-display-values: 5.0.0_postcss@8.2.15
postcss-normalize-positions: 5.0.0_postcss@8.2.14 postcss-normalize-positions: 5.0.0_postcss@8.2.15
postcss-normalize-repeat-style: 5.0.0_postcss@8.2.14 postcss-normalize-repeat-style: 5.0.0_postcss@8.2.15
postcss-normalize-string: 5.0.0_postcss@8.2.14 postcss-normalize-string: 5.0.0_postcss@8.2.15
postcss-normalize-timing-functions: 5.0.0_postcss@8.2.14 postcss-normalize-timing-functions: 5.0.0_postcss@8.2.15
postcss-normalize-unicode: 5.0.0_postcss@8.2.14 postcss-normalize-unicode: 5.0.0_postcss@8.2.15
postcss-normalize-url: 5.0.0_postcss@8.2.14 postcss-normalize-url: 5.0.0_postcss@8.2.15
postcss-normalize-whitespace: 5.0.0_postcss@8.2.14 postcss-normalize-whitespace: 5.0.0_postcss@8.2.15
postcss-ordered-values: 5.0.0_postcss@8.2.14 postcss-ordered-values: 5.0.0_postcss@8.2.15
postcss-reduce-initial: 5.0.0_postcss@8.2.14 postcss-reduce-initial: 5.0.0_postcss@8.2.15
postcss-reduce-transforms: 5.0.0_postcss@8.2.14 postcss-reduce-transforms: 5.0.0_postcss@8.2.15
postcss-svgo: 5.0.0_postcss@8.2.14 postcss-svgo: 5.0.0_postcss@8.2.15
postcss-unique-selectors: 5.0.0_postcss@8.2.14 postcss-unique-selectors: 5.0.0_postcss@8.2.15
dev: true dev: true
/cssnano-utils/2.0.0_postcss@8.2.14: /cssnano-utils/2.0.0_postcss@8.2.15:
resolution: {integrity: sha512-xvxmTszdrvSyTACdPe8VU5J6p4sm3egpgw54dILvNqt5eBUv6TFjACLhSxtRuEsxYrgy8uDy269YjScO5aKbGA==} resolution: {integrity: sha512-xvxmTszdrvSyTACdPe8VU5J6p4sm3egpgw54dILvNqt5eBUv6TFjACLhSxtRuEsxYrgy8uDy269YjScO5aKbGA==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
dev: true dev: true
/cssnano/5.0.2_postcss@8.2.14: /cssnano/5.0.2_postcss@8.2.15:
resolution: {integrity: sha512-8JK3EnPsjQsULme9/e5M2hF564f/480hwsdcHvQ7ZtAIMfQ1O3SCfs+b8Mjf5KJxhYApyRshR2QSovEJi2K72Q==} resolution: {integrity: sha512-8JK3EnPsjQsULme9/e5M2hF564f/480hwsdcHvQ7ZtAIMfQ1O3SCfs+b8Mjf5KJxhYApyRshR2QSovEJi2K72Q==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
cosmiconfig: 7.0.0 cosmiconfig: 7.0.0
cssnano-preset-default: 5.0.1_postcss@8.2.14 cssnano-preset-default: 5.0.1_postcss@8.2.15
is-resolvable: 1.1.0 is-resolvable: 1.1.0
postcss: 8.2.14 postcss: 8.2.15
dev: true dev: true
/csso/4.2.0: /csso/4.2.0:
@@ -1049,8 +1044,8 @@ packages:
requiresBuild: true requiresBuild: true
dev: true dev: true
/esbuild/0.9.7: /esbuild/0.12.6:
resolution: {integrity: sha512-VtUf6aQ89VTmMLKrWHYG50uByMF4JQlVysb8dmg6cOgW8JnFCipmz7p+HNBl+RR3LLCuBxFGVauAe2wfnF9bLg==} resolution: {integrity: sha512-RDvVLvAjsq/kIZJoneMiUOH7EE7t2QaW7T3Q7EdQij14+bZbDq5sndb0tTanmHIFSqZVMBMMyqzVHkS3dJobeA==}
hasBin: true hasBin: true
requiresBuild: true requiresBuild: true
dev: true dev: true
@@ -1268,11 +1263,10 @@ packages:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
dev: false dev: false
/fs-extra/9.1.0: /fs-extra/10.0.0:
resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} resolution: {integrity: sha512-C5owb14u9eJwizKGdchcDUQeFtlSHHthBk8pbX9Vc1PFZrLombudjDnNns88aYslCyF6IY5SUw3Roz6xShcEIQ==}
engines: {node: '>=10'} engines: {node: '>=12'}
dependencies: dependencies:
at-least-node: 1.0.0
graceful-fs: 4.2.6 graceful-fs: 4.2.6
jsonfile: 6.1.0 jsonfile: 6.1.0
universalify: 2.0.0 universalify: 2.0.0
@@ -1801,23 +1795,23 @@ packages:
saslprep: 1.0.3 saslprep: 1.0.3
dev: false dev: false
/mongoose-legacy-pluralize/1.0.2_mongoose@5.12.7: /mongoose-legacy-pluralize/1.0.2_mongoose@5.12.9:
resolution: {integrity: sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==} resolution: {integrity: sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ==}
peerDependencies: peerDependencies:
mongoose: '*' mongoose: '*'
dependencies: dependencies:
mongoose: 5.12.7 mongoose: 5.12.9
dev: false dev: false
/mongoose/5.12.7: /mongoose/5.12.9:
resolution: {integrity: sha512-BniNwACn7uflK2h+M3juvyLH5nn9JDFgnB5KE2EwWFwSrRyhSpPnCtanRKJW3OtMCJyPccMIjtGZxHNW7JfnIw==} resolution: {integrity: sha512-ZSDjW15DmUbHQcZ2PqoXsJeYnpYipISi6QJH/XHR9dcSg3IRNCa86apcTptBux03/YBPiSZlKNYUNDx7iuMWoA==}
engines: {node: '>=4.0.0'} engines: {node: '>=4.0.0'}
dependencies: dependencies:
'@types/mongodb': 3.6.12 '@types/mongodb': 3.6.12
bson: 1.1.6 bson: 1.1.6
kareem: 2.3.2 kareem: 2.3.2
mongodb: 3.6.6 mongodb: 3.6.6
mongoose-legacy-pluralize: 1.0.2_mongoose@5.12.7 mongoose-legacy-pluralize: 1.0.2_mongoose@5.12.9
mpath: 0.8.3 mpath: 0.8.3
mquery: 3.2.5 mquery: 3.2.5
ms: 2.1.2 ms: 2.1.2
@@ -1866,12 +1860,6 @@ packages:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
dev: false dev: false
/nanoid/3.1.22:
resolution: {integrity: sha512-/2ZUaJX2ANuLtTvqTlgqBQNJoQO398KyJgZloL0PZkC0dpysjncRUPsFe3DUPzz/y3h+u7C46np8RMuvF3jsSQ==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: true
/nanoid/3.1.23: /nanoid/3.1.23:
resolution: {integrity: sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==} resolution: {integrity: sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
@@ -1917,11 +1905,6 @@ packages:
boolbase: 1.0.0 boolbase: 1.0.0
dev: true dev: true
/object-assign/4.1.1:
resolution: {integrity: sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=}
engines: {node: '>=0.10.0'}
dev: true
/object-hash/2.1.1: /object-hash/2.1.1:
resolution: {integrity: sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ==} resolution: {integrity: sha512-VOJmgmS+7wvXf8CjbQmimtCnEx3IAoLxI3fp2fbWehxrWBcAQFbk+vcwb6vzR0VZv/eNCJ/27j151ZTwqW/JeQ==}
engines: {node: '>= 6'} engines: {node: '>= 6'}
@@ -1998,17 +1981,17 @@ packages:
engines: {node: '>=8.6'} engines: {node: '>=8.6'}
dev: true dev: true
/postcss-calc/8.0.0_postcss@8.2.14: /postcss-calc/8.0.0_postcss@8.2.15:
resolution: {integrity: sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g==} resolution: {integrity: sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g==}
peerDependencies: peerDependencies:
postcss: ^8.2.2 postcss: ^8.2.2
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
postcss-selector-parser: 6.0.5 postcss-selector-parser: 6.0.5
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-colormin/5.0.0_postcss@8.2.14: /postcss-colormin/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-Yt84+5V6CgS/AhK7d7MA58vG8dSZ7+ytlRtWLaQhag3HXOncTfmYpuUOX4cDoXjvLfw1sHRCHMiBjYhc35CymQ==} resolution: {integrity: sha512-Yt84+5V6CgS/AhK7d7MA58vG8dSZ7+ytlRtWLaQhag3HXOncTfmYpuUOX4cDoXjvLfw1sHRCHMiBjYhc35CymQ==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
@@ -2016,63 +1999,54 @@ packages:
dependencies: dependencies:
browserslist: 4.16.6 browserslist: 4.16.6
color: 3.1.3 color: 3.1.3
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-convert-values/5.0.0_postcss@8.2.14: /postcss-convert-values/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-V5kmYm4xoBAjNs+eHY/6XzXJkkGeg4kwNf2ocfqhLb1WBPEa4oaSmoi1fnVO7Dkblqvus9h+AenDvhCKUCK7uQ==} resolution: {integrity: sha512-V5kmYm4xoBAjNs+eHY/6XzXJkkGeg4kwNf2ocfqhLb1WBPEa4oaSmoi1fnVO7Dkblqvus9h+AenDvhCKUCK7uQ==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-discard-comments/5.0.0_postcss@8.2.14: /postcss-discard-comments/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-Umig6Gxs8m20RihiXY6QkePd6mp4FxkA1Dg+f/Kd6uw0gEMfKRjDeQOyFkLibexbJJGHpE3lrN/Q0R9SMrUMbQ==} resolution: {integrity: sha512-Umig6Gxs8m20RihiXY6QkePd6mp4FxkA1Dg+f/Kd6uw0gEMfKRjDeQOyFkLibexbJJGHpE3lrN/Q0R9SMrUMbQ==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
dev: true dev: true
/postcss-discard-duplicates/5.0.0_postcss@8.2.14: /postcss-discard-duplicates/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-vEJJ+Y3pFUnO1FyCBA6PSisGjHtnphL3V6GsNvkASq/VkP3OX5/No5RYXXLxHa2QegStNzg6HYrYdo71uR4caQ==} resolution: {integrity: sha512-vEJJ+Y3pFUnO1FyCBA6PSisGjHtnphL3V6GsNvkASq/VkP3OX5/No5RYXXLxHa2QegStNzg6HYrYdo71uR4caQ==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
dev: true dev: true
/postcss-discard-empty/5.0.0_postcss@8.2.14: /postcss-discard-empty/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-+wigy099Y1xZxG36WG5L1f2zeH1oicntkJEW4TDIqKKDO2g9XVB3OhoiHTu08rDEjLnbcab4rw0BAccwi2VjiQ==} resolution: {integrity: sha512-+wigy099Y1xZxG36WG5L1f2zeH1oicntkJEW4TDIqKKDO2g9XVB3OhoiHTu08rDEjLnbcab4rw0BAccwi2VjiQ==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
dev: true dev: true
/postcss-discard-overridden/5.0.0_postcss@8.2.14: /postcss-discard-overridden/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-hybnScTaZM2iEA6kzVQ6Spozy7kVdLw+lGw8hftLlBEzt93uzXoltkYp9u0tI8xbfhxDLTOOzHsHQCkYdmzRUg==} resolution: {integrity: sha512-hybnScTaZM2iEA6kzVQ6Spozy7kVdLw+lGw8hftLlBEzt93uzXoltkYp9u0tI8xbfhxDLTOOzHsHQCkYdmzRUg==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
dev: true
/postcss-functions/3.0.0:
resolution: {integrity: sha1-DpTQFERwCkgd4g3k1V+yZAVkJQ4=}
dependencies:
glob: 7.1.7
object-assign: 4.1.1
postcss: 6.0.23
postcss-value-parser: 3.3.1
dev: true dev: true
/postcss-js/3.0.3: /postcss-js/3.0.3:
@@ -2091,19 +2065,19 @@ packages:
import-cwd: 3.0.0 import-cwd: 3.0.0
dev: true dev: true
/postcss-merge-longhand/5.0.1_postcss@8.2.14: /postcss-merge-longhand/5.0.1_postcss@8.2.15:
resolution: {integrity: sha512-H1RO8le5deFGumQzuhJjuL0bIXPRysa+w7xtk5KrHe38oiaSS9ksPXDo24+IOS3SETPhip0J5+1uCOW+ALs3Yw==} resolution: {integrity: sha512-H1RO8le5deFGumQzuhJjuL0bIXPRysa+w7xtk5KrHe38oiaSS9ksPXDo24+IOS3SETPhip0J5+1uCOW+ALs3Yw==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
css-color-names: 1.0.1 css-color-names: 1.0.1
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
stylehacks: 5.0.0_postcss@8.2.14 stylehacks: 5.0.0_postcss@8.2.15
dev: true dev: true
/postcss-merge-rules/5.0.0_postcss@8.2.14: /postcss-merge-rules/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-TfsXbKjNYCGfUPEXGIGPySnMiJbdS+3gcVeV8gwmJP4RajyKZHW8E0FYDL1WmggTj3hi+m+WUCAvqRpX2ut4Kg==} resolution: {integrity: sha512-TfsXbKjNYCGfUPEXGIGPySnMiJbdS+3gcVeV8gwmJP4RajyKZHW8E0FYDL1WmggTj3hi+m+WUCAvqRpX2ut4Kg==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
@@ -2111,35 +2085,35 @@ packages:
dependencies: dependencies:
browserslist: 4.16.6 browserslist: 4.16.6
caniuse-api: 3.0.0 caniuse-api: 3.0.0
cssnano-utils: 2.0.0_postcss@8.2.14 cssnano-utils: 2.0.0_postcss@8.2.15
postcss: 8.2.14 postcss: 8.2.15
postcss-selector-parser: 6.0.5 postcss-selector-parser: 6.0.5
vendors: 1.0.4 vendors: 1.0.4
dev: true dev: true
/postcss-minify-font-values/5.0.0_postcss@8.2.14: /postcss-minify-font-values/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-zi2JhFaMOcIaNxhndX5uhsqSY1rexKDp23wV8EOmC9XERqzLbHsoRye3aYF716Zm+hkcR4loqKDt8LZlmihwAg==} resolution: {integrity: sha512-zi2JhFaMOcIaNxhndX5uhsqSY1rexKDp23wV8EOmC9XERqzLbHsoRye3aYF716Zm+hkcR4loqKDt8LZlmihwAg==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-minify-gradients/5.0.0_postcss@8.2.14: /postcss-minify-gradients/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-/jPtNgs6JySMwgsE5dPOq8a2xEopWTW3RyqoB9fLqxgR+mDUNLSi7joKd+N1z7FXWgVkc4l/dEBMXHgNAaUbvg==} resolution: {integrity: sha512-/jPtNgs6JySMwgsE5dPOq8a2xEopWTW3RyqoB9fLqxgR+mDUNLSi7joKd+N1z7FXWgVkc4l/dEBMXHgNAaUbvg==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
cssnano-utils: 2.0.0_postcss@8.2.14 cssnano-utils: 2.0.0_postcss@8.2.15
is-color-stop: 1.1.0 is-color-stop: 1.1.0
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-minify-params/5.0.0_postcss@8.2.14: /postcss-minify-params/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-KvZYIxTPBVKjdd+XgObq9A+Sfv8lMkXTpbZTsjhr42XbfWIeLaTItMlygsDWfjArEc3muUfDaUFgNSeDiJ5jug==} resolution: {integrity: sha512-KvZYIxTPBVKjdd+XgObq9A+Sfv8lMkXTpbZTsjhr42XbfWIeLaTItMlygsDWfjArEc3muUfDaUFgNSeDiJ5jug==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
@@ -2147,107 +2121,107 @@ packages:
dependencies: dependencies:
alphanum-sort: 1.0.2 alphanum-sort: 1.0.2
browserslist: 4.16.6 browserslist: 4.16.6
cssnano-utils: 2.0.0_postcss@8.2.14 cssnano-utils: 2.0.0_postcss@8.2.15
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
uniqs: 2.0.0 uniqs: 2.0.0
dev: true dev: true
/postcss-minify-selectors/5.0.0_postcss@8.2.14: /postcss-minify-selectors/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-cEM0O0eWwFIvmo6nfB0lH0vO/XFwgqIvymODbfPXZ1gTA3i76FKnb7TGUrEpiTxaXH6tgYQ6DcTHwRiRS+YQLQ==} resolution: {integrity: sha512-cEM0O0eWwFIvmo6nfB0lH0vO/XFwgqIvymODbfPXZ1gTA3i76FKnb7TGUrEpiTxaXH6tgYQ6DcTHwRiRS+YQLQ==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
alphanum-sort: 1.0.2 alphanum-sort: 1.0.2
postcss: 8.2.14 postcss: 8.2.15
postcss-selector-parser: 3.1.2 postcss-selector-parser: 3.1.2
dev: true dev: true
/postcss-nested/5.0.5_postcss@8.2.14: /postcss-nested/5.0.5_postcss@8.2.15:
resolution: {integrity: sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==} resolution: {integrity: sha512-GSRXYz5bccobpTzLQZXOnSOfKl6TwVr5CyAQJUPub4nuRJSOECK5AqurxVgmtxP48p0Kc/ndY/YyS1yqldX0Ew==}
engines: {node: '>=10.0'} engines: {node: '>=10.0'}
peerDependencies: peerDependencies:
postcss: ^8.1.13 postcss: ^8.1.13
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
postcss-selector-parser: 6.0.6 postcss-selector-parser: 6.0.6
dev: true dev: true
/postcss-normalize-charset/5.0.0_postcss@8.2.14: /postcss-normalize-charset/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-pqsCkgo9KmQP0ew6DqSA+uP9YN6EfsW20pQ3JU5JoQge09Z6Too4qU0TNDsTNWuEaP8SWsMp+19l15210MsDZQ==} resolution: {integrity: sha512-pqsCkgo9KmQP0ew6DqSA+uP9YN6EfsW20pQ3JU5JoQge09Z6Too4qU0TNDsTNWuEaP8SWsMp+19l15210MsDZQ==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
dev: true dev: true
/postcss-normalize-display-values/5.0.0_postcss@8.2.14: /postcss-normalize-display-values/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-t4f2d//gH1f7Ns0Jq3eNdnWuPT7TeLuISZ6RQx4j8gpl5XrhkdshdNcOnlrEK48YU6Tcb6jqK7dorME3N4oOGA==} resolution: {integrity: sha512-t4f2d//gH1f7Ns0Jq3eNdnWuPT7TeLuISZ6RQx4j8gpl5XrhkdshdNcOnlrEK48YU6Tcb6jqK7dorME3N4oOGA==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
cssnano-utils: 2.0.0_postcss@8.2.14 cssnano-utils: 2.0.0_postcss@8.2.15
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-normalize-positions/5.0.0_postcss@8.2.14: /postcss-normalize-positions/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-0o6/qU5ky74X/eWYj/tv4iiKCm3YqJnrhmVADpIMNXxzFZywsSQxl8F7cKs8jQEtF3VrJBgcDHTexZy1zgDoYg==} resolution: {integrity: sha512-0o6/qU5ky74X/eWYj/tv4iiKCm3YqJnrhmVADpIMNXxzFZywsSQxl8F7cKs8jQEtF3VrJBgcDHTexZy1zgDoYg==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-normalize-repeat-style/5.0.0_postcss@8.2.14: /postcss-normalize-repeat-style/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-KRT14JbrXKcFMYuc4q7lh8lvv8u22wLyMrq+UpHKLtbx2H/LOjvWXYdoDxmNrrrJzomAWL+ViEXr48/IhSUJnQ==} resolution: {integrity: sha512-KRT14JbrXKcFMYuc4q7lh8lvv8u22wLyMrq+UpHKLtbx2H/LOjvWXYdoDxmNrrrJzomAWL+ViEXr48/IhSUJnQ==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
cssnano-utils: 2.0.0_postcss@8.2.14 cssnano-utils: 2.0.0_postcss@8.2.15
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-normalize-string/5.0.0_postcss@8.2.14: /postcss-normalize-string/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-wSO4pf7GNcDZpmelREWYADF1+XZWrAcbFLQCOqoE92ZwYgaP/RLumkUTaamEzdT2YKRZAH8eLLKGWotU/7FNPw==} resolution: {integrity: sha512-wSO4pf7GNcDZpmelREWYADF1+XZWrAcbFLQCOqoE92ZwYgaP/RLumkUTaamEzdT2YKRZAH8eLLKGWotU/7FNPw==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-normalize-timing-functions/5.0.0_postcss@8.2.14: /postcss-normalize-timing-functions/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-TwPaDX+wl9wO3MUm23lzGmOzGCGKnpk+rSDgzB2INpakD5dgWR3L6bJq1P1LQYzBAvz8fRIj2NWdnZdV4EV98Q==} resolution: {integrity: sha512-TwPaDX+wl9wO3MUm23lzGmOzGCGKnpk+rSDgzB2INpakD5dgWR3L6bJq1P1LQYzBAvz8fRIj2NWdnZdV4EV98Q==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
cssnano-utils: 2.0.0_postcss@8.2.14 cssnano-utils: 2.0.0_postcss@8.2.15
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-normalize-unicode/5.0.0_postcss@8.2.14: /postcss-normalize-unicode/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-2CpVoz/67rXU5s9tsPZDxG1YGS9OFHwoY9gsLAzrURrCxTAb0H7Vp87/62LvVPgRWTa5ZmvgmqTp2rL8tlm72A==} resolution: {integrity: sha512-2CpVoz/67rXU5s9tsPZDxG1YGS9OFHwoY9gsLAzrURrCxTAb0H7Vp87/62LvVPgRWTa5ZmvgmqTp2rL8tlm72A==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
browserslist: 4.16.6 browserslist: 4.16.6
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-normalize-url/5.0.0_postcss@8.2.14: /postcss-normalize-url/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-ICDaGFBqLgA3dlrCIRuhblLl80D13YtgEV9NJPTYJtgR72vu61KgxAHv+z/lKMs1EbwfSQa3ALjOFLSmXiE34A==} resolution: {integrity: sha512-ICDaGFBqLgA3dlrCIRuhblLl80D13YtgEV9NJPTYJtgR72vu61KgxAHv+z/lKMs1EbwfSQa3ALjOFLSmXiE34A==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
@@ -2255,32 +2229,32 @@ packages:
dependencies: dependencies:
is-absolute-url: 3.0.3 is-absolute-url: 3.0.3
normalize-url: 4.5.0 normalize-url: 4.5.0
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-normalize-whitespace/5.0.0_postcss@8.2.14: /postcss-normalize-whitespace/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-KRnxQvQAVkJfaeXSz7JlnD9nBN9sFZF9lrk9452Q2uRoqrRSkinqifF8Iex7wZGei2DZVG/qpmDFDmRvbNAOGA==} resolution: {integrity: sha512-KRnxQvQAVkJfaeXSz7JlnD9nBN9sFZF9lrk9452Q2uRoqrRSkinqifF8Iex7wZGei2DZVG/qpmDFDmRvbNAOGA==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-ordered-values/5.0.0_postcss@8.2.14: /postcss-ordered-values/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-dPr+SRObiHueCIc4IUaG0aOGQmYkuNu50wQvdXTGKy+rzi2mjmPsbeDsheLk5WPb9Zyf2tp8E+I+h40cnivm6g==} resolution: {integrity: sha512-dPr+SRObiHueCIc4IUaG0aOGQmYkuNu50wQvdXTGKy+rzi2mjmPsbeDsheLk5WPb9Zyf2tp8E+I+h40cnivm6g==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
cssnano-utils: 2.0.0_postcss@8.2.14 cssnano-utils: 2.0.0_postcss@8.2.15
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
/postcss-reduce-initial/5.0.0_postcss@8.2.14: /postcss-reduce-initial/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-wR6pXUaFbSMG1oCKx8pKVA+rnSXCHlca5jMrlmkmif+uig0HNUTV9oGN5kjKsM3mATQAldv2PF9Tbl2vqLFjnA==} resolution: {integrity: sha512-wR6pXUaFbSMG1oCKx8pKVA+rnSXCHlca5jMrlmkmif+uig0HNUTV9oGN5kjKsM3mATQAldv2PF9Tbl2vqLFjnA==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
@@ -2288,17 +2262,17 @@ packages:
dependencies: dependencies:
browserslist: 4.16.6 browserslist: 4.16.6
caniuse-api: 3.0.0 caniuse-api: 3.0.0
postcss: 8.2.14 postcss: 8.2.15
dev: true dev: true
/postcss-reduce-transforms/5.0.0_postcss@8.2.14: /postcss-reduce-transforms/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-iHdGODW4YzM3WjVecBhPQt6fpJC4lGQZxJKjkBNHpp2b8dzmvj0ogKThqya+IRodQEFzjfXgYeESkf172FH5Lw==} resolution: {integrity: sha512-iHdGODW4YzM3WjVecBhPQt6fpJC4lGQZxJKjkBNHpp2b8dzmvj0ogKThqya+IRodQEFzjfXgYeESkf172FH5Lw==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
cssnano-utils: 2.0.0_postcss@8.2.14 cssnano-utils: 2.0.0_postcss@8.2.15
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
dev: true dev: true
@@ -2327,25 +2301,25 @@ packages:
util-deprecate: 1.0.2 util-deprecate: 1.0.2
dev: true dev: true
/postcss-svgo/5.0.0_postcss@8.2.14: /postcss-svgo/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-M3/VS4sFI1Yp9g0bPL+xzzCNz5iLdRUztoFaugMit5a8sMfkVzzhwqbsOlD8IFFymCdJDmXmh31waYHWw1K4BA==} resolution: {integrity: sha512-M3/VS4sFI1Yp9g0bPL+xzzCNz5iLdRUztoFaugMit5a8sMfkVzzhwqbsOlD8IFFymCdJDmXmh31waYHWw1K4BA==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
postcss: 8.2.14 postcss: 8.2.15
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
svgo: 2.3.0 svgo: 2.3.0
dev: true dev: true
/postcss-unique-selectors/5.0.0_postcss@8.2.14: /postcss-unique-selectors/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-o9l4pF8SRn7aCMTmzb/kNv/kjV7wPZpZ8Nlb1Gq8v/Qvw969K1wanz1RVA0ehHzWe9+wHXaC2DvZlak/gdMJ5w==} resolution: {integrity: sha512-o9l4pF8SRn7aCMTmzb/kNv/kjV7wPZpZ8Nlb1Gq8v/Qvw969K1wanz1RVA0ehHzWe9+wHXaC2DvZlak/gdMJ5w==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
alphanum-sort: 1.0.2 alphanum-sort: 1.0.2
postcss: 8.2.14 postcss: 8.2.15
postcss-selector-parser: 6.0.5 postcss-selector-parser: 6.0.5
uniqs: 2.0.0 uniqs: 2.0.0
dev: true dev: true
@@ -2358,33 +2332,6 @@ packages:
resolution: {integrity: sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==} resolution: {integrity: sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==}
dev: true dev: true
/postcss/6.0.23:
resolution: {integrity: sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==}
engines: {node: '>=4.0.0'}
dependencies:
chalk: 2.4.2
source-map: 0.6.1
supports-color: 5.5.0
dev: true
/postcss/8.2.13:
resolution: {integrity: sha512-FCE5xLH+hjbzRdpbRb1IMCvPv9yZx2QnDarBEYSN0N0HYk+TcXsEhwdFcFb+SRWOKzKGErhIEbBK2ogyLdTtfQ==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
colorette: 1.2.2
nanoid: 3.1.22
source-map: 0.6.1
dev: true
/postcss/8.2.14:
resolution: {integrity: sha512-+jD0ZijcvyCqPQo/m/CW0UcARpdFylq04of+Q7RKX6f/Tu+dvpUI/9Sp81+i6/vJThnOBX09Quw0ZLOVwpzX3w==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
colorette: 1.2.2
nanoid: 3.1.22
source-map: 0.6.1
dev: true
/postcss/8.2.15: /postcss/8.2.15:
resolution: {integrity: sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q==} resolution: {integrity: sha512-2zO3b26eJD/8rb106Qu2o7Qgg52ND5HPjcyQiK2B98O388h43A448LCslC0dI2P97wCAQRJsFvwTRcXxTKds+Q==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@@ -2399,8 +2346,8 @@ packages:
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
dev: true dev: true
/prettier-plugin-svelte/2.2.0_prettier@2.3.0+svelte@3.38.2: /prettier-plugin-svelte/2.3.0_prettier@2.3.0+svelte@3.38.2:
resolution: {integrity: sha512-Xdmqgr71tAuMqqzNCK52/v94g/Yv7V7lz+nmbO9NEA+9ol15VV3uUHOfydMNOo3SWvFaVlBcp947ebEaMWqVfQ==} resolution: {integrity: sha512-HTzXvSq7lWFuLsSaxYOUkGkVNCl3RrSjDCOgQjkBX5FQGmWjL8o3IFACSGhjPMMfWKADpapAr0zdbBWkND9mqw==}
peerDependencies: peerDependencies:
prettier: ^1.16.4 || ^2.0.0 prettier: ^1.16.4 || ^2.0.0
svelte: ^3.2.0 svelte: ^3.2.0
@@ -2760,14 +2707,14 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/stylehacks/5.0.0_postcss@8.2.14: /stylehacks/5.0.0_postcss@8.2.15:
resolution: {integrity: sha512-QOWm6XivDLb+fqffTZP8jrmPmPITVChl2KCY2R05nsCWwLi3VGhCdVc3IVGNwd1zzTt1jPd67zIKjpQfxzQZeA==} resolution: {integrity: sha512-QOWm6XivDLb+fqffTZP8jrmPmPITVChl2KCY2R05nsCWwLi3VGhCdVc3IVGNwd1zzTt1jPd67zIKjpQfxzQZeA==}
engines: {node: ^10 || ^12 || >=14.0} engines: {node: ^10 || ^12 || >=14.0}
peerDependencies: peerDependencies:
postcss: ^8.2.1 postcss: ^8.2.1
dependencies: dependencies:
browserslist: 4.16.6 browserslist: 4.16.6
postcss: 8.2.14 postcss: 8.2.15
postcss-selector-parser: 6.0.5 postcss-selector-parser: 6.0.5
dev: true dev: true
@@ -2799,7 +2746,7 @@ packages:
salteen: 1.0.0 salteen: 1.0.0
dev: false dev: false
/svelte-preprocess/4.7.3_bc60392b3f6116f56fa9e4496adda117: /svelte-preprocess/4.7.3_5944bffd99aeae72e0daf7602e6be64e:
resolution: {integrity: sha512-Zx1/xLeGOIBlZMGPRCaXtlMe4ZA0faato5Dc3CosEqwu75MIEPuOstdkH6cy+RYTUYynoxzNaDxkPX4DbrPwRA==} resolution: {integrity: sha512-Zx1/xLeGOIBlZMGPRCaXtlMe4ZA0faato5Dc3CosEqwu75MIEPuOstdkH6cy+RYTUYynoxzNaDxkPX4DbrPwRA==}
engines: {node: '>= 9.11.2'} engines: {node: '>= 9.11.2'}
requiresBuild: true requiresBuild: true
@@ -2843,7 +2790,7 @@ packages:
'@types/pug': 2.0.4 '@types/pug': 2.0.4
'@types/sass': 1.16.0 '@types/sass': 1.16.0
detect-indent: 6.0.0 detect-indent: 6.0.0
postcss: 8.2.14 postcss: 8.2.15
postcss-load-config: 3.0.1 postcss-load-config: 3.0.1
strip-indent: 3.0.0 strip-indent: 3.0.0
svelte: 3.38.2 svelte: 3.38.2
@@ -2886,8 +2833,8 @@ packages:
strip-ansi: 6.0.0 strip-ansi: 6.0.0
dev: true dev: true
/tailwindcss/2.2.0-canary.6_b46308bb95614802e1b6d1dce4e25ea6: /tailwindcss/2.2.0-canary.8_797cfc3ceddb49f111b184f6879a433f:
resolution: {integrity: sha512-gXpjyw8g4Z565tG6yDyzh2VH3mX0riWXeujg/L6Ph4z/t5ztnqRF2QGXVT5toZYyl7LP02YTrfVqxzDY5vuJDw==} resolution: {integrity: sha512-IVgtzCS3HTq4lsK793isUIhbylCoG/WrarYpK8cAlbjOt5TwLJnhfp2GKjfzK3201eM4YawTjuRI+wesEy8XGg==}
engines: {node: '>=12.13.0'} engines: {node: '>=12.13.0'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@@ -2895,7 +2842,7 @@ packages:
postcss: ^8.0.9 postcss: ^8.0.9
dependencies: dependencies:
'@fullhuman/postcss-purgecss': 3.1.3 '@fullhuman/postcss-purgecss': 3.1.3
autoprefixer: 10.2.5_postcss@8.2.14 autoprefixer: 10.2.5_postcss@8.2.15
bytes: 3.1.0 bytes: 3.1.0
chalk: 4.1.1 chalk: 4.1.1
chokidar: 3.5.1 chokidar: 3.5.1
@@ -2904,7 +2851,7 @@ packages:
didyoumean: 1.2.1 didyoumean: 1.2.1
dlv: 1.1.3 dlv: 1.1.3
fast-glob: 3.2.5 fast-glob: 3.2.5
fs-extra: 9.1.0 fs-extra: 10.0.0
html-tags: 3.1.0 html-tags: 3.1.0
lodash: 4.17.21 lodash: 4.17.21
lodash.topath: 4.5.2 lodash.topath: 4.5.2
@@ -2913,10 +2860,9 @@ packages:
normalize-path: 3.0.0 normalize-path: 3.0.0
object-hash: 2.1.1 object-hash: 2.1.1
parse-glob: 3.0.4 parse-glob: 3.0.4
postcss: 8.2.14 postcss: 8.2.15
postcss-functions: 3.0.0
postcss-js: 3.0.3 postcss-js: 3.0.3
postcss-nested: 5.0.5_postcss@8.2.14 postcss-nested: 5.0.5_postcss@8.2.15
postcss-selector-parser: 6.0.6 postcss-selector-parser: 6.0.6
postcss-value-parser: 4.1.0 postcss-value-parser: 4.1.0
pretty-hrtime: 1.0.3 pretty-hrtime: 1.0.3
@@ -3046,19 +2992,6 @@ packages:
resolution: {integrity: sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==} resolution: {integrity: sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==}
dev: true dev: true
/vite/2.2.4:
resolution: {integrity: sha512-vnIwSNci+phFMp6krhy+FbYzKL0R67Sdt9mVZ96S27AewrApSJjTqncJcalk8sf60BgcbW4+1C6DFIWkxquO9g==}
engines: {node: '>=12.0.0'}
hasBin: true
dependencies:
esbuild: 0.9.7
postcss: 8.2.13
resolve: 1.20.0
rollup: 2.46.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/vite/2.3.2: /vite/2.3.2:
resolution: {integrity: sha512-QhLdOompDrfkyryCNTts9HE+eJhvhN9ibKNJ5Q8DpQai+6nOsuIlaveZNg67e1O/2QaWqXeBo82eHnAs1De2bQ==} resolution: {integrity: sha512-QhLdOompDrfkyryCNTts9HE+eJhvhN9ibKNJ5Q8DpQai+6nOsuIlaveZNg67e1O/2QaWqXeBo82eHnAs1De2bQ==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@@ -3072,6 +3005,19 @@ packages:
fsevents: 2.3.2 fsevents: 2.3.2
dev: true dev: true
/vite/2.3.6:
resolution: {integrity: sha512-fsEpNKDHgh3Sn66JH06ZnUBnIgUVUtw6ucDhlOj1CEqxIkymU25yv1/kWDPlIjyYHnalr0cN6V+zzUJ+fmWHYw==}
engines: {node: '>=12.0.0'}
hasBin: true
dependencies:
esbuild: 0.12.6
postcss: 8.2.15
resolve: 1.20.0
rollup: 2.46.0
optionalDependencies:
fsevents: 2.3.2
dev: true
/which/2.0.2: /which/2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'} engines: {node: '>= 8'}

View File

@@ -1,87 +1,163 @@
<script> <script>
import { application } from '$store'; import { VITE_GITHUB_APP_NAME } from '$lib/consts';
import { application, isPullRequestPermissionsGranted } from '$store';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import TooltipInfo from '$components/TooltipInfo.svelte'; import TooltipInfo from '$components/TooltipInfo.svelte';
import { request } from '$lib/request';
import { page, session } from '$app/stores';
import { browser } from '$app/env';
let domainInput; let domainInput;
let loading = {
previewDeployment: false
};
let howToActivate = false;
const buildpacks = { const buildpacks = {
static: { static: {
port: { port: {
active: false, active: false,
number: 80 number: 80
}, },
build: true build: true,
start: false
}, },
nodejs: { nodejs: {
port: { port: {
active: true, active: true,
number: 3000 number: 3000
}, },
build: true build: true,
start: true
},
nestjs: {
port: {
active: true,
number: 3000
},
build: true,
start: true
}, },
vuejs: { vuejs: {
port: { port: {
active: false, active: false,
number: 80 number: 80
}, },
build: true build: true,
start: false
}, },
nuxtjs: { nuxtjs: {
port: { port: {
active: true, active: true,
number: 3000 number: 3000
}, },
build: true build: true,
start: true
}, },
react: { react: {
port: { port: {
active: false, active: false,
number: 80 number: 80
}, },
build: true build: true,
start: false
}, },
nextjs: { nextjs: {
port: { port: {
active: true, active: true,
number: 3000 number: 3000
}, },
build: true build: true,
start: true
}, },
gatsby: { gatsby: {
port: { port: {
active: true, active: true,
number: 3000 number: 3000
}, },
build: true build: true,
start: false
}, },
svelte: { svelte: {
port: { port: {
active: false, active: false,
number: 80 number: 80
}, },
build: true build: true,
start: false
}, },
php: { php: {
port: { port: {
active: false, active: false,
number: 80 number: 80
}, },
build: false build: false,
start: false
}, },
rust: { rust: {
port: { port: {
active: true, active: true,
number: 3000 number: 3000
}, },
build: false build: false,
start: false
}, },
docker: { docker: {
port: { port: {
active: true, active: true,
number: 3000 number: 3000
}, },
build: false build: false,
start: false
},
python: {
port: {
active: true,
number: 4000
},
build: false,
start: false,
custom: true
} }
}; };
async function setPreviewDeployment() {
if ($application.general.isPreviewDeploymentEnabled) {
const result = window.confirm(
"Are you sure? It will delete all PR deployments - it's NOT reversible!"
);
if (result) {
loading.previewDeployment = true;
$application.general.isPreviewDeploymentEnabled =
!$application.general.isPreviewDeploymentEnabled;
if ($page.path !== '/application/new') {
const config = await request(`/api/v1/application/config/previewDeployment`, $session, {
body: {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch,
isPreviewDeploymentEnabled: $application.general.isPreviewDeploymentEnabled
}
});
}
loading.previewDeployment = false;
}
} else {
loading.previewDeployment = true;
$application.general.isPreviewDeploymentEnabled =
!$application.general.isPreviewDeploymentEnabled;
$application.general.pullRequest = 0;
if ($page.path !== '/application/new') {
const config = await request(`/api/v1/application/config/previewDeployment`, $session, {
body: {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch,
isPreviewDeploymentEnabled: $application.general.isPreviewDeploymentEnabled
}
});
}
loading.previewDeployment = false;
}
}
function selectBuildPack(event) { function selectBuildPack(event) {
if (event.target.innerText === 'React/Preact') { if (event.target.innerText === 'React/Preact') {
$application.build.pack = 'react'; $application.build.pack = 'react';
@@ -89,9 +165,38 @@
$application.build.pack = event.target.innerText.replace(/\./g, '').toLowerCase(); $application.build.pack = event.target.innerText.replace(/\./g, '').toLowerCase();
} }
} }
async function openGithub() {
if (browser) {
const config = await request(`https://api.github.com/apps/${VITE_GITHUB_APP_NAME}`, $session);
let url = `https://github.com/settings/apps/${VITE_GITHUB_APP_NAME}/permissions`;
if (config.owner.type === 'Organization') {
url = `https://github.com/organizations/${config.owner.login}/settings/apps/${VITE_GITHUB_APP_NAME}/permissions`;
}
const left = screen.width / 2 - 1020 / 2;
const top = screen.height / 2 - 618 / 2;
const newWindow = open(
url,
'Permission Update',
'resizable=1, scrollbars=1, fullscreen=1, height=1000, width=1220,top=' +
top +
', left=' +
left +
', toolbar=0, menubar=0, status=0'
);
const timer = setInterval(async () => {
if (newWindow?.closed) {
clearInterval(timer);
location.reload();
}
}, 100);
}
}
onMount(() => { onMount(() => {
if(!$application.publish.domain) domainInput.focus(); if (!$application.publish.domain) domainInput.focus();
}); });
</script> </script>
<div> <div>
@@ -178,6 +283,14 @@
> >
Rust Rust
</div> </div>
<div
class={$application.build.pack === 'nestjs'
? 'buildpack bg-red-500'
: 'buildpack hover:border-red-500'}
on:click={selectBuildPack}
>
NestJS
</div>
<div <div
class={$application.build.pack === 'docker' class={$application.build.pack === 'docker'
? 'buildpack bg-purple-500' ? 'buildpack bg-purple-500'
@@ -186,10 +299,139 @@
> >
Docker Docker
</div> </div>
<div
class={$application.build.pack === 'python'
? 'buildpack bg-green-500'
: 'buildpack hover:border-green-500'}
on:click={selectBuildPack}
>
Python
</div>
</div> </div>
</div> </div>
<div class="text-2xl font-bold border-gradient w-52">General settings</div> <div class="text-2xl font-bold border-gradient w-52">General settings</div>
<div class="grid grid-cols-1 max-w-2xl md:mx-auto mx-6 justify-center items-center pt-10"> <div class="grid grid-cols-1 max-w-2xl md:mx-auto mx-6 justify-center items-center pt-10">
<div>
<ul class="divide-y divide-warmGray-800">
<li class="pb-6 flex items-center justify-between">
<div class="flex flex-col">
<p class="text-base font-bold text-warmGray-100">Preview deployments</p>
<p class="text-sm font-medium text-warmGray-400">
PR's will be deployed so you could review them easily
</p>
</div>
{#if $isPullRequestPermissionsGranted}
<div
class="relative"
class:animate-wiggle={loading.previewDeployment}
class:opacity-25={loading.previewDeployment}
>
<button
type="button"
disabled={loading.previewDeployment}
on:click={setPreviewDeployment}
aria-pressed="false"
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={$application.general.isPreviewDeploymentEnabled}
class:bg-warmGray-700={!$application.general.isPreviewDeploymentEnabled}
class:cursor-not-allowed={loading.previewDeployment}
>
<span class="sr-only">Use setting</span>
<span
class="pointer-events-none relative inline-block h-5 w-5 rounded-full bg-white shadow transform transition ease-in-out duration-200"
class:translate-x-5={$application.general.isPreviewDeploymentEnabled}
class:translate-x-0={!$application.general.isPreviewDeploymentEnabled}
>
<span
class=" ease-in duration-200 absolute inset-0 h-full w-full flex items-center justify-center transition-opacity"
class:opacity-0={$application.general.isPreviewDeploymentEnabled}
class:opacity-100={!$application.general.isPreviewDeploymentEnabled}
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={$application.general.isPreviewDeploymentEnabled}
class:opacity-0={!$application.general.isPreviewDeploymentEnabled}
>
<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>
{#if loading.previewDeployment}
<div class="absolute left-0 bottom-0 -mb-4 -ml-2 text-xs font-bold">{$application.general.isPreviewDeploymentEnabled ? 'Enabling...' : 'Disabling...' }</div>
{/if}
</div>
{:else}
<div class="relative">
{#if !howToActivate}
<button
class="button py-2 px-2 bg-warmGray-800 hover:bg-warmGray-700 text-white"
on:click={() => (howToActivate = !howToActivate)}>How to active this?</button
>
{:else}
<button
class="button py-2 px-2 bg-green-600 hover:bg-green-500 text-white"
on:click={openGithub}>Open Github</button
>
{/if}
{#if howToActivate}
<div class="absolute right-0 w-64 z-10">
<div class="bg-warmGray-800 p-4 my-2 rounded text-white">
<div
class="absolute right-0 top-0 p-2 my-3 mx-1 text-xs font-bold cursor-pointer hover:bg-warmGray-700"
on:click={() => (howToActivate = false)}
>
X
</div>
<p class="text-sm font-medium text-warmGray-400">
You need to add <span class="text-white">two new permissions</span> to your GitHub
App:
</p>
<br />
<p class="text-sm font-medium text-warmGray-400">
1. In <span class="text-white">Repository permissions</span>, add
<span class="text-white">Read-only</span>
access to <span class="text-white">Pull requests</span>.
</p>
<br />
<p class="text-sm font-medium text-warmGray-400">
2. In <span class="text-white">Subscribe to events</span> section,
<span class="text-white"> check Pull request</span> field.
</p>
<br />
<p class="text-sm font-medium text-warmGray-400">
3. You <span class="text-white">receive an email</span> where you need to
<span class="text-white">accept the new permissions</span>.
</p>
</div>
</div>
{/if}
</div>
{/if}
</li>
</ul>
</div>
<div class="grid grid-flow-col gap-2 items-center pb-6"> <div class="grid grid-flow-col gap-2 items-center pb-6">
<div class="grid grid-flow-row"> <div class="grid grid-flow-row">
<label for="Domain" class="">Domain</label> <label for="Domain" class="">Domain</label>
@@ -253,53 +495,92 @@
</div> </div>
</div> </div>
</div> </div>
<div <div class="text-2xl font-bold w-40 border-gradient">Commands</div>
class="text-2xl font-bold w-40"
class:border-gradient={buildpacks[$application.build.pack].build}
class:text-warmGray-800={!buildpacks[$application.build.pack].build}
>
Commands
</div>
<div class=" max-w-2xl md:mx-auto mx-6 justify-center items-center pt-10 pb-32"> <div class=" max-w-2xl md:mx-auto mx-6 justify-center items-center pt-10 pb-32">
<div class="grid grid-flow-col gap-2 items-center"> <div class="grid grid-flow-col gap-2 items-center">
<div class="grid grid-flow-row"> <div class="grid grid-flow-row">
<label {#if $application.build.pack === 'python'}
for="installCommand" <label for="ModulePackageName"
class:text-warmGray-800={!buildpacks[$application.build.pack].build} >Module/Package Name<TooltipInfo
>Install Command <TooltipInfo label="The module/package name to start (eg: the entry filename [main], without the py extension. See gunicorn.org for more details)"
label="Command to run for installing dependencies. eg: yarn install." />
/> </label>
</label>
<input <input
class="mb-6" class="mb-6"
class:bg-warmGray-900={!buildpacks[$application.build.pack].build} id="ModulePackageName"
class:text-warmGray-900={!buildpacks[$application.build.pack].build} bind:value={$application.build.command.python.module}
class:placeholder-warmGray-800={!buildpacks[$application.build.pack].build} placeholder="main"
class:hover:bg-warmGray-900={!buildpacks[$application.build.pack].build} />
class:cursor-not-allowed={!buildpacks[$application.build.pack].build} <label for="ApplicationInstance"
id="installCommand" >Application Instance<TooltipInfo
bind:value={$application.build.command.installation} label="The instance name (the main function name. See gunicorn.org for more details)"
placeholder="eg: yarn install" />
/> </label>
<label
for="buildCommand" <input
class:text-warmGray-800={!buildpacks[$application.build.pack].build} class="mb-6"
>Build Command <TooltipInfo id="ApplicationInstance"
label="Command to run for building your application. If empty, no build phase initiated in the deploy process." bind:value={$application.build.command.python.instance}
/></label placeholder="app"
> />
<input {:else}
class="mb-6" <label
class:bg-warmGray-900={!buildpacks[$application.build.pack].build} for="installCommand"
class:text-warmGray-900={!buildpacks[$application.build.pack].build} class:text-warmGray-800={!buildpacks[$application.build.pack].build}
class:placeholder-warmGray-800={!buildpacks[$application.build.pack].build} >Install Command <TooltipInfo
class:hover:bg-warmGray-900={!buildpacks[$application.build.pack].build} label="Command to run for installing dependencies. eg: yarn install"
class:cursor-not-allowed={!buildpacks[$application.build.pack].build} />
id="buildCommand" </label>
bind:value={$application.build.command.build}
placeholder="eg: yarn build" <input
/> class="mb-6"
class:bg-warmGray-900={!buildpacks[$application.build.pack].build}
class:text-warmGray-900={!buildpacks[$application.build.pack].build}
class:placeholder-warmGray-800={!buildpacks[$application.build.pack].build}
class:hover:bg-warmGray-900={!buildpacks[$application.build.pack].build}
class:cursor-not-allowed={!buildpacks[$application.build.pack].build}
id="installCommand"
bind:value={$application.build.command.installation}
placeholder="eg: yarn install"
/>
<label
for="buildCommand"
class:text-warmGray-800={!buildpacks[$application.build.pack].build}
>Build Command <TooltipInfo
label="Command to run for building your application. If empty, no build phase initiated in the deploy process."
/></label
>
<input
class="mb-6"
class:bg-warmGray-900={!buildpacks[$application.build.pack].build}
class:text-warmGray-900={!buildpacks[$application.build.pack].build}
class:placeholder-warmGray-800={!buildpacks[$application.build.pack].build}
class:hover:bg-warmGray-900={!buildpacks[$application.build.pack].build}
class:cursor-not-allowed={!buildpacks[$application.build.pack].build}
id="buildCommand"
bind:value={$application.build.command.build}
placeholder="eg: yarn build"
/>
<label
for="startCommand"
class:text-warmGray-800={!buildpacks[$application.build.pack].start}
>Start Command <TooltipInfo
label="Command to start the application. eg: yarn start"
/></label
>
<input
class="mb-6"
class:bg-warmGray-900={!buildpacks[$application.build.pack].start}
class:text-warmGray-900={!buildpacks[$application.build.pack].start}
class:placeholder-warmGray-800={!buildpacks[$application.build.pack].start}
class:hover:bg-warmGray-900={!buildpacks[$application.build.pack].start}
class:cursor-not-allowed={!buildpacks[$application.build.pack].start}
id="startcommand"
bind:value={$application.build.command.start}
placeholder="eg: yarn start"
/>
{/if}
</div> </div>
</div> </div>
</div> </div>
@@ -309,4 +590,5 @@
.buildpack { .buildpack {
@apply px-6 py-2 mx-2 my-2 bg-warmGray-800 w-48 ease-in-out transform hover:scale-105 text-center rounded border-2 border-transparent border-dashed cursor-pointer transition duration-100; @apply px-6 py-2 mx-2 my-2 bg-warmGray-800 w-48 ease-in-out transform hover:scale-105 text-center rounded border-2 border-transparent border-dashed cursor-pointer transition duration-100;
} }
</style> </style>

View File

@@ -0,0 +1,110 @@
<script>
import { browser } from '$app/env';
import { goto } from '$app/navigation';
import { onDestroy, onMount } from 'svelte';
import { session } from '$app/stores';
import { request } from '$lib/request';
import { toast } from '@zerodevx/svelte-toast';
import { application, prApplication } from '$store';
let loadPRDeployments = null;
onMount(async () => {
await getPRDeployments();
loadPRDeployments = setInterval(async () => {
await getPRDeployments();
}, 1000);
});
onDestroy(() => {
clearInterval(loadPRDeployments);
});
async function getPRDeployments() {
const { configuration } = await request(`/api/v1/application/config`, $session, {
body: {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch
}
});
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
}
async function removePR(prConfiguration) {
const result = window.confirm("Are you sure? It's NOT reversible!");
if (result) {
await request(`/api/v1/application/remove`, $session, {
body: {
organization: prConfiguration.repository.organization,
name: prConfiguration.repository.name,
branch: prConfiguration.repository.branch,
domain: prConfiguration.publish.domain
}
});
browser && toast.push('PR deployment removed.');
const { configuration } = await request(`/api/v1/application/config`, $session, {
body: {
name: prConfiguration.repository.name,
organization: prConfiguration.repository.organization,
branch: prConfiguration.repository.branch
}
});
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
}
}
</script>
<div class="text-2xl font-bold border-gradient w-48">Pull Requests</div>
<div class="text-center pt-4">
{#if $prApplication.length > 0}
<div class="py-4 ">
{#each $prApplication as pr}
<div class="flex space-x-4 justify-center items-center">
<div class="text-left font-bold tracking-tight ">
{pr.publish.domain}
</div>
<a
target="_blank"
class="icon mx-2 "
href={'https://' + pr.publish.domain + pr.publish.path}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"
/>
</svg></a
>
<!-- <div class="flex-1" /> -->
<button
class="icon hover:text-red-500 hover:bg-warmGray-800"
on:click={() => removePR(pr)}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
/>
</svg>
</button>
</div>
{/each}
</div>
{:else}
<div class="font-bold text-center">No PR deployments found</div>
{/if}
</div>

View File

@@ -1,8 +1,15 @@
<script lang="ts"> <script lang="ts">
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { session } from '$app/stores'; import { session } from '$app/stores';
import { githubRepositories, application, githubInstallations } from '$store'; import {
githubRepositories,
application,
githubInstallations,
prApplication,
initConf,
isPullRequestPermissionsGranted
} from '$store';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
@@ -18,6 +25,7 @@
}; };
let branches = []; let branches = [];
let relogin = false; let relogin = false;
let permissions = {};
function dashify(str: string, options?: any) { function dashify(str: string, options?: any) {
if (typeof str !== 'string') return str; if (typeof str !== 'string') return str;
return str return str
@@ -110,8 +118,17 @@
$session $session
); );
loading.branches = false; loading.branches = false;
await loadPermissions();
}
async function loadPermissions() {
const config = await request(
`https://api.github.com/apps/${import.meta.env.VITE_GITHUB_APP_NAME}`,
$session
);
if (config.permissions['pull_requests'] && config.events.includes('pull_request')) {
$isPullRequestPermissionsGranted = true;
}
} }
async function modifyGithubAppConfig() { async function modifyGithubAppConfig() {
if (browser) { if (browser) {
const left = screen.width / 2 - 1020 / 2; const left = screen.width / 2 - 1020 / 2;
@@ -128,20 +145,23 @@
', toolbar=0, menubar=0, status=0' ', toolbar=0, menubar=0, status=0'
); );
const timer = setInterval(async () => { const timer = setInterval(async () => {
if (newWindow.closed) { if (newWindow?.closed) {
clearInterval(timer); clearInterval(timer);
loading.github = true; loading.github = true;
if ($application.repository.name) { if ($application.repository.name) {
try { try {
const config = await request(`/api/v1/application/config`, $session, { const { configuration } = await request(`/api/v1/application/config`, $session, {
body: { body: {
name: $application.repository.name, name: $application.repository.name,
organization: $application.repository.organization, organization: $application.repository.organization,
branch: $application.repository.branch branch: $application.repository.branch
} }
}); });
$application = { ...config };
$prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
$application = configuration.find((c) => c.general.pullRequest === 0);
$initConf = JSON.parse(JSON.stringify($application));
} catch (error) { } catch (error) {
browser && goto('/dashboard/applications', { replaceState: true }); browser && goto('/dashboard/applications', { replaceState: true });
} }

View File

@@ -14,7 +14,7 @@
', toolbar=0, menubar=0, status=0' ', toolbar=0, menubar=0, status=0'
); );
const timer = setInterval(() => { const timer = setInterval(() => {
if (newWindow.closed) { if (newWindow?.closed) {
clearInterval(timer); clearInterval(timer);
location.reload() location.reload()
} }

View File

@@ -3,22 +3,28 @@
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import Tooltip from '$components/Tooltip.svelte'; import Tooltip from '$components/Tooltip.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { browser } from '$app/env'; import { browser } from '$app/env';
async function removeApplication() { async function removeApplication() {
await request(`/api/v1/application/remove`, $session, { const result = window.confirm(
body: { "Are you sure? It will delete all deployments, including PR's - it's NOT reversible!"
organization: $application.repository.organization, );
name: $application.repository.name, if (result) {
branch: $application.repository.branch await request(`/api/v1/application/remove`, $session, {
} body: {
}); organization: $application.repository.organization,
name: $application.repository.name,
branch: $application.repository.branch,
domain: $application.publish.domain
}
});
browser && toast.push('Application removed.'); browser && toast.push('Application removed.');
$application = JSON.parse(JSON.stringify(initialApplication)); $application = JSON.parse(JSON.stringify(initialApplication));
browser && goto(`/dashboard/applications`, { replaceState: true }); browser && goto(`/dashboard/applications`, { replaceState: true });
}
} }
onDestroy(() => { onDestroy(() => {
@@ -50,6 +56,7 @@
// toast.push(error.error || error || 'Ooops something went wrong.'); // toast.push(error.error || error || 'Ooops something went wrong.');
} }
} }
</script> </script>
<nav class="flex text-white justify-end items-center m-4 fixed right-0 top-0 space-x-4 z-50"> <nav class="flex text-white justify-end items-center m-4 fixed right-0 top-0 space-x-4 z-50">

View File

@@ -1,25 +1,26 @@
<script> <script>
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import templates from '$lib/api/applications/templates'; import templates from '$lib/api/applications/packs/templates';
import { application, dashboard } from '$store'; import { application, dashboard, initConf, prApplication } from '$store';
import General from '$components/Application/ActiveTab/General.svelte'; import General from '$components/Application/ActiveTab/General.svelte';
import Secrets from '$components/Application/ActiveTab/Secrets.svelte'; import Secrets from '$components/Application/ActiveTab/Secrets.svelte';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { browser } from '$app/env'; import { browser } from '$app/env';
import PullRequests from './ActiveTab/PullRequests.svelte';
let activeTab = { let activeTab = {
general: true, general: true,
buildStep: false, secrets: false,
secrets: false pullRequests: false
}; };
function activateTab(tab) { function activateTab(tab) {
if (activeTab.hasOwnProperty(tab)) { if (activeTab.hasOwnProperty(tab)) {
activeTab = { activeTab = {
general: false, general: false,
buildStep: false, pullRequests: false,
secrets: false secrets: false
}; };
activeTab[tab] = true; activeTab[tab] = true;
@@ -48,16 +49,7 @@
} }
return; return;
} }
if ($page.path !== '/application/new') { if ($page.path === '/application/new') {
const config = await request(`/api/v1/application/config`, $session, {
body: {
name: $application.repository.name,
organization: $application.repository.organization,
branch: $application.repository.branch
}
});
$application = { ...config };
} else {
try { try {
const dir = await request( const dir = await request(
`https://api.github.com/repos/${$application.repository.organization}/${$application.repository.name}/contents/?ref=${$application.repository.branch}`, `https://api.github.com/repos/${$application.repository.organization}/${$application.repository.name}/contents/?ref=${$application.repository.branch}`,
@@ -66,7 +58,7 @@
const packageJson = dir.find((f) => f.type === 'file' && f.name === 'package.json'); const packageJson = dir.find((f) => f.type === 'file' && f.name === 'package.json');
const Dockerfile = dir.find((f) => f.type === 'file' && f.name === 'Dockerfile'); const Dockerfile = dir.find((f) => f.type === 'file' && f.name === 'Dockerfile');
const CargoToml = dir.find((f) => f.type === 'file' && f.name === 'Cargo.toml'); const CargoToml = dir.find((f) => f.type === 'file' && f.name === 'Cargo.toml');
const requirementsTXT = dir.find((f) => f.type === 'file' && f.name === 'requirements.txt');
if (packageJson) { if (packageJson) {
const { content } = await request(packageJson.git_url, $session); const { content } = await request(packageJson.git_url, $session);
const packageJsonContent = JSON.parse(atob(content)); const packageJsonContent = JSON.parse(atob(content));
@@ -80,10 +72,18 @@
if (checkPackageJSONContents(dep)) { if (checkPackageJSONContents(dep)) {
const config = templates[dep]; const config = templates[dep];
$application.build.pack = config.pack; $application.build.pack = config.pack;
if (config.installation) if (config.installation) {
$application.build.command.installation = config.installation; $application.build.command.installation = config.installation;
if (config.port) $application.publish.port = config.port; }
if (config.directory) $application.publish.directory = config.directory; if (config.start) {
$application.build.command.start = config.start;
}
if (config.port) {
$application.publish.port = config.port;
}
if (config.directory) {
$application.publish.directory = config.directory;
}
if (packageJsonContent.scripts.hasOwnProperty('build') && config.build) { if (packageJsonContent.scripts.hasOwnProperty('build') && config.build) {
$application.build.command.build = config.build; $application.build.command.build = config.build;
@@ -94,6 +94,9 @@
} else if (CargoToml) { } else if (CargoToml) {
$application.build.pack = 'rust'; $application.build.pack = 'rust';
browser && toast.push(`Rust language detected. Default values set.`); browser && toast.push(`Rust language detected. Default values set.`);
} else if (requirementsTXT) {
$application.build.pack = 'python';
browser && toast.push('Python language detected. Default values set.');
} else if (Dockerfile) { } else if (Dockerfile) {
$application.build.pack = 'docker'; $application.build.pack = 'docker';
browser && toast.push('Custom Dockerfile found. Build pack set to docker.'); browser && toast.push('Custom Dockerfile found. Build pack set to docker.');
@@ -103,6 +106,7 @@
} }
} }
} }
</script> </script>
{#await load()} {#await load()}
@@ -124,6 +128,15 @@
> >
Secrets Secrets
</div> </div>
{#if $application.general.isPreviewDeploymentEnabled}
<div
on:click={() => activateTab('pullRequests')}
class:text-green-500={activeTab.pullRequests}
class="px-3 py-2 cursor-pointer hover:bg-warmGray-700 rounded-lg transition duration-100"
>
Pull Requests
</div>
{/if}
</nav> </nav>
</div> </div>
<div class="max-w-4xl mx-auto"> <div class="max-w-4xl mx-auto">
@@ -132,6 +145,8 @@
<General /> <General />
{:else if activeTab.secrets} {:else if activeTab.secrets}
<Secrets /> <Secrets />
{:else if activeTab.pullRequests && $page.path !== '/application/new'}
<PullRequests />
{/if} {/if}
</div> </div>
</div> </div>

View File

@@ -5,16 +5,19 @@
import Postgresql from './SVGs/Postgresql.svelte'; import Postgresql from './SVGs/Postgresql.svelte';
import Mysql from './SVGs/Mysql.svelte'; import Mysql from './SVGs/Mysql.svelte';
import CouchDb from './SVGs/CouchDb.svelte'; import CouchDb from './SVGs/CouchDb.svelte';
import Redis from './SVGs/Redis.svelte';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { browser } from '$app/env'; import { browser } from '$app/env';
import Loading from '$components/Loading.svelte';
let type; let type;
let defaultDatabaseName; let defaultDatabaseName;
let loading = false;
async function deploy() { async function deploy() {
try { try {
loading = true;
await request(`/api/v1/databases/deploy`, $session, { await request(`/api/v1/databases/deploy`, $session, {
body: { body: {
type, type,
@@ -28,82 +31,106 @@
} }
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} finally {
loading = false;
} }
} }
</script> </script>
<div class="text-center space-y-2 max-w-4xl mx-auto px-6" in:fade={{ duration: 100 }}> {#if loading}
{#if $page.path === '/database/new'} <Loading />
<div class="flex justify-center space-x-4 font-bold pb-6"> {:else}
<div <div class="text-center space-y-2 max-w-4xl mx-auto px-6" in:fade={{ duration: 100 }}>
class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-green-600 p-2 rounded bg-warmGray-800 w-32" {#if $page.path === '/database/new'}
class:border-green-600={type === 'mongodb'} <div class="flex justify-center space-x-4 font-bold pb-6">
on:click={() => (type = 'mongodb')} <div
> class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-green-600 p-2 rounded bg-warmGray-800 w-32"
<div class="flex items-center justify-center my-2"> class:border-green-600={type === 'mongodb'}
<MongoDb customClass="w-6" /> on:click={() => (type = 'mongodb')}
>
<div class="flex items-center justify-center my-2">
<MongoDb customClass="w-6" />
</div>
<div class="text-white">MongoDB</div>
</div> </div>
<div class="text-white">MongoDB</div> <div
</div> class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-red-600 p-2 rounded bg-warmGray-800 w-32"
<div class:border-red-600={type === 'couchdb'}
class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-red-600 p-2 rounded bg-warmGray-800 w-32" on:click={() => (type = 'couchdb')}
class:border-red-600={type === 'couchdb'} >
on:click={() => (type = 'couchdb')} <div class="flex items-center justify-center my-2">
> <CouchDb customClass="w-12 text-red-600 fill-current" />
<div class="flex items-center justify-center my-2"> </div>
<CouchDb customClass="w-12 text-red-600 fill-current" /> <div class="text-white">Couchdb</div>
</div> </div>
<div class="text-white">Couchdb</div> <div
</div> class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-blue-600 p-2 rounded bg-warmGray-800 w-32"
<div class:border-blue-600={type === 'postgresql'}
class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-blue-600 p-2 rounded bg-warmGray-800 w-32" on:click={() => (type = 'postgresql')}
class:border-blue-600={type === 'postgresql'} >
on:click={() => (type = 'postgresql')} <div class="flex items-center justify-center my-2">
> <Postgresql customClass="w-12" />
<div class="flex items-center justify-center my-2"> </div>
<Postgresql customClass="w-12" /> <div class="text-white">PostgreSQL</div>
</div> </div>
<div class="text-white">PostgreSQL</div> <div
</div> class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-orange-600 p-2 rounded bg-warmGray-800 w-32"
<div class:border-orange-600={type === 'mysql'}
class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-orange-600 p-2 rounded bg-warmGray-800 w-32" on:click={() => (type = 'mysql')}
class:border-orange-600={type === 'mysql'} >
on:click={() => (type = 'mysql')} <div class="flex items-center justify-center">
> <Mysql customClass="w-10" />
<div class="flex items-center justify-center"> </div>
<Mysql customClass="w-10" /> <div class="text-white">MySQL</div>
</div>
<div
class="text-center flex-col items-center cursor-pointer ease-in-out transform hover:scale-105 duration-100 border-2 border-dashed border-transparent hover:border-red-600 p-2 rounded bg-warmGray-800 w-32"
class:border-red-600={type === 'redis'}
on:click={() => (type = 'redis')}
>
<div class="flex items-center justify-center">
<Redis customClass="w-12" />
</div>
<div class="text-white">Redis</div>
</div> </div>
<div class="text-white">MySQL</div>
</div>
<!-- <button <!-- <button
class="button bg-gray-500 p-2 text-white hover:bg-yellow-500 cursor-pointer w-32" class="button bg-gray-500 p-2 text-white hover:bg-yellow-500 cursor-pointer w-32"
on:click="{() => (type = 'clickhouse')}" on:click="{() => (type = 'clickhouse')}"
class:bg-yellow-500="{type === 'clickhouse'}" class:bg-yellow-500="{type === 'clickhouse'}"
> >
Clickhouse Clickhouse
</button> --> </button> -->
</div>
{#if type}
<div class="flex justify-center space-x-4 items-center">
<label for="defaultDB">Default database</label>
<input id="defaultDB" class="w-64" placeholder="random" bind:value={defaultDatabaseName} />
<button
class:bg-green-600={type === 'mongodb'}
class:hover:bg-green-500={type === 'mongodb'}
class:bg-blue-600={type === 'postgresql'}
class:hover:bg-blue-500={type === 'postgresql'}
class:bg-orange-600={type === 'mysql'}
class:hover:bg-orange-500={type === 'mysql'}
class:bg-red-600={type === 'couchdb'}
class:hover:bg-red-500={type === 'couchdb'}
class:bg-yellow-500={type === 'clickhouse'}
class:hover:bg-yellow-400={type === 'clickhouse'}
class="button p-2 w-32 text-white"
on:click={deploy}>Deploy</button
>
</div> </div>
{#if type}
<div class="flex justify-center space-x-4 items-center">
{#if type !== 'redis'}
<label for="defaultDB">Default database</label>
<input
id="defaultDB"
class="w-64"
placeholder="random"
bind:value={defaultDatabaseName}
/>
{/if}
<button
class:bg-green-600={type === 'mongodb'}
class:hover:bg-green-500={type === 'mongodb'}
class:bg-blue-600={type === 'postgresql'}
class:hover:bg-blue-500={type === 'postgresql'}
class:bg-orange-600={type === 'mysql'}
class:hover:bg-orange-500={type === 'mysql'}
class:bg-red-600={type === 'couchdb' || type === 'redis'}
class:hover:bg-red-500={type === 'couchdb' || type === 'redis'}
class:bg-yellow-500={type === 'clickhouse'}
class:hover:bg-yellow-400={type === 'clickhouse'}
class="button p-2 w-32 text-white"
on:click={deploy}>Deploy</button
>
</div>
{/if}
{/if} {/if}
{/if} </div>
</div> {/if}

View File

@@ -0,0 +1,37 @@
<script>
export let customClass;
</script>
<svg
class={customClass}
height="64"
viewBox="0 0 32 32"
width="64"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
><defs
><path
id="a"
d="m45.536 38.764c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.813s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z"
/><path
id="b"
d="m45.536 28.733c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.935c2.332-.837 3.14-.867 5.126-.14s12.35 4.853 14.312 5.57 2.037 1.31.024 2.36z"
/></defs
><g transform="matrix(.848327 0 0 .848327 -7.883573 -9.449691)"
><use fill="#a41e11" xlink:href="#a" /><path
d="m45.536 34.95c-2.013 1.05-12.44 5.337-14.66 6.494s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276-2.04-1.613-.077-2.382l15.332-5.936c2.332-.836 3.14-.867 5.126-.14s12.35 4.852 14.31 5.582 2.037 1.31.024 2.36z"
fill="#d82c20"
/><use fill="#a41e11" xlink:href="#a" y="-6.218" /><use fill="#d82c20" xlink:href="#b" /><path
d="m45.536 26.098c-2.013 1.05-12.44 5.337-14.66 6.495s-3.453 1.146-5.207.308-12.85-5.32-14.85-6.276c-1-.478-1.524-.88-1.524-1.26v-3.815s14.447-3.145 16.78-3.982 3.14-.867 5.126-.14 13.853 2.868 15.814 3.587v3.76c0 .377-.452.8-1.477 1.324z"
fill="#a41e11"
/><use fill="#d82c20" xlink:href="#b" y="-6.449" /><g fill="#fff"
><path
d="m29.096 20.712-1.182-1.965-3.774-.34 2.816-1.016-.845-1.56 2.636 1.03 2.486-.814-.672 1.612 2.534.95-3.268.34zm-6.296 3.912 8.74-1.342-2.64 3.872z"
/><ellipse cx="20.444" cy="21.402" rx="4.672" ry="1.811" /></g
><path d="m42.132 21.138-5.17 2.042-.004-4.087z" fill="#7a0c00" /><path
d="m36.963 23.18-.56.22-5.166-2.042 5.723-2.264z"
fill="#ad2115"
/></g
></svg
>

View File

@@ -3,7 +3,7 @@
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import Loading from '../Loading.svelte'; import Loading from '../Loading.svelte';
import Tooltip from '$components/Tooltip.svelte'; import Tooltip from '$components/Tooltip.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import PasswordField from '$components/PasswordField.svelte'; import PasswordField from '$components/PasswordField.svelte';
import { browser } from '$app/env'; import { browser } from '$app/env';

8
src/global.d.ts vendored
View File

@@ -36,6 +36,8 @@ export type Application = {
deployId: string; deployId: string;
nickname: string; nickname: string;
workdir: string; workdir: string;
isPreviewDeploymentEnabled: boolean;
pullRequest: number;
}; };
build: { build: {
pack: string; pack: string;
@@ -43,6 +45,12 @@ export type Application = {
command: { command: {
build: string | null; build: string | null;
installation: string; installation: string;
start: string;
python: {
module?: string;
instance?: string;
}
}; };
container: { container: {
name: string; name: string;

View File

@@ -4,6 +4,9 @@ import { publicPages } from '$lib/consts';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { verifyUserId } from '$lib/api/common'; import { verifyUserId } from '$lib/api/common';
import { initializeSession } from 'svelte-kit-cookie-session'; import { initializeSession } from 'svelte-kit-cookie-session';
import { cleanupStuckedDeploymentsInDB } from '$lib/api/applications/cleanup';
import { docker } from '$lib/api/docker';
import Configuration from '$models/Configuration';
process.on('SIGINT', function () { process.on('SIGINT', function () {
mongoose.connection.close(function () { mongoose.connection.close(function () {
@@ -13,6 +16,7 @@ process.on('SIGINT', function () {
}); });
async function connectMongoDB() { async function connectMongoDB() {
// TODO: Save configurations on start?
const { MONGODB_USER, MONGODB_PASSWORD, MONGODB_HOST, MONGODB_PORT, MONGODB_DB } = process.env; const { MONGODB_USER, MONGODB_PASSWORD, MONGODB_HOST, MONGODB_PORT, MONGODB_DB } = process.env;
try { try {
if (process.env.NODE_ENV === 'production') { if (process.env.NODE_ENV === 'production') {
@@ -27,19 +31,84 @@ async function connectMongoDB() {
); );
} }
console.log('Connected to mongodb.'); console.log('Connected to mongodb.');
} catch (error) { } catch (error) {
console.log(error); console.log(error);
} }
} }
if (mongoose.connection.readyState !== 1) connectMongoDB(); (async () => {
if (mongoose.connection.readyState !== 1) await connectMongoDB();
try {
await mongoose.connection.db.dropCollection('logs-servers');
} catch (error) {
//
}
try {
await cleanupStuckedDeploymentsInDB();
} catch (error) {
console.log(error)
}
try {
const dockerServices = await docker.engine.listServices();
let applications: any = dockerServices.filter(
(r) =>
r.Spec.Labels.managedBy === 'coolify' &&
r.Spec.Labels.type === 'application' &&
r.Spec.Labels.configuration
);
applications = applications.map((r) => {
if (JSON.parse(r.Spec.Labels.configuration)) {
return {
configuration: JSON.parse(r.Spec.Labels.configuration),
UpdatedAt: r.UpdatedAt
};
}
return {};
});
applications = [
...new Map(
applications.map((item) => [
item.configuration.publish.domain + item.configuration.publish.path,
item
])
).values()
];
for (const application of applications) {
await Configuration.findOneAndUpdate({
'repository.name': application.configuration.repository.name,
'repository.organization': application.configuration.repository.organization,
'repository.branch': application.configuration.repository.branch,
'publish.domain': application.configuration.publish.domain
}, {
...application.configuration
}, { upsert: true, new: true })
}
} catch (error) {
console.log(error)
}
})()
export async function handle({ request, render }) {
export async function handle({ request, resolve }) {
const { SECRETS_ENCRYPTION_KEY } = process.env; const { SECRETS_ENCRYPTION_KEY } = process.env;
const session = initializeSession(request.headers, { let session;
secret: SECRETS_ENCRYPTION_KEY, try {
cookie: { path: '/' } session = initializeSession(request.headers, {
}); secret: SECRETS_ENCRYPTION_KEY,
cookie: { path: '/' }
});
} catch(error) {
return {
status: 302,
headers: {
'set-cookie': 'kit.session=deleted;path=/;expires=Wed, 21 Oct 2015 07:28:00 GMT',
location: '/'
}
};
}
request.locals.session = session; request.locals.session = session;
if (session?.data?.coolToken) { if (session?.data?.coolToken) {
try { try {
@@ -49,7 +118,7 @@ export async function handle({ request, render }) {
request.locals.session.destroy = true; request.locals.session.destroy = true;
} }
} }
const response = await render(request); const response = await resolve(request);
if (!session['set-cookie']) { if (!session['set-cookie']) {
if (!session?.data?.coolToken && !publicPages.includes(request.path)) { if (!session?.data?.coolToken && !publicPages.includes(request.path)) {
return { return {

View File

@@ -1,4 +1,4 @@
import Deployment from '$models/Logs/Deployment'; import Deployment from '$models/Deployment';
import { saveAppLog } from './logging'; import { saveAppLog } from './logging';
import * as packs from './packs'; import * as packs from './packs';

View File

@@ -1,4 +1,5 @@
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import Deployment from '$models/Deployment';
import { execShellAsync } from '../common'; import { execShellAsync } from '../common';
export async function deleteSameDeployments(configuration) { export async function deleteSameDeployments(configuration) {
@@ -10,36 +11,57 @@ export async function deleteSameDeployments(configuration) {
const running = JSON.parse(s.Spec.Labels.configuration); const running = JSON.parse(s.Spec.Labels.configuration);
if ( if (
running.repository.id === configuration.repository.id && running.repository.id === configuration.repository.id &&
running.repository.branch === configuration.repository.branch running.repository.branch === configuration.repository.branch &&
running.publish.domain === configuration.publish.domain
) { ) {
await execShellAsync(`docker stack rm ${s.Spec.Labels['com.docker.stack.namespace']}`); await execShellAsync(`docker stack rm ${s.Spec.Labels['com.docker.stack.namespace']}`);
} }
}); });
} }
export async function cleanupStuckedDeploymentsInDB() {
// Cleanup stucked deployments.
await Deployment.updateMany(
{ progress: { $in: ['queued', 'inprogress'] } },
{ progress: 'failed' }
);
}
export async function purgeImagesContainers(configuration, deleteAll = false) { export async function purgeImagesContainers(configuration, deleteAll = false) {
const { name, tag } = configuration.build.container; const { name, tag } = configuration.build.container;
await execShellAsync('docker container prune -f'); try {
if (deleteAll) { await execShellAsync('docker container prune -f');
const IDsToDelete = ( } catch (error) {
await execShellAsync(`docker images ls --filter=reference='${name}' --format '{{json .ID }}'`) //
) }
.trim() try {
.replace(/"/g, '') if (deleteAll) {
.split('\n'); const IDsToDelete = (
if (IDsToDelete.length > 0) await execShellAsync(
await execShellAsync(`docker rmi -f ${IDsToDelete.toString().replace(',', ' ')}`); `docker images ls --filter=reference='${name}' --format '{{json .ID }}'`
} else { )
const IDsToDelete = ( )
await execShellAsync( .trim()
`docker images ls --filter=reference='${name}' --filter=before='${name}:${tag}' --format '{{json .ID }}'` .replace(/"/g, '')
) .split('\n');
) if (IDsToDelete.length > 0) await execShellAsync(`docker rmi -f ${IDsToDelete.join(' ')}`);
.trim() } else {
.replace(/"/g, '') const IDsToDelete = (
.split('\n'); await execShellAsync(
if (IDsToDelete.length > 1) `docker images ls --filter=reference='${name}' --filter=before='${name}:${tag}' --format '{{json .ID }}'`
await execShellAsync(`docker rmi -f ${IDsToDelete.toString().replace(',', ' ')}`); )
)
.trim()
.replace(/"/g, '')
.split('\n');
if (IDsToDelete.length > 1) await execShellAsync(`docker rmi -f ${IDsToDelete.join(' ')}`);
}
} catch (error) {
console.log(error);
}
try {
await execShellAsync('docker image prune -f');
} catch (error) {
//
} }
await execShellAsync('docker image prune -f');
} }

View File

@@ -4,7 +4,7 @@ import { execShellAsync } from '../common';
export default async function (configuration) { export default async function (configuration) {
try { try {
const { GITHUB_APP_PRIVATE_KEY } = process.env; const { GITHUB_APP_PRIVATE_KEY } = process.env;
const { workdir } = configuration.general; const { workdir, isPreviewDeploymentEnabled, pullRequest } = configuration.general;
const { organization, name, branch } = configuration.repository; const { organization, name, branch } = configuration.repository;
const github = configuration.github; const github = configuration.github;
if (!github.installation.id || !github.app.id) { if (!github.installation.id || !github.app.id) {
@@ -37,8 +37,12 @@ export default async function (configuration) {
await execShellAsync( await execShellAsync(
`mkdir -p ${workdir} && git clone -q -b ${branch} https://x-access-token:${token}@github.com/${organization}/${name}.git ${workdir}/` `mkdir -p ${workdir} && git clone -q -b ${branch} https://x-access-token:${token}@github.com/${organization}/${name}.git ${workdir}/`
); );
if (isPreviewDeploymentEnabled && pullRequest && pullRequest !== 0) {
await execShellAsync(`cd ${workdir} && git fetch origin pull/${pullRequest}/head:pull_${pullRequest} && git checkout pull_${pullRequest}`)
}
configuration.build.container.tag = ( configuration.build.container.tag = (
await execShellAsync(`cd ${configuration.general.workdir}/ && git rev-parse HEAD`) await execShellAsync(`cd ${workdir}/ && git rev-parse HEAD`)
) )
.replace('\n', '') .replace('\n', '')
.slice(0, 7); .slice(0, 7);

View File

@@ -10,9 +10,8 @@ function getUniq() {
} }
export function setDefaultConfiguration(configuration) { export function setDefaultConfiguration(configuration) {
const nickname = getUniq(); const nickname = configuration.general.nickname || getUniq();
const deployId = cuid(); const deployId = cuid();
const shaBase = JSON.stringify({ repository: configuration.repository }); const shaBase = JSON.stringify({ repository: configuration.repository });
const sha256 = crypto.createHash('sha256').update(shaBase).digest('hex'); const sha256 = crypto.createHash('sha256').update(shaBase).digest('hex');
@@ -21,17 +20,22 @@ export function setDefaultConfiguration(configuration) {
configuration.general.nickname = nickname; configuration.general.nickname = nickname;
configuration.general.deployId = deployId; configuration.general.deployId = deployId;
configuration.general.workdir = `/tmp/${deployId}`; configuration.general.workdir = `/tmp/${deployId}`;
if (configuration.general.isPreviewDeploymentEnabled && configuration.general.pullRequest !== 0) {
configuration.build.container.name = `pr${configuration.general.pullRequest}-${sha256.slice(0, 8)}`
configuration.publish.domain = `pr${configuration.general.pullRequest}.${configuration.publish.domain}`
}
if (!configuration.publish.path) configuration.publish.path = '/'; if (!configuration.publish.path) configuration.publish.path = '/';
if (!configuration.publish.port) { if (!configuration.publish.port) {
if ( if (
configuration.build.pack === 'nodejs' || configuration.build.pack === 'nodejs' ||
configuration.build.pack === 'vuejs' ||
configuration.build.pack === 'nuxtjs' || configuration.build.pack === 'nuxtjs' ||
configuration.build.pack === 'rust' || configuration.build.pack === 'rust' ||
configuration.build.pack === 'nextjs' configuration.build.pack === 'nextjs' ||
configuration.build.pack === 'nestjs'
) { ) {
configuration.publish.port = 3000; configuration.publish.port = 3000;
} else if (configuration.build.pack === 'python') {
configuration.publish.port = 4000;
} else { } else {
configuration.publish.port = 80; configuration.publish.port = 80;
} }
@@ -48,6 +52,19 @@ export function setDefaultConfiguration(configuration) {
if (!configuration.build.command.installation) if (!configuration.build.command.installation)
configuration.build.command.installation = 'yarn install'; configuration.build.command.installation = 'yarn install';
} }
if (
configuration.build.pack === 'nodejs' ||
configuration.build.pack === 'vuejs' ||
configuration.build.pack === 'nuxtjs' ||
configuration.build.pack === 'nextjs' ||
configuration.build.pack === 'nestjs'
) {
if (!configuration.build.command.start) configuration.build.command.start = 'yarn start'
}
if (configuration.build.pack === 'python') {
if (!configuration.build.command.python.module) configuration.build.command.python.module = 'main'
if (!configuration.build.command.python.instance) configuration.build.command.python.instance = 'app'
}
configuration.build.container.baseSHA = crypto configuration.build.container.baseSHA = crypto
.createHash('sha256') .createHash('sha256')
@@ -58,20 +75,23 @@ export function setDefaultConfiguration(configuration) {
return configuration; return configuration;
} }
export async function precheckDeployment({ services, configuration }) { export async function precheckDeployment(configuration) {
const services = (await docker.engine.listServices()).filter(
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application' && JSON.parse(r.Spec.Labels.configuration).publish.domain === configuration.publish.domain
);
let foundService = false; let foundService = false;
let configChanged = false; let configChanged = false;
let imageChanged = false; let imageChanged = false;
let forceUpdate = false; let forceUpdate = false;
for (const service of services) { for (const service of services) {
const running = JSON.parse(service.Spec.Labels.configuration); const running = JSON.parse(service.Spec.Labels.configuration);
if (running) { if (running) {
if ( if (
running.repository.id === configuration.repository.id && running.repository.id === configuration.repository.id &&
running.repository.branch === configuration.repository.branch running.repository.branch === configuration.repository.branch
) { ) {
foundService = true;
// Base service configuration changed // Base service configuration changed
if ( if (
!running.build.container.baseSHA || !running.build.container.baseSHA ||
@@ -91,9 +111,32 @@ export async function precheckDeployment({ services, configuration }) {
(n) => (n) =>
n.DesiredState !== 'Running' && n.Image.split(':')[1] === running.build.container.tag n.DesiredState !== 'Running' && n.Image.split(':')[1] === running.build.container.tag
); );
if (isError.length > 0) forceUpdate = true; if (isError.length > 0) {
foundService = true; forceUpdate = true;
}
const compareObjects = (a, b) => {
if (a === b) return true;
if (typeof a != 'object' || typeof b != 'object' || a == null || b == null) return false;
let keysA = Object.keys(a), keysB = Object.keys(b);
if (keysA.length != keysB.length) return false;
for (let key of keysA) {
if (!keysB.includes(key)) return false;
if (typeof a[key] === 'function' || typeof b[key] === 'function') {
if (a[key].toString() != b[key].toString()) return false;
} else {
if (!compareObjects(a[key], b[key])) return false;
}
}
return true;
}
const runningWithoutContainer = JSON.parse(JSON.stringify(running)); const runningWithoutContainer = JSON.parse(JSON.stringify(running));
delete runningWithoutContainer.build.container; delete runningWithoutContainer.build.container;
@@ -102,16 +145,19 @@ export async function precheckDeployment({ services, configuration }) {
// If only the configuration changed // If only the configuration changed
if ( if (
JSON.stringify(runningWithoutContainer.build) !== !compareObjects(runningWithoutContainer.build,configurationWithoutContainer.build) ||
JSON.stringify(configurationWithoutContainer.build) || !compareObjects(runningWithoutContainer.publish,configurationWithoutContainer.publish) ||
JSON.stringify(runningWithoutContainer.publish) !== runningWithoutContainer.general.isPreviewDeploymentEnabled !==
JSON.stringify(configurationWithoutContainer.publish) configurationWithoutContainer.general.isPreviewDeploymentEnabled
) ){
configChanged = true; configChanged = true;
}
// If only the image changed // If only the image changed
if (running.build.container.tag !== configuration.build.container.tag) imageChanged = true; if (running.build.container.tag !== configuration.build.container.tag) imageChanged = true;
// If build pack changed, forceUpdate the service // If build pack changed, forceUpdate the service
if (running.build.pack !== configuration.build.pack) forceUpdate = true; if (running.build.pack !== configuration.build.pack) forceUpdate = true;
if (configuration.general.isPreviewDeploymentEnabled && configuration.general.pullRequest !== 0) forceUpdate = true
} }
} }
} }
@@ -128,29 +174,5 @@ export async function precheckDeployment({ services, configuration }) {
} }
export async function updateServiceLabels(configuration) { export async function updateServiceLabels(configuration) {
// In case of any failure during deployment, still update the current configuration. return await execShellAsync(`docker service update --label-add configuration='${JSON.stringify(configuration)}' ${configuration.build.container.name}_${configuration.build.container.name}`)
const services = (await docker.engine.listServices()).filter(
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application'
);
const found = services.find((s) => {
const config = JSON.parse(s.Spec.Labels.configuration);
if (
config.repository.id === configuration.repository.id &&
config.repository.branch === configuration.repository.branch
) {
return config;
}
return null;
});
if (found) {
const { ID } = found;
const Labels = { ...JSON.parse(found.Spec.Labels.configuration), ...configuration };
await execShellAsync(
`docker service update --label-add configuration='${JSON.stringify(
Labels
)}' --label-add com.docker.stack.image='${configuration.build.container.name}:${
configuration.build.container.tag
}' ${ID}`
);
}
} }

View File

@@ -1,9 +1,9 @@
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import { saveAppLog } from './logging'; import { saveAppLog } from './logging';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import { deleteSameDeployments } from './cleanup'; import { deleteSameDeployments, purgeImagesContainers } from './cleanup';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
import { execShellAsync } from '../common'; import { delay, execShellAsync } from '../common';
export default async function (configuration, imageChanged) { export default async function (configuration, imageChanged) {
const generateEnvs = {}; const generateEnvs = {};
@@ -11,6 +11,7 @@ export default async function (configuration, imageChanged) {
generateEnvs[secret.name] = secret.value; generateEnvs[secret.name] = secret.value;
} }
const containerName = configuration.build.container.name; const containerName = configuration.build.container.name;
const containerTag = configuration.build.container.tag;
// Only save SHA256 of it in the configuration label // Only save SHA256 of it in the configuration label
const baseServiceConfiguration = configuration.baseServiceConfiguration; const baseServiceConfiguration = configuration.baseServiceConfiguration;
@@ -20,7 +21,7 @@ export default async function (configuration, imageChanged) {
version: '3.8', version: '3.8',
services: { services: {
[containerName]: { [containerName]: {
image: `${configuration.build.container.name}:${configuration.build.container.tag}`, image: `${containerName}:${containerTag}`,
networks: [`${docker.network}`], networks: [`${docker.network}`],
environment: generateEnvs, environment: generateEnvs,
deploy: { deploy: {
@@ -31,21 +32,21 @@ export default async function (configuration, imageChanged) {
'configuration=' + JSON.stringify(configuration), 'configuration=' + JSON.stringify(configuration),
'traefik.enable=true', 'traefik.enable=true',
'traefik.http.services.' + 'traefik.http.services.' +
configuration.build.container.name + containerName +
`.loadbalancer.server.port=${configuration.publish.port}`, `.loadbalancer.server.port=${configuration.publish.port}`,
'traefik.http.routers.' + configuration.build.container.name + '.entrypoints=websecure', 'traefik.http.routers.' + containerName + '.entrypoints=websecure',
'traefik.http.routers.' + 'traefik.http.routers.' +
configuration.build.container.name + containerName +
'.rule=Host(`' + '.rule=Host(`' +
configuration.publish.domain + configuration.publish.domain +
'`) && PathPrefix(`' + '`) && PathPrefix(`' +
configuration.publish.path + configuration.publish.path +
'`)', '`)',
'traefik.http.routers.' + 'traefik.http.routers.' +
configuration.build.container.name + containerName +
'.tls.certresolver=letsencrypt', '.tls.certresolver=letsencrypt',
'traefik.http.routers.' + 'traefik.http.routers.' +
configuration.build.container.name + containerName +
'.middlewares=global-compress' '.middlewares=global-compress'
] ]
} }
@@ -62,7 +63,7 @@ export default async function (configuration, imageChanged) {
if (imageChanged) { if (imageChanged) {
// console.log('image changed') // console.log('image changed')
await execShellAsync( await execShellAsync(
`docker service update --image ${configuration.build.container.name}:${configuration.build.container.tag} ${configuration.build.container.name}_${configuration.build.container.name}` `docker service update --image ${containerName}:${containerTag} ${containerName}_${containerName}`
); );
} else { } else {
// console.log('new deployment or force deployment or config changed') // console.log('new deployment or force deployment or config changed')
@@ -71,6 +72,11 @@ export default async function (configuration, imageChanged) {
`cat ${configuration.general.workdir}/stack.yml | docker stack deploy --prune -c - ${containerName}` `cat ${configuration.general.workdir}/stack.yml | docker stack deploy --prune -c - ${containerName}`
); );
} }
async function purgeImagesAsync(found) {
await delay(10000);
await purgeImagesContainers(found, true);
}
purgeImagesAsync(configuration)
await saveAppLog('### Published done!', configuration); await saveAppLog('### Published done!', configuration);
} }

View File

@@ -1,9 +1,10 @@
import Settings from '$models/Settings'; import Settings from '$models/Settings';
import ServerLog from '$models/Logs/Server'; import ServerLog from '$models/ServerLog';
import ApplicationLog from '$models/Logs/Application'; import ApplicationLog from '$models/ApplicationLog';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { version } from '../../../../package.json'; import { version } from '../../../../package.json';
function generateTimestamp() { function generateTimestamp() {
return `${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} `; return `${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} `;
} }

View File

@@ -1,20 +1,21 @@
import { docker, streamEvents } from '$lib/api/docker'; import { docker, streamEvents } from '$lib/api/docker';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
const buildImageNodeDocker = (configuration) => { const buildImageNodeDocker = (configuration, prodBuild) => {
return [ return [
'FROM node:lts', 'FROM node:lts',
'WORKDIR /usr/src/app', 'WORKDIR /usr/src/app',
`COPY ${configuration.build.directory}/package*.json ./`, `COPY ${configuration.build.directory}/package*.json ./`,
configuration.build.command.installation && `RUN ${configuration.build.command.installation}`, configuration.build.command.installation && `RUN ${configuration.build.command.installation}`,
`COPY ./${configuration.build.directory} ./`, `COPY ./${configuration.build.directory} ./`,
`RUN ${configuration.build.command.build}` `RUN ${configuration.build.command.build}`,
prodBuild && `RUN rm -fr node_modules && ${configuration.build.command.installation} --prod`
].join('\n'); ].join('\n');
}; };
export async function buildImage(configuration, cacheBuild?: boolean) { export async function buildImage(configuration, cacheBuild?: boolean, prodBuild?: boolean) {
await fs.writeFile( await fs.writeFile(
`${configuration.general.workdir}/Dockerfile`, `${configuration.general.workdir}/Dockerfile`,
buildImageNodeDocker(configuration) buildImageNodeDocker(configuration, prodBuild)
); );
const stream = await docker.engine.buildImage( const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir }, { src: ['.'], context: configuration.general.workdir },

View File

@@ -7,8 +7,10 @@ import php from './php';
import nuxtjs from './nuxtjs'; import nuxtjs from './nuxtjs';
import nodejs from './nodejs'; import nodejs from './nodejs';
import nextjs from './nextjs'; import nextjs from './nextjs';
import nestjs from './nestjs';
import gatsby from './gatsby'; import gatsby from './gatsby';
import docker from './docker'; import docker from './docker';
import python from './python';
export { export {
vuejs, vuejs,
@@ -20,6 +22,8 @@ export {
nuxtjs, nuxtjs,
nodejs, nodejs,
nextjs, nextjs,
nestjs,
gatsby, gatsby,
docker docker,
python
}; };

View File

@@ -0,0 +1,31 @@
import { docker, streamEvents } from '$lib/api/docker';
import { promises as fs } from 'fs';
import { buildImage } from '../helpers';
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
const publishNodejsDocker = (configuration) => {
return [
'FROM node:lts',
'WORKDIR /usr/src/app',
configuration.build.command.build
? `COPY --from=${configuration.build.container.name}:${configuration.build.container.tag} /usr/src/app/${configuration.publish.directory} ./`
: `
COPY ${configuration.build.directory}/package*.json ./
RUN ${configuration.build.command.installation}
COPY ./${configuration.build.directory} ./`,
`EXPOSE ${configuration.publish.port}`,
`CMD ${configuration.build.command.start}`
].join('\n');
};
export default async function (configuration) {
if (configuration.build.command.build) await buildImage(configuration, false, true);
await fs.writeFile(
`${configuration.general.workdir}/Dockerfile`,
publishNodejsDocker(configuration)
);
const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir },
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
);
await streamEvents(stream, configuration);
}

View File

@@ -13,7 +13,7 @@ const publishNodejsDocker = (configuration) => {
RUN ${configuration.build.command.installation} RUN ${configuration.build.command.installation}
COPY ./${configuration.build.directory} ./`, COPY ./${configuration.build.directory} ./`,
`EXPOSE ${configuration.publish.port}`, `EXPOSE ${configuration.publish.port}`,
'CMD [ "yarn", "start" ]' `CMD ${configuration.build.command.start}`
].join('\n'); ].join('\n');
}; };
export default async function (configuration) { export default async function (configuration) {

View File

@@ -13,7 +13,7 @@ const publishNodejsDocker = (configuration) => {
RUN ${configuration.build.command.installation} RUN ${configuration.build.command.installation}
COPY ./${configuration.build.directory} ./`, COPY ./${configuration.build.directory} ./`,
`EXPOSE ${configuration.publish.port}`, `EXPOSE ${configuration.publish.port}`,
'CMD [ "yarn", "start" ]' `CMD ${configuration.build.command.start}`
].join('\n'); ].join('\n');
}; };

View File

@@ -13,7 +13,7 @@ const publishNodejsDocker = (configuration) => {
RUN ${configuration.build.command.installation} RUN ${configuration.build.command.installation}
COPY ./${configuration.build.directory} ./`, COPY ./${configuration.build.directory} ./`,
`EXPOSE ${configuration.publish.port}`, `EXPOSE ${configuration.publish.port}`,
'CMD [ "yarn", "start" ]' `CMD ${configuration.build.command.start}`
].join('\n'); ].join('\n');
}; };

View File

@@ -8,7 +8,7 @@ const publishPHPDocker = (configuration) => {
'WORKDIR /usr/src/app', 'WORKDIR /usr/src/app',
`COPY ./${configuration.build.directory} /var/www/html`, `COPY ./${configuration.build.directory} /var/www/html`,
'EXPOSE 80', 'EXPOSE 80',
' CMD ["apache2-foreground"]' 'CMD ["apache2-foreground"]'
].join('\n'); ].join('\n');
}; };

View File

@@ -0,0 +1,27 @@
import { docker, streamEvents } from '$lib/api/docker';
import { promises as fs } from 'fs';
// `HEALTHCHECK --timeout=10s --start-period=10s --interval=5s CMD curl -I -s -f http://localhost:${configuration.publish.port}${configuration.publish.path} || exit 1`,
const publishPython = (configuration) => {
return [
'FROM python:3-alpine',
'WORKDIR /usr/src/app',
'RUN pip install gunicorn',
`COPY ./${configuration.build.directory}/requirements.txt ./`,
`RUN pip install --no-cache-dir -r ./${configuration.build.directory}/requirements.txt`,
`COPY ./${configuration.build.directory}/ .`,
`EXPOSE ${configuration.publish.port}`,
`CMD gunicorn -w=4 ${configuration.build.command.python.module}:${configuration.build.command.python.instance}`
].join('\n');
};
export default async function (configuration) {
await fs.writeFile(
`${configuration.general.workdir}/Dockerfile`,
publishPython(configuration)
);
const stream = await docker.engine.buildImage(
{ src: ['.'], context: configuration.general.workdir },
{ t: `${configuration.build.container.name}:${configuration.build.container.tag}` }
);
await streamEvents(stream, configuration);
}

View File

@@ -1,6 +1,7 @@
const defaultBuildAndDeploy = { const defaultBuildAndDeploy = {
installation: 'yarn install', installation: 'yarn install',
build: 'yarn build' build: 'yarn build',
start: 'yarn start'
}; };
const templates = { const templates = {
@@ -10,6 +11,13 @@ const templates = {
directory: 'public', directory: 'public',
name: 'Svelte' name: 'Svelte'
}, },
'@nestjs/core': {
pack: 'nestjs',
...defaultBuildAndDeploy,
start: 'yarn start:prod',
port: 3000,
name: 'NestJS'
},
next: { next: {
pack: 'nextjs', pack: 'nextjs',
...defaultBuildAndDeploy, ...defaultBuildAndDeploy,
@@ -38,6 +46,7 @@ const templates = {
pack: 'vuejs', pack: 'vuejs',
...defaultBuildAndDeploy, ...defaultBuildAndDeploy,
directory: 'dist', directory: 'dist',
port: 80,
name: 'Vue' name: 'Vue'
}, },
gatsby: { gatsby: {

View File

@@ -1,4 +1,5 @@
import Deployment from '$models/Logs/Deployment';
import Deployment from '$models/Deployment';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import buildContainer from './buildContainer'; import buildContainer from './buildContainer';
import { updateServiceLabels } from './configuration'; import { updateServiceLabels } from './configuration';
@@ -9,23 +10,22 @@ import { saveAppLog } from './logging';
export default async function (configuration, imageChanged) { export default async function (configuration, imageChanged) {
const { id, organization, name, branch } = configuration.repository; const { id, organization, name, branch } = configuration.repository;
const { domain } = configuration.publish; const { domain } = configuration.publish;
const { deployId, nickname } = configuration.general; const { deployId } = configuration.general;
await new Deployment({ try {
repoId: id, await saveAppLog(`${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} Queued.`, configuration);
branch, await copyFiles(configuration);
deployId, await buildContainer(configuration);
domain, await deploy(configuration, imageChanged);
organization, await Deployment.findOneAndUpdate(
name, { repoId: id, branch, deployId, organization, name, domain },
nickname { repoId: id, branch, deployId, organization, name, domain, progress: 'done' }
}).save(); );
await saveAppLog(`${dayjs().format('YYYY-MM-DD HH:mm:ss.SSS')} Queued.`, configuration);
await copyFiles(configuration); await updateServiceLabels(configuration);
await buildContainer(configuration); } catch (error) {
await deploy(configuration, imageChanged); await Deployment.findOneAndUpdate(
await Deployment.findOneAndUpdate( { repoId: id, branch, deployId, organization, name, domain },
{ repoId: id, branch, deployId, organization, name, domain }, { repoId: id, branch, deployId, organization, name, domain, progress: 'failed' }
{ repoId: id, branch, deployId, organization, name, domain, progress: 'done' } );
); }
await updateServiceLabels(configuration);
} }

View File

@@ -1,13 +1,17 @@
import shell from 'shelljs'; import shell from 'shelljs';
import User from '$models/User'; import User from '$models/User';
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
import { saveServerLog } from './applications/logging';
export function execShellAsync(cmd, opts = {}) { export function execShellAsync(cmd, opts = {}) {
try { try {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
shell.config.silent = true; shell.config.silent = true;
shell.exec(cmd, opts, function (code, stdout, stderr) { shell.exec(cmd, opts, async function (code, stdout, stderr) {
if (code !== 0) return reject(new Error(stderr)); if (code !== 0) {
await saveServerLog({ message: JSON.stringify({ cmd, opts, code, stdout, stderr }) })
return reject(new Error(stderr));
}
return resolve(stdout); return resolve(stdout);
}); });
}); });

23
src/lib/api/github.ts Normal file
View File

@@ -0,0 +1,23 @@
import type { Request } from '@sveltejs/kit';
export async function githubAPI(
request: Request,
resource: string,
token?: string,
data?: Record<string, unknown>
) {
const base = 'https://api.github.com';
const res = await fetch(`${base}${resource}`, {
method: request.method,
headers: {
'content-type': 'application/json',
accept: 'application/json',
authorization: token ? `token ${token}` : ''
},
body: data && JSON.stringify(data)
});
return {
status: res.status,
body: await res.json()
};
}

View File

@@ -1 +1,2 @@
export const publicPages = ['/', '/api/v1/login/github/app', '/api/v1/webhooks/deploy', '/success']; export const publicPages = ['/', '/api/v1/login/github/app', '/api/v1/webhooks/deploy', '/success'];
export const VITE_GITHUB_APP_NAME = import.meta.env.VITE_GITHUB_APP_NAME

View File

@@ -61,10 +61,8 @@ export async function request(
} else if (response.headers.get('content-type').match(/multipart\/form-data/)) { } else if (response.headers.get('content-type').match(/multipart\/form-data/)) {
return await response.formData(); return await response.formData();
} else { } else {
console.log(response);
if (response.headers.get('content-disposition')) { if (response.headers.get('content-disposition')) {
const blob = await response.blob(); const blob = await response.blob();
console.log(blob);
const link = document.createElement('a'); const link = document.createElement('a');
link.href = URL.createObjectURL(blob); link.href = URL.createObjectURL(blob);
link.download = response.headers.get('content-disposition').split('=')[1] || 'backup.gz'; link.download = response.headers.get('content-disposition').split('=')[1] || 'backup.gz';
@@ -86,10 +84,10 @@ export async function request(
}); });
} else if (response.status >= 500) { } else if (response.status >= 500) {
const error = (await response.json()).error; const error = (await response.json()).error;
browser && toast.push(error); browser && toast.push(error.message || error);
return Promise.reject({ return Promise.reject({
status: response.status, status: response.status,
error: error || 'Oops, something is not okay. Are you okay?' error: error.message || error || 'Oops, something is not okay. Are you okay?'
}); });
} else { } else {
browser && toast.push(response.statusText); browser && toast.push(response.statusText);

View File

@@ -8,4 +8,4 @@ const ApplicationLogsSchema = new Schema({
ApplicationLogsSchema.set('timestamps', true); ApplicationLogsSchema.set('timestamps', true);
export default mongoose.model('logs-application', ApplicationLogsSchema); export default mongoose.models['logs-application'] || mongoose.model('logs-application', ApplicationLogsSchema);

View File

@@ -0,0 +1,55 @@
import mongoose from 'mongoose';
const { Schema } = mongoose;
const ConfigurationSchema = new Schema({
github: {
installation: {
id: { type: Number, required: true }
},
app: {
id: { type: Number, required: true }
}
},
repository: {
id: { type: Number, required: true },
organization: { type: String, required: true },
name: { type: String, required: true },
branch: { type: String, required: true }
},
general: {
deployId: { type: String, required: true },
nickname: { type: String, required: true },
workdir: { type: String, required: true },
isPreviewDeploymentEnabled: { type: Boolean, required: true, default: false },
pullRequest: { type: Number, required: true, default: 0 },
},
build: {
pack: { type: String, required: true },
directory: { type: String },
command: {
build: { type: String },
installation: { type: String },
start: { type: String },
python: {
module: { type: String },
instance: { type: String },
}
},
container: {
name: { type: String, required: true },
tag: { type: String, required: true },
baseSHA: { type: String, required: true },
},
},
publish: {
directory: { type: String },
domain: { type: String, required: true },
path: { type: String },
port: { type: Number },
secrets: { type: Array },
}
});
ConfigurationSchema.set('timestamps', true);
export default mongoose.models['configuration'] || mongoose.model('configuration', ConfigurationSchema);

View File

@@ -14,4 +14,4 @@ const DeploymentSchema = new Schema({
DeploymentSchema.set('timestamps', true); DeploymentSchema.set('timestamps', true);
export default mongoose.model('deployment', DeploymentSchema); export default mongoose.models['deployment'] || mongoose.model('deployment', DeploymentSchema);

View File

@@ -1,5 +1,5 @@
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import { version } from '../../../package.json'; import { version } from '../../package.json'
const { Schema, Document } = mongoose; const { Schema, Document } = mongoose;
// export interface ILogsServer extends Document { // export interface ILogsServer extends Document {
@@ -20,4 +20,4 @@ const LogsServerSchema = new Schema({
LogsServerSchema.set('timestamps', { createdAt: 'createdAt', updatedAt: false }); LogsServerSchema.set('timestamps', { createdAt: 'createdAt', updatedAt: false });
export default mongoose.model('logs-server', LogsServerSchema); export default mongoose.models['logs-server'] || mongoose.model('logs-server', LogsServerSchema);

View File

@@ -14,4 +14,4 @@ const SettingsSchema = new Schema({
SettingsSchema.set('timestamps', true); SettingsSchema.set('timestamps', true);
export default mongoose.model('settings', SettingsSchema); export default mongoose.models['settings'] || mongoose.model('settings', SettingsSchema);

View File

@@ -14,4 +14,4 @@ const UserSchema = new Schema({
UserSchema.set('timestamps', true); UserSchema.set('timestamps', true);
export default mongoose.model('user', UserSchema); export default mongoose.models['user'] || mongoose.model('user', UserSchema);

View File

@@ -1,6 +1,6 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
import { publicPages } from '$lib/consts'; import { publicPages } from '$lib/consts';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
/** /**
* @type {import('@sveltejs/kit').Load} * @type {import('@sveltejs/kit').Load}
*/ */
@@ -306,7 +306,7 @@
> >
{:else} {:else}
<button <button
class="opacity-50 tracking-tight font-bold text-xs rounded px-2 cursor-not-allowed" class="opacity-50 tracking-tight font-bold text-xs rounded px-2 cursor-not-allowed"
disabled={upgradeDisabled}>Upgrading. It could take a while, please wait...</button disabled={upgradeDisabled}>Upgrading. It could take a while, please wait...</button
> >
{/if} {/if}

View File

@@ -1,42 +0,0 @@
import type { Request } from '@sveltejs/kit';
// export async function api(request: Request, resource: string, data?: {}) {
// const base = 'https://github.com/';
// if (!request.context.isLoggedIn) {
// return { status: 401, body: 'Unauthorized' };
// }
// const res = await fetch(`${base}${resource}`, {
// method: request.method,
// headers: {
// 'content-type': 'application/json'
// },
// body: data && JSON.stringify(data)
// });
// return {
// status: res.status,
// body: await res.json()
// };
// }
export async function githubAPI(
request: Request,
resource: string,
token?: string,
data?: Record<string, unknown>
) {
const base = 'https://api.github.com';
const res = await fetch(`${base}${resource}`, {
method: request.method,
headers: {
'content-type': 'application/json',
accept: 'application/json',
authorization: token ? `token ${token}` : ''
},
body: data && JSON.stringify(data)
});
return {
status: res.status,
body: await res.json()
};
}

View File

@@ -1,32 +1,18 @@
import { setDefaultConfiguration } from '$lib/api/applications/configuration'; import { setDefaultConfiguration } from '$lib/api/applications/configuration';
import { saveServerLog } from '$lib/api/applications/logging'; import { saveServerLog } from '$lib/api/applications/logging';
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import Configuration from '$models/Configuration';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
export async function post(request: Request) { export async function post(request: Request) {
try { try {
const { DOMAIN } = process.env; const { DOMAIN } = process.env;
const configuration = setDefaultConfiguration(request.body); const configuration = setDefaultConfiguration(request.body);
const configurationFound = await Configuration.find({
const services = (await docker.engine.listServices()).filter( 'repository.id': { '$ne': configuration.repository.id },
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application' 'publish.domain': configuration.publish.domain
); }).select('-_id -__v -createdAt -updatedAt')
let foundDomain = false; if (configurationFound.length > 0 || configuration.publish.domain === DOMAIN) {
for (const service of services) {
const running = JSON.parse(service.Spec.Labels.configuration);
if (running) {
if (
running.publish.domain === configuration.publish.domain &&
running.repository.id !== configuration.repository.id &&
running.publish.path === configuration.publish.path
) {
foundDomain = true;
}
}
}
if (DOMAIN === configuration.publish.domain) foundDomain = true;
if (foundDomain) {
return { return {
status: 200, status: 200,
body: { body: {
@@ -38,13 +24,13 @@ export async function post(request: Request) {
return { return {
status: 200, status: 200,
body: { success: true, message: 'OK' } body: { success: true, message: 'OK' }
}; }
} catch (error) { } catch (error) {
await saveServerLog(error); await saveServerLog(error);
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} }

View File

@@ -1,9 +1,25 @@
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import Configuration from '$models/Configuration';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
export async function post(request: Request) { export async function post(request: Request) {
const { name, organization, branch }: any = request.body || {}; const { name, organization, branch }: any = request.body || {};
if (name && organization && branch) { if (name && organization && branch) {
const configurationFound = await Configuration.find({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch
}).select('-_id -__v -createdAt -updatedAt')
if (configurationFound) {
return {
status: 200,
body: {
configuration: [...configurationFound]
}
};
}
const services = await docker.engine.listServices(); const services = await docker.engine.listServices();
const applications = services.filter( const applications = services.filter(
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application' (r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application'
@@ -30,6 +46,7 @@ export async function post(request: Request) {
} }
return null; return null;
}); });
if (found) { if (found) {
return { return {
status: 200, status: 200,
@@ -38,13 +55,12 @@ export async function post(request: Request) {
...JSON.parse(found.Spec.Labels.configuration) ...JSON.parse(found.Spec.Labels.configuration)
} }
}; };
} else {
return {
status: 500,
body: {
error: 'No configuration found.'
}
};
} }
return {
status: 500,
body: {
error: 'No configuration found.'
}
};
} }
} }

View File

@@ -0,0 +1,62 @@
import { updateServiceLabels } from '$lib/api/applications/configuration';
import { execShellAsync } from '$lib/api/common';
import { docker } from '$lib/api/docker';
import ApplicationLog from '$models/ApplicationLog';
import Configuration from '$models/Configuration';
import Deployment from '$models/Deployment';
import type { Request } from '@sveltejs/kit';
export async function post(request: Request) {
const { name, organization, branch, isPreviewDeploymentEnabled }: any = request.body || {};
if (name && organization && branch) {
const configuration = await Configuration.findOneAndUpdate({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch
}, { $set: { 'general.isPreviewDeploymentEnabled': isPreviewDeploymentEnabled, 'general.pullRequest': 0 } }, { new: true }).select('-_id -__v -createdAt -updatedAt')
if (!isPreviewDeploymentEnabled) {
const found = await Configuration.find({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch,
'general.pullRequest': { '$ne': 0 }
})
for (const prDeployment of found) {
await Configuration.findOneAndRemove({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch,
'publish.domain': prDeployment.publish.domain
})
const deploys = await Deployment.find({ organization, branch, name, domain: prDeployment.publish.domain });
for (const deploy of deploys) {
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
await Deployment.deleteMany({ deployId: deploy.deployId });
}
await execShellAsync(`docker stack rm ${prDeployment.build.container.name}`);
}
return {
status: 200,
body: {
organization,
name,
branch
}
};
}
updateServiceLabels(configuration);
return {
status: 200,
body: {
success: true
}
};
}
return {
status: 500,
body: {
error: 'Cannot save.'
}
};
}

View File

@@ -1,31 +1,24 @@
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
import Deployment from '$models/Logs/Deployment'; import Deployment from '$models/Deployment';
import { docker } from '$lib/api/docker';
import { precheckDeployment, setDefaultConfiguration } from '$lib/api/applications/configuration'; import { precheckDeployment, setDefaultConfiguration } from '$lib/api/applications/configuration';
import cloneRepository from '$lib/api/applications/cloneRepository'; import cloneRepository from '$lib/api/applications/cloneRepository';
import { cleanupTmp } from '$lib/api/common'; import { cleanupTmp } from '$lib/api/common';
import queueAndBuild from '$lib/api/applications/queueAndBuild'; import queueAndBuild from '$lib/api/applications/queueAndBuild';
export async function post(request: Request) { import Configuration from '$models/Configuration';
let configuration;
try {
const services = (await docker.engine.listServices()).filter(
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application'
);
configuration = setDefaultConfiguration(request.body);
if (!configuration) { export async function post(request: Request) {
return { const configuration = setDefaultConfiguration(request.body);
status: 500, if (!configuration) {
body: { return {
error: 'Whaaat?' status: 500,
} body: {
}; error: 'Whaaat?'
} }
};
}
try {
await cloneRepository(configuration); await cloneRepository(configuration);
const { foundService, imageChanged, configChanged, forceUpdate } = await precheckDeployment({ const { foundService, imageChanged, configChanged, forceUpdate } = await precheckDeployment(configuration);
services,
configuration
});
if (foundService && !forceUpdate && !imageChanged && !configChanged) { if (foundService && !forceUpdate && !imageChanged && !configChanged) {
cleanupTmp(configuration.general.workdir); cleanupTmp(configuration.general.workdir);
return { return {
@@ -53,9 +46,33 @@ export async function post(request: Request) {
} }
}; };
} }
const { id, organization, name, branch } = configuration.repository;
const { domain } = configuration.publish;
const { deployId, nickname, pullRequest } = configuration.general;
await new Deployment({
repoId: id,
branch,
deployId,
domain,
organization,
name,
nickname
}).save();
await Configuration.findOneAndUpdate({
'repository.id': id,
'repository.organization': organization,
'repository.name': name,
'repository.branch': branch,
'general.pullRequest': { '$in': [null, 0] },
},
{ ...configuration },
{ upsert: true, new: true })
queueAndBuild(configuration, imageChanged); queueAndBuild(configuration, imageChanged);
return { return {
status: 200, status: 201,
body: { body: {
message: 'Deployment queued.', message: 'Deployment queued.',
nickname: configuration.general.nickname, nickname: configuration.general.nickname,
@@ -64,26 +81,28 @@ export async function post(request: Request) {
} }
}; };
} catch (error) { } catch (error) {
console.log(error)
await Deployment.findOneAndUpdate( await Deployment.findOneAndUpdate(
{ {
repoId: configuration.repository.id, repoId: configuration.repository.id,
branch: configuration.repository.branch, branch: configuration.repository.branch,
organization: configuration.repository.organization, organization: configuration.repository.organization,
name: configuration.repository.name, name: configuration.repository.name,
domain: configuration.publish.domain, domain: configuration.publish.domain
}, },
{ {
repoId: configuration.repository.id, repoId: configuration.repository.id,
branch: configuration.repository.branch, branch: configuration.repository.branch,
organization: configuration.repository.organization, organization: configuration.repository.organization,
name: configuration.repository.name, name: configuration.repository.name,
domain: configuration.publish.domain, progress: 'failed' domain: configuration.publish.domain,
progress: 'failed'
} }
); );
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} }

View File

@@ -1,6 +1,6 @@
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
import ApplicationLog from '$models/Logs/Application'; import ApplicationLog from '$models/ApplicationLog';
import Deployment from '$models/Logs/Deployment'; import Deployment from '$models/Deployment';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
export async function get(request: Request) { export async function get(request: Request) {
@@ -12,8 +12,6 @@ export async function get(request: Request) {
const deploy: any = await Deployment.findOne({ deployId }) const deploy: any = await Deployment.findOne({ deployId })
.select('-_id -__v') .select('-_id -__v')
.sort({ createdAt: 'desc' });
const finalLogs: any = {}; const finalLogs: any = {};
finalLogs.progress = deploy.progress; finalLogs.progress = deploy.progress;
finalLogs.events = logs.map((log) => log.event); finalLogs.events = logs.map((log) => log.event);
@@ -24,11 +22,11 @@ export async function get(request: Request) {
...finalLogs ...finalLogs
} }
}; };
} catch (e) { } catch (error) {
return { return {
status: 500, status: 500,
body: { body: {
error: e error: error.message || error
} }
}; };
} }

View File

@@ -2,7 +2,7 @@ import type { Request } from '@sveltejs/kit';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import utc from 'dayjs/plugin/utc.js'; import utc from 'dayjs/plugin/utc.js';
import relativeTime from 'dayjs/plugin/relativeTime.js'; import relativeTime from 'dayjs/plugin/relativeTime.js';
import Deployment from '$models/Logs/Deployment'; import Deployment from '$models/Deployment';
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(relativeTime); dayjs.extend(relativeTime);
export async function get(request: Request) { export async function get(request: Request) {
@@ -10,22 +10,18 @@ export async function get(request: Request) {
const repoId = request.query.get('repoId'); const repoId = request.query.get('repoId');
const branch = request.query.get('branch'); const branch = request.query.get('branch');
const page = request.query.get('page'); const page = request.query.get('page');
const onePage = 5; const onePage = 5;
const show = Number(page) * onePage || 5; const show = Number(page) * onePage || 5;
const deploy: any = await Deployment.find({ repoId, branch }) const deploy: any = await Deployment.find({ repoId, branch })
.select('-_id -__v -repoId') .select('-_id -__v -repoId')
.sort({ createdAt: 'desc' }) .sort({ createdAt: 'desc' })
.limit(show); .limit(show);
const finalLogs = deploy.map((d) => { const finalLogs = deploy.map((d) => {
const finalLogs = { ...d._doc }; const finalLogs = { ...d._doc };
const updatedAt = dayjs(d.updatedAt).utc(); const updatedAt = dayjs(d.updatedAt).utc();
finalLogs.took = updatedAt.diff(dayjs(d.createdAt)) / 1000; finalLogs.took = updatedAt.diff(dayjs(d.createdAt)) / 1000;
finalLogs.since = updatedAt.fromNow(); finalLogs.since = updatedAt.fromNow();
finalLogs.isPr = d.domain.startsWith('pr')
return finalLogs; return finalLogs;
}); });
return { return {
@@ -36,11 +32,10 @@ export async function get(request: Request) {
} }
}; };
} catch (error) { } catch (error) {
console.log(error);
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} }

View File

@@ -16,11 +16,12 @@ export async function get(request: Request) {
body: { success: true, logs } body: { success: true, logs }
}; };
} catch (error) { } catch (error) {
console.log(error)
await saveServerLog(error); await saveServerLog(error);
return { return {
status: 500, status: 500,
body: { body: {
error error: 'No such service. Is it under deployment?'
} }
}; };
} }

View File

@@ -1,55 +1,69 @@
import { purgeImagesContainers } from '$lib/api/applications/cleanup'; import { purgeImagesContainers } from '$lib/api/applications/cleanup';
import { docker } from '$lib/api/docker'; import Deployment from '$models/Deployment';
import Deployment from '$models/Logs/Deployment'; import ApplicationLog from '$models/ApplicationLog';
import ApplicationLog from '$models/Logs/Application';
import { delay, execShellAsync } from '$lib/api/common'; import { delay, execShellAsync } from '$lib/api/common';
import Configuration from '$models/Configuration';
async function call(found) { async function purgeImagesAsync(found) {
await delay(10000); await delay(10000);
await purgeImagesContainers(found, true); await purgeImagesContainers(found, true);
} }
export async function post(request: Request) { export async function post(request: Request) {
const { organization, name, branch } = request.body; const { organization, name, branch, domain } = request.body;
let found = false;
try { try {
(await docker.engine.listServices()) const configurationFound = await Configuration.findOne({
.filter((r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application') 'repository.organization': organization,
.map((s) => { 'repository.name': name,
const running = JSON.parse(s.Spec.Labels.configuration); 'repository.branch': branch,
if ( 'publish.domain': domain
running.repository.organization === organization && })
running.repository.name === name && if (configurationFound) {
running.repository.branch === branch const id = configurationFound._id
) { if (configurationFound?.general?.pullRequest === 0) {
found = running; // Main deployment deletion request; deleting main + PRs
const allConfiguration = await Configuration.find({
'repository.name': name,
'repository.organization': organization,
'repository.branch': branch,
})
for (const config of allConfiguration) {
await Configuration.findOneAndRemove({
'repository.name': config.repository.name,
'repository.organization': config.repository.organization,
'repository.branch': config.repository.branch,
})
await execShellAsync(`docker stack rm ${config.build.container.name}`);
} }
return null; const deploys = await Deployment.find({ organization, branch, name })
}); for (const deploy of deploys) {
if (found) { await ApplicationLog.deleteMany({ deployId: deploy.deployId });
const deploys = await Deployment.find({ organization, branch, name }); await Deployment.deleteMany({ deployId: deploy.deployId });
for (const deploy of deploys) { }
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
await Deployment.deleteMany({ deployId: deploy.deployId }); purgeImagesAsync(configurationFound);
} else {
// Delete only PRs
await Configuration.findByIdAndRemove(id)
await execShellAsync(`docker stack rm ${configurationFound.build.container.name}`);
const deploys = await Deployment.find({ organization, branch, name, domain })
for (const deploy of deploys) {
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
await Deployment.deleteMany({ deployId: deploy.deployId });
}
purgeImagesAsync(configurationFound);
} }
await execShellAsync(`docker stack rm ${found.build.container.name}`);
call(found);
return {
status: 200,
body: {
organization,
name,
branch
}
};
} else {
return {
status: 500,
error: {
message: 'Nothing to do.'
}
};
} }
return {
status: 200,
body: {
organization,
name,
branch
}
};
} catch (error) { } catch (error) {
console.log(error)
return { return {
status: 500, status: 500,
error: { error: {

View File

@@ -1,16 +1,9 @@
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import LogsServer from '$models/Logs/Server';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
import Configuration from '$models/Configuration'
export async function get(request: Request) { export async function get(request: Request) {
const serverLogs = await LogsServer.find(); // Should update this to get data from mongodb and update db with the currently running services on start!
const dockerServices = await docker.engine.listServices(); const dockerServices = await docker.engine.listServices();
let applications: any = dockerServices.filter(
(r) =>
r.Spec.Labels.managedBy === 'coolify' &&
r.Spec.Labels.type === 'application' &&
r.Spec.Labels.configuration
);
let databases: any = dockerServices.filter( let databases: any = dockerServices.filter(
(r) => (r) =>
r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.managedBy === 'coolify' &&
@@ -23,15 +16,6 @@ export async function get(request: Request) {
r.Spec.Labels.type === 'service' && r.Spec.Labels.type === 'service' &&
r.Spec.Labels.configuration r.Spec.Labels.configuration
); );
applications = applications.map((r) => {
if (JSON.parse(r.Spec.Labels.configuration)) {
return {
configuration: JSON.parse(r.Spec.Labels.configuration),
UpdatedAt: r.UpdatedAt
};
}
return {};
});
databases = databases.map((r) => { databases = databases.map((r) => {
if (JSON.parse(r.Spec.Labels.configuration)) { if (JSON.parse(r.Spec.Labels.configuration)) {
return { return {
@@ -49,19 +33,27 @@ export async function get(request: Request) {
} }
return {}; return {};
}); });
applications = [ const configurations = await Configuration.find({
...new Map( 'general.pullRequest': { '$in': [null, 0] }
applications.map((item) => [ }).select('-_id -__v -createdAt')
item.configuration.publish.domain + item.configuration.publish.path, const applications = []
item for (const configuration of configurations) {
]) const foundPRDeployments = await Configuration.find({
).values() 'repository.id': configuration.repository.id,
]; 'repository.branch': configuration.repository.branch,
'general.pullRequest': { '$ne': 0 }
}).select('-_id -__v -createdAt')
const payload = {
configuration,
UpdatedAt: configuration.updatedAt,
prBuilds: foundPRDeployments.length > 0 ? true : false,
}
applications.push(payload)
}
return { return {
status: 200, status: 200,
body: { body: {
success: true, success: true,
serverLogs,
applications: { applications: {
deployed: applications deployed: applications
}, },

View File

@@ -100,6 +100,31 @@ export async function post(request: Request) {
body: fs.readFileSync(`${fullfilename}`) body: fs.readFileSync(`${fullfilename}`)
}; };
} }
} else if (type === 'redis') {
if (databaseService) {
const password = configuration.database.passwords[0];
const databaseName = configuration.database.defaultDatabaseName;
const filename = `${databaseName}_${now.getTime()}.rdb`;
const fullfilename = `${tmpdir}/${filename}`;
await execShellAsync(
`docker exec -i ${containerID} /bin/bash -c "redis-cli --pass ${password} save"`
);
await execShellAsync(
`docker cp ${containerID}:/bitnami/redis/data/dump.rdb ${fullfilename}`
);
await execShellAsync(
`docker exec -i ${containerID} /bin/bash -c "rm -f /bitnami/redis/data/dump.rdb"`
);
return {
status: 200,
headers: {
'Content-Type': 'application/octet-stream',
'Content-Transfer-Encoding': 'binary',
'Content-Disposition': `attachment; filename=${filename}`
},
body: fs.readFileSync(`${fullfilename}`)
};
}
} }
return { return {
status: 501, status: 501,
@@ -108,12 +133,11 @@ export async function post(request: Request) {
} }
}; };
} catch (error) { } catch (error) {
console.log(error);
await saveServerLog(error); await saveServerLog(error);
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} finally { } finally {

View File

@@ -96,6 +96,12 @@ export async function post(request: Request) {
hard: 262144 hard: 262144
} }
}; };
} else if (type === 'redis') {
image = 'bitnami/redis';
volume = `${configuration.general.deployId}-${type}-data:/bitnami/redis/data`;
generateEnvs = {
REDIS_PASSWORD: passwords[0]
};
} }
const stack = { const stack = {

View File

@@ -1,10 +1,10 @@
import { githubAPI } from '$api';
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
import mongoose from 'mongoose'; import mongoose from 'mongoose';
import User from '$models/User'; import User from '$models/User';
import Settings from '$models/Settings'; import Settings from '$models/Settings';
import cuid from 'cuid'; import cuid from 'cuid';
import jsonwebtoken from 'jsonwebtoken'; import jsonwebtoken from 'jsonwebtoken';
import { githubAPI } from '$lib/api/github';
export async function get(request: Request) { export async function get(request: Request) {
const code = request.query.get('code'); const code = request.query.get('code');
@@ -17,7 +17,7 @@ export async function get(request: Request) {
{ headers: { accept: 'application/json' } } { headers: { accept: 'application/json' } }
) )
).json(); ).json();
const { avatar_url, id } = await (await githubAPI(request, '/user', access_token)).body; const { avatar_url } = await (await githubAPI(request, '/user', access_token)).body;
const email = (await githubAPI(request, '/user/emails', access_token)).body.filter( const email = (await githubAPI(request, '/user/emails', access_token)).body.filter(
(e) => e.primary (e) => e.primary
)[0].email; )[0].email;
@@ -41,11 +41,10 @@ export async function get(request: Request) {
try { try {
await newUser.save(); await newUser.save();
await defaultSettings.save(); await defaultSettings.save();
} catch (e) { } catch (error) {
console.log(e);
return { return {
status: 500, status: 500,
body: e error: error.message || error
}; };
} }
} else { } else {
@@ -73,12 +72,11 @@ export async function get(request: Request) {
}); });
try { try {
await newUser.save(); await newUser.save();
} catch (e) { } catch (error) {
console.log(e);
return { return {
status: 500, status: 500,
body: { body: {
error: e error: error.message || error
} }
}; };
} }
@@ -103,8 +101,6 @@ export async function get(request: Request) {
} }
}; };
} catch (error) { } catch (error) {
console.log('error happened'); return { status: 500, body: { error: error.message || error } };
console.log(error);
return { status: 500, body: { ...error } };
} }
} }

View File

@@ -34,12 +34,11 @@ export async function get(request: Request) {
}; };
} }
} catch (error) { } catch (error) {
console.log(error);
return { return {
status: 500, status: 500,
body: { body: {
success: false, success: false,
error error: error.message || error
} }
}; };
} }

View File

@@ -22,7 +22,7 @@ export async function get(request: Request) {
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} }
@@ -45,7 +45,7 @@ export async function post(request: Request) {
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} }

View File

@@ -10,7 +10,6 @@ export async function get(request: Request) {
execShellAsync( execShellAsync(
'docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -u root coolify bash -c "$(curl -fsSL https://get.coollabs.io/coolify/upgrade-p2.sh)"' 'docker run --rm -v /var/run/docker.sock:/var/run/docker.sock -u root coolify bash -c "$(curl -fsSL https://get.coollabs.io/coolify/upgrade-p2.sh)"'
); );
// saveServerLog({ message: upgradeP2, type: 'UPGRADE-P-2' })
return { return {
status: 200, status: 200,
body: { body: {

View File

@@ -1,24 +0,0 @@
// import { deleteCookies } from '$lib/api/common';
// import { verifyUserId } from '$lib/api/common';
// import type { Request } from '@sveltejs/kit';
// import * as cookie from 'cookie';
// export async function post(request: Request) {
// const { coolToken } = cookie.parse(request.headers.cookie || '');
// try {
// await verifyUserId(coolToken);
// return {
// status: 200,
// body: { success: true }
// };
// } catch (error) {
// return {
// status: 301,
// headers: {
// location: '/',
// 'set-cookie': [...deleteCookies]
// },
// body: { error: 'Unauthorized' }
// };
// }
// }

View File

@@ -1,13 +1,19 @@
import type { Request } from '@sveltejs/kit'; import type { Request } from '@sveltejs/kit';
import crypto from 'crypto'; import crypto from 'crypto';
import Deployment from '$models/Logs/Deployment'; import Deployment from '$models/Deployment';
import { docker } from '$lib/api/docker'; import { docker } from '$lib/api/docker';
import { precheckDeployment, setDefaultConfiguration } from '$lib/api/applications/configuration'; import { precheckDeployment, setDefaultConfiguration } from '$lib/api/applications/configuration';
import cloneRepository from '$lib/api/applications/cloneRepository'; import cloneRepository from '$lib/api/applications/cloneRepository';
import { cleanupTmp } from '$lib/api/common'; import { cleanupTmp, execShellAsync } from '$lib/api/common';
import queueAndBuild from '$lib/api/applications/queueAndBuild'; import queueAndBuild from '$lib/api/applications/queueAndBuild';
import Configuration from '$models/Configuration';
import ApplicationLog from '$models/ApplicationLog';
import { cleanupStuckedDeploymentsInDB } from '$lib/api/applications/cleanup';
export async function post(request: Request) { export async function post(request: Request) {
let configuration; let configuration;
const allowedGithubEvents = ['push', 'pull_request']
const allowedPRActions = ['opened', , 'reopened', 'synchronize', 'closed']
const githubEvent = request.headers['x-github-event']
const { GITHUP_APP_WEBHOOK_SECRET } = process.env; const { GITHUP_APP_WEBHOOK_SECRET } = process.env;
const hmac = crypto.createHmac('sha256', GITHUP_APP_WEBHOOK_SECRET); const hmac = crypto.createHmac('sha256', GITHUP_APP_WEBHOOK_SECRET);
const digest = Buffer.from( const digest = Buffer.from(
@@ -19,52 +25,92 @@ export async function post(request: Request) {
return { return {
status: 500, status: 500,
body: { body: {
error: 'Invalid request' error: 'Invalid request.'
} }
}; };
} }
if (request.headers['x-github-event'] !== 'push') { if (!allowedGithubEvents.includes(githubEvent)) {
return { return {
status: 500, status: 500,
body: { body: {
error: 'Not a push event.' error: 'Event not allowed.'
} }
}; };
} }
try { try {
const services = (await docker.engine.listServices()).filter( const applications = await Configuration.find({
(r) => r.Spec.Labels.managedBy === 'coolify' && r.Spec.Labels.type === 'application' 'repository.id': request.body.repository.id,
); }).select('-_id -__v -createdAt -updatedAt')
if (githubEvent === 'push') {
configuration = services.find((r) => { configuration = applications.find((r) => {
if (request.body.ref.startsWith('refs')) { if (request.body.ref.startsWith('refs')) {
const branch = request.body.ref.split('/')[2]; if (r.repository.branch === request.body.ref.split('/')[2]) {
if ( return r;
JSON.parse(r.Spec.Labels.configuration).repository.id === request.body.repository.id && }
JSON.parse(r.Spec.Labels.configuration).repository.branch === branch
) {
return r;
} }
return null;
});
} else if (githubEvent === 'pull_request') {
if (!allowedPRActions.includes(request.body.action)) {
return {
status: 500,
body: {
error: 'PR action is not allowed.'
}
};
} }
configuration = applications.find((r) => r.repository.branch === request.body['pull_request'].base.ref);
return null; if (configuration) {
}); if (!configuration.general.isPreviewDeploymentEnabled) {
configuration = setDefaultConfiguration(JSON.parse(configuration.Spec.Labels.configuration)); return {
status: 500,
body: {
error: 'PR deployments are not enabled.'
}
};
}
configuration.general.pullRequest = request.body.number
}
}
if (!configuration) { if (!configuration) {
return { return {
status: 500, status: 500,
body: { body: {
error: 'Whaaat?' error: 'No configuration found.'
}
};
}
configuration = setDefaultConfiguration(configuration);
const { id, organization, name, branch } = configuration.repository;
const { domain } = configuration.publish;
const { deployId, nickname, pullRequest } = configuration.general;
if (request.body.action === 'closed') {
const deploys = await Deployment.find({ organization, branch, name, domain });
for (const deploy of deploys) {
await ApplicationLog.deleteMany({ deployId: deploy.deployId });
await Deployment.deleteMany({ deployId: deploy.deployId });
}
await Configuration.findOneAndRemove({
'repository.id': id,
'repository.organization': organization,
'repository.name': name,
'repository.branch': branch,
'general.pullRequest': pullRequest
})
await execShellAsync(`docker stack rm ${configuration.build.container.name}`);
return {
status: 200,
body: {
success: true,
message: 'Removed'
} }
}; };
} }
await cloneRepository(configuration); await cloneRepository(configuration);
const { foundService, imageChanged, configChanged, forceUpdate } = await precheckDeployment({ const { foundService, imageChanged, configChanged, forceUpdate } = await precheckDeployment(configuration);
services,
configuration
});
if (foundService && !forceUpdate && !imageChanged && !configChanged) { if (foundService && !forceUpdate && !imageChanged && !configChanged) {
cleanupTmp(configuration.general.workdir); cleanupTmp(configuration.general.workdir);
return { return {
@@ -76,11 +122,11 @@ export async function post(request: Request) {
}; };
} }
const alreadyQueued = await Deployment.find({ const alreadyQueued = await Deployment.find({
repoId: configuration.repository.id, repoId: id,
branch: configuration.repository.branch, branch: branch,
organization: configuration.repository.organization, organization: organization,
name: configuration.repository.name, name: name,
domain: configuration.publish.domain, domain: domain,
progress: { $in: ['queued', 'inprogress'] } progress: { $in: ['queued', 'inprogress'] }
}); });
if (alreadyQueued.length > 0) { if (alreadyQueued.length > 0) {
@@ -92,6 +138,40 @@ export async function post(request: Request) {
} }
}; };
} }
await new Deployment({
repoId: id,
branch,
deployId,
domain,
organization,
name,
nickname
}).save();
if (githubEvent === 'pull_request') {
await Configuration.findOneAndUpdate({
'repository.id': id,
'repository.organization': organization,
'repository.name': name,
'repository.branch': branch,
'general.pullRequest': pullRequest
},
{ ...configuration },
{ upsert: true, new: true })
} else {
await Configuration.findOneAndUpdate({
'repository.id': id,
'repository.organization': organization,
'repository.name': name,
'repository.branch': branch,
'general.pullRequest': { '$in': [null, 0] }
},
{ ...configuration },
{ upsert: true, new: true })
}
queueAndBuild(configuration, imageChanged); queueAndBuild(configuration, imageChanged);
return { return {
status: 201, status: 201,
@@ -103,11 +183,40 @@ export async function post(request: Request) {
} }
}; };
} catch (error) { } catch (error) {
console.log(error)
// console.log(configuration)
if (configuration) {
cleanupTmp(configuration.general.workdir);
await Deployment.findOneAndUpdate(
{
repoId: configuration.repository.id,
branch: configuration.repository.branch,
organization: configuration.repository.organization,
name: configuration.repository.name,
domain: configuration.publish.domain
},
{
repoId: configuration.repository.id,
branch: configuration.repository.branch,
organization: configuration.repository.organization,
name: configuration.repository.name,
domain: configuration.publish.domain,
progress: 'failed'
}
);
}
return { return {
status: 500, status: 500,
body: { body: {
error error: error.message || error
} }
}; };
} finally {
try {
await cleanupStuckedDeploymentsInDB();
} catch (error) {
console.log(error)
}
} }
} }

View File

@@ -2,7 +2,7 @@
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { browser } from '$app/env'; import { browser } from '$app/env';

View File

@@ -5,7 +5,7 @@
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { session } from '$app/stores'; import { session } from '$app/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
@@ -84,13 +84,15 @@
deployment.progress !== 'failed'} deployment.progress !== 'failed'}
class:bg-warmGray-800={deployment.progress !== 'done' && class:bg-warmGray-800={deployment.progress !== 'done' &&
deployment.progress !== 'failed'} deployment.progress !== 'failed'}
class:hover:bg-red-200={deployment.progress === 'failed'}
class:hover:border-red-500={deployment.progress === 'failed'} class:hover:border-red-500={deployment.progress === 'failed'}
on:click={() => goto(`./logs/${deployment.deployId}`)} on:click={() => goto(`./logs/${deployment.deployId}`)}
> >
<div class="font-bold text-sm px-3 flex justify-center items-center"> <div class="flex space-x-2 px-2">
{deployment.branch} <div class="font-bold text-sm flex justify-center items-center">
{deployment.branch}
</div> </div>
<div class="font-bold text-xs flex justify-center items-center text-warmGray-500">{deployment.isPr ? 'PR' : ''}</div>
</div>
<div class="flex-1" /> <div class="flex-1" />
<div class="px-3 w-48"> <div class="px-3 w-48">
<div <div

View File

@@ -1,12 +1,12 @@
<script> <script>
import { application, initialApplication, initConf, dashboard } from '$store'; import { application, initialApplication, initConf, dashboard, prApplication } from '$store';
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import Navbar from '$components/Application/Navbar.svelte'; import Navbar from '$components/Application/Navbar.svelte';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { browser } from '$app/env'; import { browser } from '$app/env';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
$application.repository.organization = $page.params.organization; $application.repository.organization = $page.params.organization;
$application.repository.name = $page.params.name; $application.repository.name = $page.params.name;
@@ -14,15 +14,16 @@
async function setConfiguration() { async function setConfiguration() {
try { try {
const config = await request(`/api/v1/application/config`, $session, { const { configuration } = await request(`/api/v1/application/config`, $session, {
body: { body: {
name: $application.repository.name, name: $application.repository.name,
organization: $application.repository.organization, organization: $application.repository.organization,
branch: $application.repository.branch branch: $application.repository.branch
} }
}); });
$application = { ...config }; $prApplication = configuration.filter((c) => c.general.pullRequest !== 0);
$initConf = JSON.parse(JSON.stringify($application)); $application = configuration.find((c) => c.general.pullRequest === 0);
if (!$application) browser && goto('/dashboard/applications');
} catch (error) { } catch (error) {
browser && goto('/dashboard/applications'); browser && goto('/dashboard/applications');
} }

View File

@@ -1,5 +1,5 @@
<script context="module" lang="ts"> <script context="module" lang="ts">
import { request } from '$lib/api/request'; import { request } from '$lib/request';
/** /**
* @type {import('@sveltejs/kit').Load} * @type {import('@sveltejs/kit').Load}
*/ */

File diff suppressed because one or more lines are too long

View File

@@ -7,6 +7,8 @@
import Mysql from '$components/Database/SVGs/Mysql.svelte'; import Mysql from '$components/Database/SVGs/Mysql.svelte';
import { dashboard } from '$store'; import { dashboard } from '$store';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import Redis from '$components/Database/SVGs/Redis.svelte';
</script> </script>
<div class="py-5 text-left px-6 text-3xl tracking-tight font-bold flex items-center"> <div class="py-5 text-left px-6 text-3xl tracking-tight font-bold flex items-center">
@@ -56,6 +58,10 @@
<CouchDb <CouchDb
customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4" customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4"
/> />
{:else if database.configuration.general.type == 'redis'}
<Redis
customClass="w-10 h-10 absolute top-0 left-0 -m-4"
/>
{:else if database.configuration.general.type == 'clickhouse'} {:else if database.configuration.general.type == 'clickhouse'}
<Clickhouse <Clickhouse
customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4" customClass="w-10 h-10 fill-current text-red-600 absolute top-0 left-0 -m-4"
@@ -79,4 +85,3 @@
<div class="text-2xl font-bold text-center">No databases found</div> <div class="text-2xl font-bold text-center">No databases found</div>
{/if} {/if}
</div> </div>

View File

@@ -1,7 +1,7 @@
<script> <script>
import { database } from '$store'; import { database } from '$store';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import MongoDb from '$components/Database/SVGs/MongoDb.svelte'; import MongoDb from '$components/Database/SVGs/MongoDb.svelte';
@@ -12,10 +12,11 @@
import PasswordField from '$components/PasswordField.svelte'; import PasswordField from '$components/PasswordField.svelte';
import { browser } from '$app/env'; import { browser } from '$app/env';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import Redis from '$components/Database/SVGs/Redis.svelte';
async function backup() { async function backup() {
try { try {
await request(`/api/v1/databases/${$page.params.name}/backup`, $session, {body: {}}); await request(`/api/v1/databases/${$page.params.name}/backup`, $session, { body: {} });
browser && toast.push(`Successfully created backup.`); browser && toast.push(`Successfully created backup.`);
} catch (error) { } catch (error) {
@@ -56,6 +57,8 @@
<Mysql customClass="w-8 h-8" /> <Mysql customClass="w-8 h-8" />
{:else if $database.config.general.type === 'couchdb'} {:else if $database.config.general.type === 'couchdb'}
<CouchDb customClass="w-8 h-8 fill-current text-red-600" /> <CouchDb customClass="w-8 h-8 fill-current text-red-600" />
{:else if $database.config.general.type === 'redis'}
<Redis customClass="w-8 h-8" />
{/if} {/if}
</div> </div>
</div> </div>
@@ -81,6 +84,10 @@
<PasswordField <PasswordField
value={`http://${$database.envs.COUCHDB_USER}:${$database.envs.COUCHDB_PASSWORD}@${$database.config.general.deployId}:5984`} value={`http://${$database.envs.COUCHDB_USER}:${$database.envs.COUCHDB_PASSWORD}@${$database.config.general.deployId}:5984`}
/> />
{:else if $database.config.general.type === 'redis'}
<PasswordField
value={`redis://${$database.envs.REDIS_PASSWORD}@${$database.config.general.deployId}:6379`}
/>
{:else if $database.config.general.type === 'clickhouse'} {:else if $database.config.general.type === 'clickhouse'}
<!-- {JSON.stringify($database)} --> <!-- {JSON.stringify($database)} -->
<!-- <textarea <!-- <textarea
@@ -97,6 +104,12 @@
<PasswordField value={$database.envs.MONGODB_ROOT_PASSWORD} /> <PasswordField value={$database.envs.MONGODB_ROOT_PASSWORD} />
</div> </div>
{/if} {/if}
{#if $database.config.general.type === 'redis'}
<div class="flex items-center">
<div class="font-bold w-64 text-warmGray-400">Redis password</div>
<PasswordField value={$database.envs.REDIS_PASSWORD} />
</div>
{/if}
<div class="pb-2 pt-5 space-y-4"> <div class="pb-2 pt-5 space-y-4">
<div class="text-2xl font-bold border-gradient w-32">Backup</div> <div class="text-2xl font-bold border-gradient w-32">Backup</div>
<div class="pt-4"> <div class="pt-4">

View File

@@ -4,7 +4,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import Tooltip from '$components/Tooltip.svelte'; import Tooltip from '$components/Tooltip.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { database, initialDatabase } from '$store'; import { database, initialDatabase } from '$store';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { onDestroy } from 'svelte'; import { onDestroy } from 'svelte';

View File

@@ -3,7 +3,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { session } from '$app/stores'; import { session } from '$app/stores';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
async function login() { async function login() {
const left = screen.width / 2 - 1020 / 2; const left = screen.width / 2 - 1020 / 2;
@@ -20,7 +20,7 @@
', toolbar=0, menubar=0, status=0' ', toolbar=0, menubar=0, status=0'
); );
const timer = setInterval(() => { const timer = setInterval(() => {
if (newWindow.closed) { if (newWindow?.closed) {
clearInterval(timer); clearInterval(timer);
browser && location.reload() browser && location.reload()
} }

View File

@@ -5,7 +5,7 @@
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import Tooltip from '$components/Tooltip.svelte'; import Tooltip from '$components/Tooltip.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';

View File

@@ -3,7 +3,7 @@
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import Plausible from '$components/Service/Plausible.svelte'; import Plausible from '$components/Service/Plausible.svelte';

View File

@@ -5,7 +5,7 @@
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { initialNewService, newService } from '$store'; import { initialNewService, newService } from '$store';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';

View File

@@ -4,7 +4,7 @@
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { newService } from '$store'; import { newService } from '$store';
import { page, session } from '$app/stores'; import { page, session } from '$app/stores';
import { request } from '$lib/api/request'; import { request } from '$lib/request';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import Loading from '$components/Loading.svelte'; import Loading from '$components/Loading.svelte';
import TooltipInfo from '$components/TooltipInfo.svelte'; import TooltipInfo from '$components/TooltipInfo.svelte';

View File

@@ -26,10 +26,10 @@
<script> <script>
export let allowRegistration; export let allowRegistration;
export let sendErrors; export let sendErrors;
import { browser } from '$app/env'; import { browser } from '$app/env';
import { session } from '$app/stores'; import { session } from '$app/stores';
import { request } from '$lib/request';
import { request } from '$lib/api/request';
import { toast } from '@zerodevx/svelte-toast'; import { toast } from '@zerodevx/svelte-toast';
import { fade } from 'svelte/transition'; import { fade } from 'svelte/transition';
let settings = { let settings = {

View File

@@ -5,7 +5,7 @@ import type {
DateTimeFormatOptions, DateTimeFormatOptions,
GithubInstallations GithubInstallations
} from 'src/global'; } from 'src/global';
import { writable, derived, readable, Writable } from 'svelte/store'; import { writable } from 'svelte/store';
export const dashboard = writable<Dashboard>({ export const dashboard = writable<Dashboard>({
databases: { databases: {
@@ -48,14 +48,21 @@ export const application = writable<Application>({
general: { general: {
deployId: null, deployId: null,
nickname: null, nickname: null,
workdir: null workdir: null,
isPreviewDeploymentEnabled: false,
pullRequest: 0
}, },
build: { build: {
pack: 'static', pack: 'static',
directory: null, directory: null,
command: { command: {
build: null, build: null,
installation: null installation: null,
start: null,
python: {
module: null,
instance: null
}
}, },
container: { container: {
name: null, name: null,
@@ -71,6 +78,7 @@ export const application = writable<Application>({
secrets: [] secrets: []
} }
}); });
export const prApplication = writable([])
export const initConf = writable({}); export const initConf = writable({});
@@ -92,14 +100,21 @@ export const initialApplication: Application = {
general: { general: {
deployId: null, deployId: null,
nickname: null, nickname: null,
workdir: null workdir: null,
isPreviewDeploymentEnabled: false,
pullRequest: 0
}, },
build: { build: {
pack: 'static', pack: 'static',
directory: null, directory: null,
command: { command: {
build: null, build: null,
installation: null installation: null,
start: null,
python: {
module: null,
instance: null
}
}, },
container: { container: {
name: null, name: null,
@@ -152,3 +167,5 @@ export const initialNewService = {
userPasswordAgain: null, userPasswordAgain: null,
baseURL: null baseURL: null
}; };
export const isPullRequestPermissionsGranted = writable(false)

View File

@@ -30,7 +30,7 @@ export default {
alias: { alias: {
$components: path.resolve('./src/components/'), $components: path.resolve('./src/components/'),
$store: path.resolve('./src/store/index.ts'), $store: path.resolve('./src/store/index.ts'),
$api: path.resolve('./src/routes/api/_index.ts'), $api: path.resolve('./src/routes/api/'),
$models: path.resolve('./src/models/') $models: path.resolve('./src/models/')
} }
} }

View File

@@ -8,30 +8,6 @@ const svelteClassColonExtractor = (content) => {
module.exports = { module.exports = {
mode: 'jit', mode: 'jit',
purge: ['./**/*.html', './src/**/*.{js,jsx,ts,tsx,svelte}'], purge: ['./**/*.html', './src/**/*.{js,jsx,ts,tsx,svelte}'],
// purge: {
// enabled: process.env.NODE_ENV === 'production',
// content: ['./src/**/*.svelte', './src/**/*.html', './src/**/*.css', './index.html'],
// preserveHtmlElements: true,
// options: {
// safelist: [
// /svelte-/,
// 'border-green-500',
// 'border-yellow-300',
// 'border-red-500',
// 'hover:border-green-500',
// 'hover:border-red-200',
// 'hover:bg-red-200',
// 'hover:bg-warmGray-900',
// 'hover:bg-transparent'
// ],
// defaultExtractor: (content) => {
// // WARNING: tailwindExtractor is internal tailwind api
// // if this breaks after a tailwind update, report to svite repo
// return [...tailwindExtractor(content), ...svelteClassColonExtractor(content)];
// },
// keyframes: false
// }
// },
important: true, important: true,
theme: { theme: {
extend: { extend: {

View File

@@ -26,7 +26,7 @@
"paths": { "paths": {
"$lib/*": ["src/lib/*"], "$lib/*": ["src/lib/*"],
"$store": ["src/store/index.ts"], "$store": ["src/store/index.ts"],
"$api": ["src/routes/api/_index.ts"], "$api/*": ["src/routes/api/*"],
"$models/*": ["src/models/*"], "$models/*": ["src/models/*"],
"$components/*": ["src/components/*"] "$components/*": ["src/components/*"]
} }