Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
- Display expiration date of SSL certificates (commit [48218](https://github.com/MathieuRA/nguix/commit/4821882d17b8cb7d23fdcdf9452b0d48e2884244))
- Display `nginx.service` status(commit [852bd](https://github.com/MathieuRA/nguix/commit/852bd52e735687fa4b6855f3f41bba0eae322e2f))
- Disable/Enable virtual hosts (commit [af4f6](https://github.com/MathieuRA/nguix/commit/af4f6578b05386e9ffab3aeb8a9a1d91ad0b10ed))
- Destroy virtual host (PR [#4](https://github.com/MathieuRA/nguix/pull/4))

## Fixes

Expand Down
8 changes: 6 additions & 2 deletions back/src/classes/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import { serveStatic } from '../static.ts'

// @ts-ignore
type HttpCallback = (params: { request: http.IncomingMessage, response: http.ServerResponse, [key: string]: number | string, body: <T> (req: http.IncomingMessage) => Promise<T> }) => void | ApiResponse | Promise<void | ApiResponse>
type HttpVerb = 'get' | 'patch'
type HttpVerb = 'get' | 'delete' | 'patch'
type RouteConfig = {
path: string
callback: HttpCallback
method: HttpVerb,
}
const HTTP_VERB: HttpVerb[] = ['get', 'patch']
const HTTP_VERB: HttpVerb[] = ['get', 'delete', 'patch']

const BASE_PATH = '/api'

Expand Down Expand Up @@ -87,6 +87,10 @@ export class Router {
this.#addRoute({ method: 'get', callback, path })
}

delete(path: string, callback: HttpCallback) {
this.#addRoute({ method: 'delete', callback, path })
}

patch(path: string, callback: HttpCallback) {
this.#addRoute({ method: 'patch', callback, path })
}
Expand Down
34 changes: 31 additions & 3 deletions back/src/classes/vhost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,16 @@ export class Vhost {
return { domain, aliases, port, ssl, sslExpiry, proxy };
}

private readonly _filePath: string
/** null if the sym link doesn't exist anymore. (e.g. after a disable call) */
private _filePath: string | null
private readonly _symlink: boolean
private readonly _fileName: string
private readonly _enabled: boolean
private readonly _conf: VhostConf
private readonly _id: string

constructor(params: { path: string, symlink: boolean, conf: VhostConf }) {
this._filePath = Object.freeze(params.path)
this._filePath = params.path
this._symlink = Object.freeze(params.symlink)
this._fileName = Object.freeze(params.path.split('/').pop() ?? '_unknown_')
this._enabled = Object.freeze(this._symlink)
Expand Down Expand Up @@ -83,6 +84,13 @@ export class Vhost {
}

linkTo() {
if (!this.isSymlink) {
throw new Error('not a symlink')
}
if (this.filePath === null) {
throw new Error('not an active symlink')
}

return fs.readlink(this.filePath)
}

Expand Down Expand Up @@ -118,13 +126,33 @@ export class Vhost {
throw new Error("update partially applied")
}

async destroy() {
const path = this.isSymlink ? await this.linkTo() : this.filePath!

if (this.enabled) {
await this.#disable()
}

await fs.rm(path)
}

async #enable() {
await fs.symlink(this.filePath, `${getEnv('SITES_ENABLED')}/${this.fileName}`, 'file')
if (this.isSymlink) {
throw new Error('[enable vhost]: cannot enable a symlink')
}

await fs.symlink(this.filePath!, `${getEnv('SITES_ENABLED')}/${this.fileName}`, 'file')
}

async #disable() {
if (this.isSymlink) {
if (this.filePath === null) {
console.warn('[disable vhost]: vhost already disabled')
return
}

await fs.unlink(this.filePath)
this._filePath = null
} else {
console.error('trying to unlink a non symlink', this)
}
Expand Down
7 changes: 7 additions & 0 deletions back/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,13 @@ router.get('/vhosts', async () => {
return new ApiResponse({ data: vHosts.map(host => host.toJSON()) })
})

router.delete('/vhosts/:id', async ({ id }) => {
const vhost = await getVirtualHost(decodeURIComponent(id as string))
await vhost.destroy()

return new ApiResponse({ status: 204 })
})

router.patch('/vhosts/:id', async ({ id, request, body }) => {
const vhost = await getVirtualHost(decodeURIComponent(id as string))

Expand Down
16 changes: 13 additions & 3 deletions front/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ function setFilter(el) {
renderVhosts();
}

async function getVhosts(opts) {
async function getVhosts(opts = {}) {
// TODO: handle cache expiration
if (CACHE['vhosts'] === undefined || opts.bypassCache) {
const response = await fetch('/vhosts')
Expand All @@ -37,6 +37,17 @@ async function toggleVhost(id, value) {
await renderVhosts({ bypassCache: true })
}

async function destroyVhost(id) {
const vhost = (await getVhosts()).find(vhost => vhost.id === id)
confirm(`Are you sure you want to destroy the vhost: ${vhost.conf.domain}`)

await fetch(`/vhosts/${encodeURIComponent(id)}`, {
method: 'DELETE'
})

await renderVhosts({ bypassCache: true })
}

async function renderVhosts(opts) {
const query = document.getElementById('search').value.toLowerCase();

Expand Down Expand Up @@ -86,10 +97,9 @@ async function renderVhosts(opts) {
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>'
+ '</button>'
+ '<button class="icon-btn" title="' + (vhost.enabled ? 'Disable' : 'Enable') + '" onclick="toggleVhost(' + `'${vhost.id}', ${!vhost.enabled}` + ')">'
// + '<button class="icon-btn" title="' + (vhost.enabled ? 'Disable' : 'Enable') + '" onclick="toggleVhost("' + vhost.id + '",' + !vhost.enabled + ')">'
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"/><line x1="12" y1="2" x2="12" y2="12"/></svg>'
+ '</button>'
+ '<button class="icon-btn danger" title="Delete" onclick="alert(\'Delete ' + vhost.conf.domain + '\')">'
+ '<button class="icon-btn danger" title="Destroy" onclick="destroyVhost(' + `'${vhost.id}'` + ')">'
+ '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6l-1 14H6L5 6"/><path d="M10 11v6"/><path d="M14 11v6"/><path d="M9 6V4h6v2"/></svg>'
+ '</button>'
+ '</div></td>'
Expand Down
Loading