prototype of the deploys page using the inspect component

main
Antonio De Lucreziis 11 months ago
parent d997295848
commit 6ffa9ca777

@ -0,0 +1 @@
CONFIG_PATH=config.yaml

@ -0,0 +1,9 @@
{
"printWidth": 100,
"singleQuote": true,
"quoteProps": "consistent",
"tabWidth": 4,
"semi": false,
"arrowParens": "avoid",
"proseWrap": "always"
}

@ -23,11 +23,19 @@ deploys:
branch: main branch: main
type: dockerfile type: dockerfile
options: options:
path: ./Dockerfile ports:
- 80:8080
volumes:
- /var/www/html:/var/www/html
- name: project2 - name: project2
url: ssh://example.org/username/project2.git url: ssh://example.org/username/project2.git
commit: 04c540647a commit: 04c540647a
type: docker-compose type: docker-compose
options: options:
path: ./docker-compose.yml path: ./docker-compose.yml # already the default
- name: project3
url: https://github.com/username/project3
type: shell
options:
path: ./deploy.sh # already the default
``` ```

@ -2,9 +2,18 @@ deploys:
- name: project1 - name: project1
url: https://github.com/username/project1 url: https://github.com/username/project1
branch: main branch: main
type: docker
options:
ports:
- 80:8080
volumes:
- /var/www/html:/var/www/html
- name: project2
url: https://github.com/username/project2
type: dockerfile type: dockerfile
options: options:
port: 80:8080 ports:
- 9000:8080
volumes: volumes:
- /var/www/html:/var/www/html - /var/www/html:/var/www/html
- name: project2 - name: project2

@ -20,6 +20,7 @@
"async-mutex": "^0.4.1", "async-mutex": "^0.4.1",
"dockerode": "^4.0.2", "dockerode": "^4.0.2",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"nodegit": "^0.27.0", "nodegit": "^0.27.0",
"preact": "^10.19.4", "preact": "^10.19.4",
"typescript": "^5.3.3" "typescript": "^5.3.3"
@ -27,6 +28,7 @@
"devDependencies": { "devDependencies": {
"@types/dockerode": "^3.3.23", "@types/dockerode": "^3.3.23",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/lodash": "^4.14.202",
"sass": "^1.70.0" "sass": "^1.70.0"
} }
} }

@ -35,6 +35,9 @@ dependencies:
js-yaml: js-yaml:
specifier: ^4.1.0 specifier: ^4.1.0
version: 4.1.0 version: 4.1.0
lodash:
specifier: ^4.17.21
version: 4.17.21
nodegit: nodegit:
specifier: ^0.27.0 specifier: ^0.27.0
version: 0.27.0 version: 0.27.0
@ -52,6 +55,9 @@ devDependencies:
'@types/js-yaml': '@types/js-yaml':
specifier: ^4.0.9 specifier: ^4.0.9
version: 4.0.9 version: 4.0.9
'@types/lodash':
specifier: ^4.14.202
version: 4.14.202
sass: sass:
specifier: ^1.70.0 specifier: ^1.70.0
version: 1.70.0 version: 1.70.0
@ -1000,6 +1006,10 @@ packages:
'@types/node': 20.11.17 '@types/node': 20.11.17
dev: false dev: false
/@types/lodash@4.14.202:
resolution: {integrity: sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==}
dev: true
/@types/mdast@4.0.3: /@types/mdast@4.0.3:
resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==} resolution: {integrity: sha512-LsjtqsyF+d2/yFOYaN22dHZI1Cpwkrj+g06G8+qtUKlhovPW89YhqSnfKtMbkgmEtYpH2gydRNULd6y8mciAFg==}
dependencies: dependencies:

@ -0,0 +1,9 @@
import { Value } from './Inspect.jsx'
export const Deploy = ({ deploy }) => {
return (
<div class="deploy">
<Value value={deploy} borderless />
</div>
)
}

@ -0,0 +1,41 @@
import _ from 'lodash'
import { clsx } from '../utils.js'
export const Value = ({ value, borderless }) => {
return Array.isArray(value) ? (
<ValueArray value={value} borderless={borderless} />
) : typeof value === 'object' ? (
<ValueObject value={value} borderless={borderless} />
) : (
value
)
}
export const ValueArray = ({ value, borderless }) => {
return (
<div class={clsx('compound-value array', borderless && 'borderless')}>
{value.map(item => (
<div class="item">
<Value value={item} borderless={borderless} />
</div>
))}
</div>
)
}
export const ValueObject = ({ value, borderless }) => {
return (
<div class={clsx('compound-value object', borderless && 'borderless')}>
{Object.entries(value).map(([k, v]) => (
<>
<div title={k} class="key">
{_.startCase(k)}
</div>
<div class="value">
<Value value={v} borderless={borderless} />
</div>
</>
))}
</div>
)
}

