feat: new servers view
This commit is contained in:
parent
372c0ed457
commit
0e13e3bd81
@ -499,9 +499,26 @@ export async function createRemoteEngineConfiguration(id: string) {
|
|||||||
}
|
}
|
||||||
return await fs.writeFile(`${homedir}/.ssh/config`, sshConfig.stringify(config))
|
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> {
|
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')
|
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) {
|
if (remoteEngine) {
|
||||||
await createRemoteEngineConfiguration(dockerId)
|
await createRemoteEngineConfiguration(dockerId)
|
||||||
engine = `ssh://${remoteIpAddress}`
|
engine = `ssh://${remoteIpAddress}`
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import os from 'node:os';
|
|
||||||
import osu from 'node-os-utils';
|
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
import { compareVersions } from 'compare-versions';
|
import { compareVersions } from 'compare-versions';
|
||||||
import cuid from 'cuid';
|
import cuid from 'cuid';
|
||||||
@ -15,9 +14,10 @@ export async function hashPassword(password: string): Promise<string> {
|
|||||||
return bcrypt.hash(password, saltRounds);
|
return bcrypt.hash(password, saltRounds);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function cleanupManually() {
|
export async function cleanupManually(request: FastifyRequest) {
|
||||||
try {
|
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)
|
await cleanupDockerStorage(destination.id, true, true)
|
||||||
return {}
|
return {}
|
||||||
} catch ({ status, message }) {
|
} catch ({ status, message }) {
|
||||||
@ -86,25 +86,7 @@ export async function restartCoolify(request: FastifyRequest<any>) {
|
|||||||
return errorHandler({ status, message })
|
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) {
|
export async function showDashboard(request: FastifyRequest) {
|
||||||
try {
|
try {
|
||||||
const userId = request.user.userId;
|
const userId = request.user.userId;
|
||||||
|
@ -43,17 +43,13 @@ const root: FastifyPluginAsync = async (fastify): Promise<void> => {
|
|||||||
onRequest: [fastify.authenticate]
|
onRequest: [fastify.authenticate]
|
||||||
}, async (request) => await showDashboard(request));
|
}, async (request) => await showDashboard(request));
|
||||||
|
|
||||||
fastify.get('/usage', {
|
|
||||||
onRequest: [fastify.authenticate]
|
|
||||||
}, async () => await showUsage());
|
|
||||||
|
|
||||||
fastify.post('/internal/restart', {
|
fastify.post('/internal/restart', {
|
||||||
onRequest: [fastify.authenticate]
|
onRequest: [fastify.authenticate]
|
||||||
}, async (request) => await restartCoolify(request));
|
}, async (request) => await restartCoolify(request));
|
||||||
|
|
||||||
fastify.post('/internal/cleanup', {
|
fastify.post('/internal/cleanup', {
|
||||||
onRequest: [fastify.authenticate]
|
onRequest: [fastify.authenticate]
|
||||||
}, async () => await cleanupManually());
|
}, async (request) => await cleanupManually(request));
|
||||||
};
|
};
|
||||||
|
|
||||||
export default root;
|
export default root;
|
||||||
|
117
apps/api/src/routes/api/v1/servers/handlers.ts
Normal file
117
apps/api/src/routes/api/v1/servers/handlers.ts
Normal 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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
14
apps/api/src/routes/api/v1/servers/index.ts
Normal file
14
apps/api/src/routes/api/v1/servers/index.ts
Normal 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;
|
27
apps/api/src/routes/api/v1/servers/types.ts
Normal file
27
apps/api/src/routes/api/v1/servers/types.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
export let server: any;
|
||||||
let usage = {
|
let usage = {
|
||||||
cpu: {
|
cpu: {
|
||||||
load: [0, 0, 0],
|
load: [0, 0, 0],
|
||||||
@ -29,7 +30,7 @@
|
|||||||
async function getStatus() {
|
async function getStatus() {
|
||||||
if (loading.usage) return;
|
if (loading.usage) return;
|
||||||
loading.usage = true;
|
loading.usage = true;
|
||||||
const data = await get('/usage');
|
const data = await get(`/servers/usage/${server.id}?remoteEngine=${server.remoteEngine}`);
|
||||||
usage = data.usage;
|
usage = data.usage;
|
||||||
loading.usage = false;
|
loading.usage = false;
|
||||||
}
|
}
|
||||||
@ -52,7 +53,7 @@
|
|||||||
async function manuallyCleanupStorage() {
|
async function manuallyCleanupStorage() {
|
||||||
try {
|
try {
|
||||||
loading.cleanup = true;
|
loading.cleanup = true;
|
||||||
await post('/internal/cleanup', {});
|
await post('/internal/cleanup', { serverId: server.id });
|
||||||
return addToast({
|
return addToast({
|
||||||
message: 'Cleanup done.',
|
message: 'Cleanup done.',
|
||||||
type: 'success'
|
type: 'success'
|
||||||
@ -65,16 +66,42 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="w-full">
|
<div class="w-full relative p-5 ">
|
||||||
<div class="flex lg:flex-row flex-col gap-4">
|
{#if loading.usage}
|
||||||
<h1 class="title lg:text-3xl">Hardware Details</h1>
|
<span class="indicator-item badge bg-yellow-500 badge-sm" />
|
||||||
<div class="flex lg:flex-row flex-col space-x-0 lg:space-x-2 space-y-2 lg:space-y-0">
|
{:else}
|
||||||
|
<span class="indicator-item badge bg-success badge-sm" />
|
||||||
|
{/if}
|
||||||
|
{#if server.remoteEngine}
|
||||||
|
<div
|
||||||
|
class="absolute top-0 right-0 text-xl font-bold uppercase bg-gradient-to-r from-purple-500 via-pink-500 to-red-500 p-1 rounded m-2"
|
||||||
|
>
|
||||||
|
BETA
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
<div class="w-full flex flex-row space-x-4">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<h1 class="font-bold text-lg lg:text-xl truncate">
|
||||||
|
{server.name}
|
||||||
|
</h1>
|
||||||
|
<div class="text-xs ">
|
||||||
|
{#if server?.remoteIpAddress}
|
||||||
|
<h2>{server?.remoteIpAddress}</h2>
|
||||||
|
{:else}
|
||||||
|
<h2>localhost</h2>
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
{#if $appSession.teamId === '0'}
|
{#if $appSession.teamId === '0'}
|
||||||
<button on:click={manuallyCleanupStorage} class:loading={loading.cleanup} class="btn btn-sm"
|
<button
|
||||||
>Cleanup Storage</button
|
on:click={manuallyCleanupStorage}
|
||||||
|
class:loading={loading.cleanup}
|
||||||
|
class="btn btn-sm bg-coollabs">Cleanup Storage</button
|
||||||
>
|
>
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex lg:flex-row flex-col gap-4">
|
||||||
|
<div class="flex lg:flex-row flex-col space-x-0 lg:space-x-2 space-y-2 lg:space-y-0" />
|
||||||
</div>
|
</div>
|
||||||
<div class="divider" />
|
<div class="divider" />
|
||||||
<div class="grid grid-flow-col gap-4 grid-rows-3 justify-start lg:justify-center lg:grid-rows-1">
|
<div class="grid grid-flow-col gap-4 grid-rows-3 justify-start lg:justify-center lg:grid-rows-1">
|
||||||
@ -82,21 +109,21 @@
|
|||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Total Memory</div>
|
<div class="stat-title">Total Memory</div>
|
||||||
<div class="stat-value text-2xl">
|
<div class="stat-value text-2xl">
|
||||||
{(usage?.memory.totalMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
{(usage?.memory?.totalMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Used Memory</div>
|
<div class="stat-title">Used Memory</div>
|
||||||
<div class="stat-value text-2xl">
|
<div class="stat-value text-2xl">
|
||||||
{(usage?.memory.usedMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
{(usage?.memory?.usedMemMb).toFixed(0)}<span class="text-sm">MB</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Free Memory</div>
|
<div class="stat-title">Free Memory</div>
|
||||||
<div class="stat-value text-2xl">
|
<div class="stat-value text-2xl">
|
||||||
{usage?.memory.freeMemPercentage}<span class="text-sm">%</span>
|
{(usage?.memory?.freeMemPercentage).toFixed(0)}<span class="text-sm">%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -105,41 +132,41 @@
|
|||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Total CPU</div>
|
<div class="stat-title">Total CPU</div>
|
||||||
<div class="stat-value text-2xl">
|
<div class="stat-value text-2xl">
|
||||||
{usage?.cpu.count}
|
{usage?.cpu?.count}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">CPU Usage</div>
|
<div class="stat-title">CPU Usage</div>
|
||||||
<div class="stat-value text-2xl">
|
<div class="stat-value text-2xl">
|
||||||
{usage?.cpu.usage}<span class="text-sm">%</span>
|
{usage?.cpu?.usage}<span class="text-sm">%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Load Average (5,10,30mins)</div>
|
<div class="stat-title">Load Average (5,10,30mins)</div>
|
||||||
<div class="stat-value text-2xl">{usage?.cpu.load}</div>
|
<div class="stat-value text-2xl">{usage?.cpu?.load}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="stats stats-vertical min-w-[16rem] mb-5 bg-transparent rounded">
|
<div class="stats stats-vertical min-w-[16rem] mb-5 bg-transparent rounded">
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Total Disk</div>
|
<div class="stat-title">Total Disk</div>
|
||||||
<div class="stat-value text-2xl">
|
<div class="stat-value text-2xl">
|
||||||
{usage?.disk.totalGb}<span class="text-sm">GB</span>
|
{usage?.disk?.totalGb}<span class="text-sm">GB</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Used Disk</div>
|
<div class="stat-title">Used Disk</div>
|
||||||
<div class="stat-value text-2xl">
|
<div class="stat-value text-2xl">
|
||||||
{usage?.disk.usedGb}<span class="text-sm">GB</span>
|
{usage?.disk?.usedGb}<span class="text-sm">GB</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="stat">
|
<div class="stat">
|
||||||
<div class="stat-title">Free Disk</div>
|
<div class="stat-title">Free Disk</div>
|
||||||
<div class="stat-value text-2xl">
|
<div class="stat-value text-2xl">
|
||||||
{usage?.disk.freePercentage}<span class="text-sm">%</span>
|
{usage?.disk?.freePercentage}<span class="text-sm">%</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -132,9 +132,9 @@
|
|||||||
<img src={$appSession.whiteLabeledDetails.icon} alt="White labeled logo" />
|
<img src={$appSession.whiteLabeledDetails.icon} alt="White labeled logo" />
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex flex-col space-y-2 py-2" id="dashboard" class:mt-2={$appSession.whiteLabeled}>
|
<div class="flex flex-col space-y-2 py-2" class:mt-2={$appSession.whiteLabeled}>
|
||||||
<a
|
<a
|
||||||
|
id="dashboard"
|
||||||
sveltekit:prefetch
|
sveltekit:prefetch
|
||||||
href="/"
|
href="/"
|
||||||
class="icons hover:text-white"
|
class="icons hover:text-white"
|
||||||
@ -159,8 +159,35 @@
|
|||||||
<path d="M16 15c-2.21 1.333 -5.792 1.333 -8 0" />
|
<path d="M16 15c-2.21 1.333 -5.792 1.333 -8 0" />
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
<a
|
||||||
|
id="servers"
|
||||||
|
sveltekit:prefetch
|
||||||
|
href="/servers"
|
||||||
|
class="icons hover:text-white"
|
||||||
|
class:text-white={$page.url.pathname === '/servers'}
|
||||||
|
class:bg-coolgray-500={$page.url.pathname === '/servers'}
|
||||||
|
class:bg-coolgray-200={!($page.url.pathname === '/servers')}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="w-8 h-8 mx-auto"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke-width="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
fill="none"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
>
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
|
||||||
|
<rect x="3" y="4" width="18" height="8" rx="3" />
|
||||||
|
<rect x="3" y="12" width="18" height="8" rx="3" />
|
||||||
|
<line x1="7" y1="8" x2="7" y2="8.01" />
|
||||||
|
<line x1="7" y1="16" x2="7" y2="16.01" />
|
||||||
|
</svg>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<Tooltip triggeredBy="#dashboard" placement="right">Dashboard</Tooltip>
|
<Tooltip triggeredBy="#dashboard" placement="right">Dashboard</Tooltip>
|
||||||
|
<Tooltip triggeredBy="#servers" placement="right">Servers</Tooltip>
|
||||||
<div class="flex-1" />
|
<div class="flex-1" />
|
||||||
|
|
||||||
<UpdateAvailable />
|
<UpdateAvailable />
|
||||||
|
47
apps/ui/src/routes/servers/index.svelte
Normal file
47
apps/ui/src/routes/servers/index.svelte
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
<script context="module" lang="ts">
|
||||||
|
import { get } from '$lib/api';
|
||||||
|
import Usage from '$lib/components/Usage.svelte';
|
||||||
|
|
||||||
|
import type { Load } from '@sveltejs/kit';
|
||||||
|
export const load: Load = async ({}) => {
|
||||||
|
try {
|
||||||
|
const { servers } = await get('/servers');
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
servers
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} catch (error: any) {
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
error: new Error(error)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export let servers: any;
|
||||||
|
import { appSession } from '$lib/store';
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="flex space-x-1 p-6 font-bold">
|
||||||
|
<div class="mr-4 text-2xl tracking-tight">Servers</div>
|
||||||
|
</div>
|
||||||
|
<div class="container lg:mx-auto lg:p-0 px-8 p-5">
|
||||||
|
{#if servers.length > 0}
|
||||||
|
<div class="grid grid-col gap-8 auto-cols-max grid-cols-1 p-4">
|
||||||
|
{#each servers as server}
|
||||||
|
<div class="no-underline mb-5">
|
||||||
|
<div class="w-full rounded bg-coolgray-100 indicator">
|
||||||
|
{#if $appSession.teamId === '0'}
|
||||||
|
<Usage {server} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<h1 class="">Nothing here.</h1>
|
||||||
|
{/if}
|
||||||
|
</div>
|
Loading…
Reference in New Issue
Block a user