feat: new servers view

This commit is contained in:
Andras Bacsai
2022-09-08 14:42:04 +02:00
parent 372c0ed457
commit 0e13e3bd81
9 changed files with 304 additions and 50 deletions

View File

@@ -499,9 +499,26 @@ export async function createRemoteEngineConfiguration(id: string) {
}
return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config))
}
export async function executeSSHCmd({ dockerId, command }) {
const { execaCommand } = await import('execa')
let { remoteEngine, remoteIpAddress, engine, remoteUser } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
if (remoteEngine) {
await createRemoteEngineConfiguration(dockerId)
engine = `ssh://${remoteIpAddress}`
} else {
engine = 'unix:///var/run/docker.sock'
}
if (process.env.CODESANDBOX_HOST) {
if (command.startsWith('docker compose')) {
command = command.replace(/docker compose/gi, 'docker-compose')
}
}
command = `ssh ${remoteIpAddress} ${command}`
return await execaCommand(command)
}
export async function executeDockerCmd({ debug, buildId, applicationId, dockerId, command }: { debug?: boolean, buildId?: string, applicationId?: string, dockerId: string, command: string }): Promise<any> {
const { execaCommand } = await import('execa')
let { remoteEngine, remoteIpAddress, engine } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
let { remoteEngine, remoteIpAddress, engine, remoteUser } = await prisma.destinationDocker.findUnique({ where: { id: dockerId } })
if (remoteEngine) {
await createRemoteEngineConfiguration(dockerId)
engine = `ssh://${remoteIpAddress}`

View File

@@ -1,5 +1,4 @@
import os from 'node:os';
import osu from 'node-os-utils';
import axios from 'axios';
import { compareVersions } from 'compare-versions';
import cuid from 'cuid';
@@ -15,9 +14,10 @@ export async function hashPassword(password: string): Promise<string> {
return bcrypt.hash(password, saltRounds);
}
export async function cleanupManually() {
export async function cleanupManually(request: FastifyRequest) {
try {
const destination = await prisma.destinationDocker.findFirst({ where: { engine: '/var/run/docker.sock' } })
const { serverId } = request.body;
const destination = await prisma.destinationDocker.findUnique({ where: { id: serverId } })
await cleanupDockerStorage(destination.id, true, true)
return {}
} catch ({ status, message }) {
@@ -86,25 +86,7 @@ export async function restartCoolify(request: FastifyRequest<any>) {
return errorHandler({ status, message })
}
}
export async function showUsage() {
try {
return {
usage: {
uptime: os.uptime(),
memory: await osu.mem.info(),
cpu: {
load: os.loadavg(),
usage: await osu.cpu.usage(),
count: os.cpus().length
},
disk: await osu.drive.info('/')
}
};
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
export async function showDashboard(request: FastifyRequest) {
try {
const userId = request.user.userId;

View File

@@ -43,17 +43,13 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
onRequest: [fastify.authenticate]
}, async (request) => await showDashboard(request));
fastify.get('/usage', {
onRequest: [fastify.authenticate]
}, async () => await showUsage());
fastify.post('/internal/restart', {
onRequest: [fastify.authenticate]
}, async (request) => await restartCoolify(request));
fastify.post('/internal/cleanup', {
onRequest: [fastify.authenticate]
}, async () => await cleanupManually());
}, async (request) => await cleanupManually(request));
};
export default root;

View File

@@ -0,0 +1,117 @@
import type { FastifyRequest } from 'fastify';
import { errorHandler, executeDockerCmd, prisma, createRemoteEngineConfiguration, executeSSHCmd } from '../../../../lib/common';
import os from 'node:os';
import osu from 'node-os-utils';
export async function listServers(request: FastifyRequest) {
try {
const userId = request.user.userId;
const teamId = request.user.teamId;
const remoteServers = await prisma.destinationDocker.findMany({ where: { teams: { some: { id: teamId === '0' ? undefined : teamId } } }, distinct: ['remoteIpAddress', 'engine'] })
return {
servers: remoteServers
}
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
const mappingTable = [
['K total memory', 'totalMemoryKB'],
['K used memory', 'usedMemoryKB'],
['K active memory', 'activeMemoryKB'],
['K inactive memory', 'inactiveMemoryKB'],
['K free memory', 'freeMemoryKB'],
['K buffer memory', 'bufferMemoryKB'],
['K swap cache', 'swapCacheKB'],
['K total swap', 'totalSwapKB'],
['K used swap', 'usedSwapKB'],
['K free swap', 'freeSwapKB'],
['non-nice user cpu ticks', 'nonNiceUserCpuTicks'],
['nice user cpu ticks', 'niceUserCpuTicks'],
['system cpu ticks', 'systemCpuTicks'],
['idle cpu ticks', 'idleCpuTicks'],
['IO-wait cpu ticks', 'ioWaitCpuTicks'],
['IRQ cpu ticks', 'irqCpuTicks'],
['softirq cpu ticks', 'softIrqCpuTicks'],
['stolen cpu ticks', 'stolenCpuTicks'],
['pages paged in', 'pagesPagedIn'],
['pages paged out', 'pagesPagedOut'],
['pages swapped in', 'pagesSwappedIn'],
['pages swapped out', 'pagesSwappedOut'],
['interrupts', 'interrupts'],
['CPU context switches', 'cpuContextSwitches'],
['boot time', 'bootTime'],
['forks', 'forks']
];
function parseFromText(text) {
var data = {};
var lines = text.split(/\r?\n/);
for (const line of lines) {
for (const [key, value] of mappingTable) {
if (line.indexOf(key) >= 0) {
const values = line.match(/[0-9]+/)[0];
data[value] = parseInt(values, 10);
}
}
}
return data;
}
export async function showUsage(request: FastifyRequest) {
const { id } = request.params;
let { remoteEngine } = request.query
remoteEngine = remoteEngine === 'true' ? true : false
if (remoteEngine) {
const { stdout: stats } = await executeSSHCmd({ dockerId: id, command: `vmstat -s` })
const { stdout: disks } = await executeSSHCmd({ dockerId: id, command: `df -m / --output=size,used,pcent|grep -v 'Used'| xargs` })
const { stdout: cpus } = await executeSSHCmd({ dockerId: id, command: `nproc --all` })
// const { stdout: cpuUsage } = await executeSSHCmd({ dockerId: id, command: `echo $[100-$(vmstat 1 2|tail -1|awk '{print $15}')]` })
// console.log(cpuUsage)
const parsed: any = parseFromText(stats)
return {
usage: {
uptime: parsed.bootTime / 1024,
memory: {
totalMemMb: parsed.totalMemoryKB / 1024,
usedMemMb: parsed.usedMemoryKB / 1024,
freeMemMb: parsed.freeMemoryKB / 1024,
usedMemPercentage: (parsed.usedMemoryKB / parsed.totalMemoryKB) * 100,
freeMemPercentage: (parsed.totalMemoryKB - parsed.usedMemoryKB) / parsed.totalMemoryKB * 100
},
cpu: {
load: 0,
usage: 0,
count: cpus
},
disk: {
totalGb: (disks.split(' ')[0] / 1024).toFixed(1),
usedGb: (disks.split(' ')[1] / 1024).toFixed(1),
freeGb: (disks.split(' ')[0] - disks.split(' ')[1]).toFixed(1),
usedPercentage: disks.split(' ')[2].replace('%', ''),
freePercentage: 100 - disks.split(' ')[2].replace('%', '')
}
}
}
} else {
try {
return {
usage: {
uptime: os.uptime(),
memory: await osu.mem.info(),
cpu: {
load: os.loadavg(),
usage: await osu.cpu.usage(),
count: os.cpus().length
},
disk: await osu.drive.info('/')
}
};
} catch ({ status, message }) {
return errorHandler({ status, message })
}
}
}

View File

@@ -0,0 +1,14 @@
import { FastifyPluginAsync } from 'fastify';
import { listServers, showUsage } from './handlers';
const root: FastifyPluginAsync = async (fastify): Promise<void> => {
fastify.addHook('onRequest', async (request) => {
return await request.jwtVerify()
})
fastify.get('/', async (request) => await listServers(request));
fastify.get('/usage/:id', async (request) => await showUsage(request));
};
export default root;

View File

@@ -0,0 +1,27 @@
import { OnlyId } from "../../../../types"
export interface SaveTeam extends OnlyId {
Body: {
name: string
}
}
export interface InviteToTeam {
Body: {
email: string,
permission: string,
teamId: string,
teamName: string
}
}
export interface BodyId {
Body: {
id: string
}
}
export interface SetPermission {
Body: {
userId: string,
newPermission: string,
permissionId: string
}
}