@ -10,27 +10,50 @@ export const NewDeployForm = () => {
<input id="deploy-name" name="deploy-name" type="text" placeholder="Project name..." /> <input id="deploy-name" name="deploy-name" type="text" placeholder="Project name..." />
<label for="deploy-url">Url</label> <label for="deploy-url">Url</label>
<input id="deploy-url" name="deploy-url" type="text" placeholder="Valid git clone url..." /> <input
id="deploy-url"
name="deploy-url"
type="text"
placeholder="Valid git clone url..."
/>
<label for="deploy-ref-type">Ref</label> <label for="deploy-ref-type">Ref</label>
<div class="compound"> <div class="compound">
<select id="deploy-ref-type" name="deploy-ref-type" value={refType} onChange={e => setRefType(e.target.value)}> <select
id="deploy-ref-type"
name="deploy-ref-type"
value={refType}
onChange={e => setRefType(e.target.value)}
>
<option value="default">Default</option> <option value="default">Default</option>
<option value="branch">Branch</option> <option value="branch">Branch</option>
<option value="tag">Tag</option> <option value="tag">Tag</option>
<option value="commit">Commit</option> <option value="commit">Commit</option>
</select> </select>
<input class="fill" id="deploy-ref-value" name="deploy-ref-value" disabled={refType === 'default'} type="text" placeholder="Ref value..." /> <input
class="fill"
id="deploy-ref-value"
name="deploy-ref-value"
disabled={refType === 'default'}
type="text"
placeholder="Ref value..."
/>
</div> </div>
<label for="deploy-type">Type</label> <label for="deploy-type">Type</label>
<select id="deploy-type" name="deploy-type" value={deployType} onChange={e => setDeployType(e.target.value)}> <select
id="deploy-type"
name="deploy-type"
value={deployType}
onChange={e => setDeployType(e.target.value)}
>
<option value="initial" disabled> <option value="initial" disabled>
Select a deploy type... Select a deploy type...
</option> </option>
<option value="shell">Shell</option> <option value="docker">Docker</option>
<option value="dockerfile">Dockerfile</option> <option value="dockerfile">Dockerfile</option>
<option value="docker-compose">Docker Compose</option> <option value="docker-compose">Docker Compose</option>
<option value="shell">Shell</option>
</select> </select>
<DeployOptions type={deployType} /> <DeployOptions type={deployType} />
@ -44,12 +67,14 @@ export const NewDeployForm = () => {
export const DeployOptions = ({ type }) => { export const DeployOptions = ({ type }) => {
switch (type) { switch (type) {
case 'shell': case 'docker':
return <ShellDeploy />
case 'dockerfile':
return <DockerDeploy /> return <DockerDeploy />
case 'dockerfile':
return <DockerfileDeploy />
case 'docker-compose': case 'docker-compose':
return <DockerComposeDeploy /> return <DockerComposeDeploy />
case 'shell':
return <ShellDeploy />
default: default:
return null return null
} }
@ -58,20 +83,75 @@ export const DeployOptions = ({ type }) => {
const DockerDeploy = () => { const DockerDeploy = () => {
return ( return (
<> <>
<label for="deploy-options-path">Path</label>
<input id="deploy-options-path" name="deploy-options-path" type="text" placeholder="Default is ./Dockerfile..." />
<label for="deploy-options-image">Image</label> <label for="deploy-options-image">Image</label>
<input id="deploy-options-image" name="deploy-options-image" type="text" placeholder="organization/image:latest" /> <input
id="deploy-options-image"
name="deploy-options-image"
type="text"
placeholder="organization/image:latest"
/>
<label for="deploy-options-ports">Ports</label>
<textarea
id="deploy-options-ports"
name="deploy-options-ports"
rows={2}
placeholder="80:8080"
/>
<label for="deploy-options-env">Environment</label>
<textarea
id="deploy-options-env"
name="deploy-options-env"
rows={2}
placeholder="FOO=bar"
/>
<label for="deploy-options-volumes">Volumes</label>
<textarea
id="deploy-options-volumes"
name="deploy-options-volumes"
rows={2}
placeholder="/var/www/example:/data"
/>
</>
)
}
const DockerfileDeploy = () => {
return (
<>
<label for="deploy-options-path">Path</label>
<input
id="deploy-options-path"
name="deploy-options-path"
type="text"
placeholder="./Dockerfile"
/>
<label for="deploy-options-ports">Ports</label> <label for="deploy-options-ports">Ports</label>
<textarea id="deploy-options-ports" name="deploy-options-ports" rows={2} placeholder="80:8080" /> <textarea
id="deploy-options-ports"
name="deploy-options-ports"
rows={2}
placeholder="80:8080..."
/>
<label for="deploy-options-env">Environment</label> <label for="deploy-options-env">Environment</label>
<textarea id="deploy-options-env" name="deploy-options-env" rows={2} placeholder="FOO=bar" /> <textarea
id="deploy-options-env"
name="deploy-options-env"
rows={2}
placeholder="FOO=bar"
/>
<label for="deploy-options-volumes">Volumes</label> <label for="deploy-options-volumes">Volumes</label>
<textarea id="deploy-options-volumes" name="deploy-options-volumes" rows={2} placeholder="/var/www/example:/data" /> <textarea
id="deploy-options-volumes"
name="deploy-options-volumes"
rows={2}
placeholder="/var/www/example:/data"
/>
</> </>
) )
} }
@ -80,10 +160,20 @@ const ShellDeploy = () => {
return ( return (
<> <>
<label for="deploy-options-path">Path</label> <label for="deploy-options-path">Path</label>
<input id="deploy-options-path" name="deploy-options-path" type="text" placeholder="./deploy.sh" /> <input
id="deploy-options-path"
name="deploy-options-path"
type="text"
placeholder="./deploy.sh"
/>
<label for="deploy-options-env">Environment</label> <label for="deploy-options-env">Environment</label>
<textarea id="deploy-options-env" name="deploy-options-env" rows={2} placeholder="FOO=bar" /> <textarea
id="deploy-options-env"
name="deploy-options-env"
rows={2}
placeholder="FOO=bar"
/>
</> </>
) )
} }
@ -92,7 +182,12 @@ const DockerComposeDeploy = () => {
return ( return (
<> <>
<label for="deploy-options-path">Path</label> <label for="deploy-options-path">Path</label>
<input id="deploy-options-path" name="deploy-options-path" type="text" /> <input
id="deploy-options-path"
name="deploy-options-path"
type="text"
placeholder="./docker-compose.yml"
/>
</> </>
) )
} }

@ -3,46 +3,61 @@ import yaml from 'js-yaml'
import { readFile, writeFile } from 'fs/promises' import { readFile, writeFile } from 'fs/promises'
import { Mutex } from 'async-mutex' import { Mutex } from 'async-mutex'
type GitRef = { commit: string } | { branch: string } | { tag: string } export type GitRef = { type: 'default' } | { type: 'branch' | 'tag' | 'commit'; value: string }
type BaseDeploy = { export type BaseDeploy = { name: string }
name: string
export type BaseGitDeploy = BaseDeploy & {
url: string url: string
} & GitRef ref: GitRef
}
type DockerDeploy = BaseDeploy & { export type DockerDeploy = BaseDeploy & {
type: 'docker' type: 'docker'
options: { options: {
path?: string
image: string image: string
name?: string
volumes?: string[] volumes?: string[]
ports?: string[] ports?: string[]
env?: Record<string, string> env?: Record<string, string>
} }
} }
type DockerComposeDeploy = BaseDeploy & { export type DockerfileDeploy = BaseGitDeploy & {
type: 'dockerfile'
options: {
path?: string
name?: string
volumes?: string[]
ports?: string[]
env?: Record<string, string>
}
}
export type DockerComposeDeploy = BaseGitDeploy & {
type: 'docker-compose' type: 'docker-compose'
options: { options: {
path?: string path?: string
} }
} }
type ShellDeploy = BaseDeploy & { export type ShellDeploy = BaseGitDeploy & {
type: 'shell' type: 'shell'
options: { options: {
path?: string path?: string
env?: Record<string, string>
} }
} }
type Deploy = DockerDeploy | DockerComposeDeploy | ShellDeploy export type Deploy = DockerDeploy | DockerfileDeploy | DockerComposeDeploy | ShellDeploy
type Config = { export type Config = {
deploys: Deploy[] deploys: Deploy[]
} }
export const refToString = (ref: GitRef) => { export const refToString = (ref: GitRef) => {
return 'commit' in ref ? ref.commit : 'branch' in ref ? ref.branch : 'tag' in ref ? ref.tag : '<default>' return ref.type === 'default' ? '<default>' : ref.value
} }
const mutex = new Mutex() const mutex = new Mutex()

@ -1,36 +1,15 @@
--- ---
import { Deploy } from '../../client/Deploy'
import { loadConfig, refToString } from '../../config' import { loadConfig, refToString } from '../../config'
import Layout from '../../layouts/Layout.astro' import Layout from '../../layouts/Layout.astro'
const { deploys } = await loadConfig() const { deploys } = await loadConfig()
--- ---
<Layout title="Deplots | phCD"> <Layout title="Deploys | phCD">
<h1>Deploys</h1> <h1>Deploys</h1>
<a class="button" href="/deploys/new">New Deploy</a> <a class="button" href="/deploys/new">New Deploy</a>
<ul> <div class="deploys">
{ {deploys.map(deploy => <Deploy deploy={deploy} />)}
deploys.map(deploy => ( </div>
<li>
<strong>Name: </strong>
{deploy.name} <br />
<strong>URL: </strong>
{deploy.url.startsWith('http') ? (
<a href={deploy.url} target="_blank">
{deploy.url}
</a>
) : (
deploy.url
)}
<br />
<strong>Ref: </strong>
{refToString(deploy)} <br />
<strong>Type: </strong>
{deploy.type} <br />
<strong>Path: </strong>
{deploy.options.path} <br />
</li>
))
}
</ul>
</Layout> </Layout>

@ -104,7 +104,7 @@ textarea {
width: auto; width: auto;
&:not(textarea) { &:not(textarea) {
height: 32px; height: 2rem;
} }
font-size: 18px; font-size: 18px;
@ -116,7 +116,7 @@ textarea {
border: 1px solid #ddd; border: 1px solid #ddd;
border-radius: 6px; border-radius: 6px;
padding: 5px 0.25rem; padding: 0 0.25rem;
display: flex; display: flex;
align-items: center; align-items: center;
@ -132,6 +132,11 @@ textarea {
} }
} }
textarea {
min-height: 2rem;
padding: 7px 0.25rem;
}
form { form {
width: 100%; width: 100%;
@ -144,7 +149,7 @@ form {
border: 1px solid #ddd; border: 1px solid #ddd;
grid-template-columns: minmax(7rem, auto) 1fr; grid-template-columns: minmax(7rem, auto) 1fr;
gap: 0.75rem 1rem; gap: 0.5rem;
align-items: start; align-items: start;
@ -180,9 +185,99 @@ form {
display: flex; display: flex;
align-items: center; align-items: center;
height: 32px; height: 2rem;
font-weight: 700;
}
}
.object {
display: grid;
grid-template-columns: auto 1fr;
border: 1px solid #ddd;
width: 100%;
& > .key {
font-weight: 700; font-weight: 700;
display: flex;
align-items: center;
min-height: 2rem;
padding: 0 1rem 0 0.25rem;
}
& > .value {
min-height: 2rem;
padding: 0.25rem;
display: flex;
align-items: center;
border-left: 1px solid #ddd;
}
& > .key,
& > .value {
border-top: 1px solid #ddd;
} }
& > .key:nth-child(1) {
border-top: none;
}
& > .value:nth-child(2) {
border-top: none;
}
}
.array {
display: grid;
border: 1px solid #ddd;
width: 100%;
& > .item {
display: flex;
align-items: center;
min-height: 2rem;
padding: 0.25rem;
}
& > .item {
border-top: 1px solid #ddd;
}
& > .item:nth-child(1) {
border-top: none;
}
}
.compound-value {
border-radius: 4px;
&.borderless {
border: none;
}
}
.deploys {
width: 100%;
display: flex;
flex-direction: column;
gap: 1rem;
}
.deploy {
width: 100%;
background: #fff;
padding: 1rem;
border-radius: 1rem;
} }
// //

@ -0,0 +1,5 @@
export const clsx = (...args) =>
args
.filter(Boolean)
.flatMap(s => (typeof s === 'string' ? s.split(' ') : [s]))
.join(' ')

@ -2,6 +2,7 @@
"extends": "astro/tsconfigs/strict", "extends": "astro/tsconfigs/strict",
"compilerOptions": { "compilerOptions": {
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "preact" "jsxImportSource": "preact",
"exactOptionalPropertyTypes": true
} }
} }

Loading…
Cancel
Save