Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b602b579eb | |||
| 7ba588497b | |||
| 75fc311a11 | |||
| 6518831a26 | |||
| b9d50fa717 | |||
| 4bcb891f24 | |||
| 8aab3ff282 | |||
| 0bd1398085 | |||
| 734869eb26 | |||
| 7a39e67a53 | |||
| 11c127c430 | |||
| 661b45ec82 | |||
| 40b9d1802b | |||
| 0e7cef6334 | |||
| 599a9e178a | |||
| d8c4425456 | |||
| 9a9868a964 | |||
| dc05d524bb | |||
| f37b1e8952 | |||
| 90fda36588 | |||
| 4d5f1d1955 | |||
| 9bd09c8b18 | |||
| afdbd1d535 | |||
| 72016cee81 | |||
| c453288a58 | |||
| 61520d9e2b | |||
| 2b452d817b | |||
| a6a2c0d4f7 |
191
ServerBuildSettingsCard.tsx
Normal file
191
ServerBuildSettingsCard.tsx
Normal file
@ -0,0 +1,191 @@
|
||||
import { AdminServerContext } from '@/state/admin/server'
|
||||
import { useFlashKey } from '@/util/useFlash'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { FormProvider, useForm } from 'react-hook-form'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { z } from 'zod'
|
||||
|
||||
import updateBuild from '@/api/admin/servers/updateBuild'
|
||||
|
||||
import Button from '@/components/elements/Button'
|
||||
import FlashMessageRender from '@/components/elements/FlashMessageRenderer'
|
||||
import FormCard from '@/components/elements/FormCard'
|
||||
import TextInputForm from '@/components/elements/forms/TextInputForm'
|
||||
|
||||
import AddressesMultiSelectForm from '@/components/admin/servers/AddressesMultiSelectForm'
|
||||
|
||||
|
||||
const ServerBuildSettingsCard = () => {
|
||||
const server = AdminServerContext.useStoreState(state => state.server.data!)
|
||||
const setServer = AdminServerContext.useStoreActions(
|
||||
actions => actions.server.setServer
|
||||
)
|
||||
const { clearFlashes, clearAndAddHttpError } = useFlashKey(
|
||||
`admin.servers.${server.uuid}.settings.hardware.build`
|
||||
)
|
||||
const { t: tStrings } = useTranslation('strings')
|
||||
const { t } = useTranslation('admin.servers.settings')
|
||||
const { t: tIndex } = useTranslation('admin.servers.index')
|
||||
|
||||
const pluckedAddressIds = [
|
||||
...server.limits.addresses.ipv4.map(address => address.id.toString()),
|
||||
...server.limits.addresses.ipv6.map(address => address.id.toString()),
|
||||
]
|
||||
|
||||
const schema = z.object({
|
||||
cpu: z.preprocess(Number, z.number().min(1)),
|
||||
memory: z.preprocess(Number, z.number().min(16)),
|
||||
disk: z.preprocess(Number, z.number().min(1)),
|
||||
addressIds: z.array(z.preprocess(Number, z.number())),
|
||||
snapshotLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
backupLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
bandwidthLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
rateLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
bandwidthUsage: z.preprocess(Number, z.number().min(0)),
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
cpu: server.limits.cpu.toString(),
|
||||
memory: (server.limits.memory / 1048576).toString(),
|
||||
disk: (server.limits.disk / 1048576).toString(),
|
||||
addressIds: pluckedAddressIds,
|
||||
snapshotLimit: server.limits.snapshots?.toString() ?? '',
|
||||
backupLimit: server.limits.backups?.toString() ?? '',
|
||||
bandwidthLimit: server.limits.bandwidth
|
||||
? (server.limits.bandwidth / 1048576).toString()
|
||||
: '',
|
||||
rateLimit: server.limits.rateLimit?.toString() ?? '',
|
||||
bandwidthUsage: (server.usages.bandwidth / 1048576).toString(),
|
||||
},
|
||||
})
|
||||
|
||||
const submit = async (_data: any) => {
|
||||
const {
|
||||
memory,
|
||||
disk,
|
||||
snapshotLimit,
|
||||
backupLimit,
|
||||
bandwidthLimit,
|
||||
rateLimit,
|
||||
bandwidthUsage,
|
||||
...data
|
||||
} = _data as z.infer<typeof schema>
|
||||
clearFlashes()
|
||||
|
||||
try {
|
||||
const newServer = await updateBuild(server.uuid, {
|
||||
memory: memory * 1048576,
|
||||
disk: disk * 1048576,
|
||||
snapshotLimit: snapshotLimit !== '' ? snapshotLimit : null,
|
||||
backupLimit: backupLimit !== '' ? backupLimit : null,
|
||||
bandwidthLimit:
|
||||
bandwidthLimit !== '' ? bandwidthLimit * 1048576 : null,
|
||||
rateLimit: rateLimit !== '' ? Number(rateLimit) : null,
|
||||
bandwidthUsage: bandwidthUsage * 1048576,
|
||||
...data,
|
||||
})
|
||||
|
||||
setServer(newServer)
|
||||
|
||||
form.reset({
|
||||
cpu: data.cpu.toString(),
|
||||
memory: memory.toString(),
|
||||
disk: disk.toString(),
|
||||
addressIds: data.addressIds.map(id => id.toString()),
|
||||
snapshotLimit: snapshotLimit.toString() ?? '',
|
||||
backupLimit: backupLimit.toString() ?? '',
|
||||
bandwidthLimit:
|
||||
bandwidthLimit !== '' ? bandwidthLimit.toString() : '',
|
||||
rateLimit: rateLimit !== '' ? rateLimit.toString() : '',
|
||||
bandwidthUsage: bandwidthUsage.toString(),
|
||||
})
|
||||
} catch (error) {
|
||||
clearAndAddHttpError(error as any)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<FormCard className='w-full'>
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(submit)}>
|
||||
<FormCard.Body>
|
||||
<FormCard.Title>{t('build.title')}</FormCard.Title>
|
||||
<div className='space-y-3 mt-3'>
|
||||
<FlashMessageRender
|
||||
byKey={`admin.servers.${server.uuid}.settings.hardware.build`}
|
||||
/>
|
||||
<TextInputForm name='cpu' label={tStrings('cpu')} />
|
||||
<TextInputForm
|
||||
name='memory'
|
||||
label={`${tStrings('memory')} (MiB)`}
|
||||
/>
|
||||
<TextInputForm
|
||||
name='disk'
|
||||
label={`${tStrings('disk')} (MiB)`}
|
||||
/>
|
||||
<AddressesMultiSelectForm nodeId={server.nodeId} />
|
||||
<TextInputForm
|
||||
name='snapshotLimit'
|
||||
label={tIndex('snapshot_limit')}
|
||||
placeholder={'Leave blank for no limit'}
|
||||
/>
|
||||
<TextInputForm
|
||||
name='backupLimit'
|
||||
label={tIndex('backup_limit')}
|
||||
placeholder={'Leave blank for no limit'}
|
||||
/>
|
||||
<TextInputForm
|
||||
name='bandwidthLimit'
|
||||
label={`${tIndex('bandwidth_limit')} (MiB)`}
|
||||
placeholder={
|
||||
tIndex('limit_placeholder') ??
|
||||
'Leave blank for no limit'
|
||||
}
|
||||
/>
|
||||
<TextInputForm
|
||||
name='rateLimit'
|
||||
label={`${tIndex('rate_limit')} (MiB/s)`}
|
||||
placeholder={
|
||||
tIndex('limit_placeholder') ??
|
||||
'Leave blank for no limit'
|
||||
}
|
||||
/>
|
||||
<TextInputForm
|
||||
name='bandwidthUsage'
|
||||
label={`${tIndex('bandwidth_usage')} (MiB)`}
|
||||
/>
|
||||
</div>
|
||||
</FormCard.Body>
|
||||
<FormCard.Footer>
|
||||
<Button
|
||||
loading={form.formState.isSubmitting}
|
||||
disabled={!form.formState.isDirty}
|
||||
type='submit'
|
||||
variant='filled'
|
||||
color='success'
|
||||
size='sm'
|
||||
>
|
||||
{tStrings('save')}
|
||||
</Button>
|
||||
</FormCard.Footer>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</FormCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServerBuildSettingsCard
|
||||
313
Speed/CreateServerModal.tsx
Normal file
313
Speed/CreateServerModal.tsx
Normal file
@ -0,0 +1,313 @@
|
||||
import { useFlashKey } from '@/util/useFlash'
|
||||
import usePagination from '@/util/usePagination'
|
||||
import { hostname, password, usKeyboardCharacters } from '@/util/validation'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { FormProvider, useForm } from 'react-hook-form'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { z } from 'zod'
|
||||
|
||||
import createServer from '@/api/admin/servers/createServer'
|
||||
import { ServerResponse } from '@/api/admin/servers/getServers'
|
||||
import useServersSWR from '@/api/admin/servers/useServersSWR'
|
||||
|
||||
import FlashMessageRender from '@/components/elements/FlashMessageRenderer'
|
||||
import Modal from '@/components/elements/Modal'
|
||||
import CheckboxForm from '@/components/elements/forms/CheckboxForm'
|
||||
import TextInputForm from '@/components/elements/forms/TextInputForm'
|
||||
|
||||
import AddressesMultiSelectForm from '@/components/admin/servers/AddressesMultiSelectForm'
|
||||
import NodesSelectForm from '@/components/admin/servers/NodesSelectForm'
|
||||
import TemplatesSelectForm from '@/components/admin/servers/TemplatesSelectForm'
|
||||
import UsersSelectForm from '@/components/admin/servers/UsersSelectForm'
|
||||
|
||||
|
||||
interface Props {
|
||||
nodeId?: number
|
||||
userId?: number
|
||||
open: boolean
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const CreateServerModal = ({ nodeId, userId, open, onClose }: Props) => {
|
||||
const [page] = usePagination()
|
||||
const { mutate } = useServersSWR({
|
||||
nodeId,
|
||||
userId,
|
||||
page,
|
||||
query: '',
|
||||
include: ['node', 'user'],
|
||||
})
|
||||
const { clearFlashes, clearAndAddHttpError } = useFlashKey(
|
||||
'admin.servers.create'
|
||||
)
|
||||
const { t } = useTranslation('admin.servers.index')
|
||||
const { t: tStrings } = useTranslation('strings')
|
||||
|
||||
const schemaWithCreateVm = z.object({
|
||||
name: z.string().max(40).nonempty(),
|
||||
nodeId: z.preprocess(Number, z.number()),
|
||||
userId: z.preprocess(Number, z.number()),
|
||||
vmid: z.union([
|
||||
z.preprocess(Number, z.number().int().min(100).max(999999999)),
|
||||
z.literal(''),
|
||||
]),
|
||||
hostname: hostname().max(191).nonempty(),
|
||||
addressIds: z.array(z.preprocess(Number, z.number())),
|
||||
cpu: z.preprocess(Number, z.number().min(1)),
|
||||
memory: z.preprocess(Number, z.number().min(16)),
|
||||
disk: z.preprocess(Number, z.number().min(1)),
|
||||
snapshotLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
backupLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
bandwidthLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
rateLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
accountPassword: password(usKeyboardCharacters()).nonempty(),
|
||||
shouldCreateServer: z.literal(true),
|
||||
startOnCompletion: z.boolean(),
|
||||
templateUuid: z.string().nonempty(),
|
||||
})
|
||||
|
||||
const schemaWithoutCreatingVm = z.object({
|
||||
name: z.string().max(40).nonempty(),
|
||||
nodeId: z.preprocess(Number, z.number()),
|
||||
userId: z.preprocess(Number, z.number()),
|
||||
vmid: z.union([
|
||||
z.preprocess(Number, z.number().int().min(100).max(999999999)),
|
||||
z.literal(''),
|
||||
]),
|
||||
hostname: hostname().max(191).nonempty(),
|
||||
addressIds: z.array(z.preprocess(Number, z.number())),
|
||||
cpu: z.preprocess(Number, z.number().min(1)),
|
||||
memory: z.preprocess(Number, z.number().min(16)),
|
||||
disk: z.preprocess(Number, z.number().min(1)),
|
||||
snapshotLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
backupLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
bandwidthLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
rateLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
accountPassword: password(usKeyboardCharacters()).optional(),
|
||||
shouldCreateServer: z.literal(false),
|
||||
startOnCompletion: z.boolean(),
|
||||
templateUuid: z.string(),
|
||||
})
|
||||
|
||||
const schema = z.discriminatedUnion('shouldCreateServer', [
|
||||
schemaWithCreateVm,
|
||||
schemaWithoutCreatingVm,
|
||||
])
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
name: '',
|
||||
nodeId: nodeId?.toString() ?? '',
|
||||
userId: userId?.toString() ?? '',
|
||||
vmid: '',
|
||||
hostname: '',
|
||||
addressIds: [],
|
||||
cpu: '',
|
||||
memory: '',
|
||||
disk: '',
|
||||
snapshotLimit: '0',
|
||||
backupLimit: '',
|
||||
bandwidthLimit: '',
|
||||
rateLimit: '',
|
||||
accountPassword: '',
|
||||
shouldCreateServer: true,
|
||||
startOnCompletion: false,
|
||||
templateUuid: '',
|
||||
},
|
||||
})
|
||||
|
||||
const watchShouldCreateServer = form.watch('shouldCreateServer')
|
||||
const watchNodeId = form.watch('nodeId')
|
||||
|
||||
const submit = async (_data: any) => {
|
||||
const {
|
||||
vmid,
|
||||
cpu,
|
||||
memory,
|
||||
disk,
|
||||
snapshotLimit,
|
||||
backupLimit,
|
||||
bandwidthLimit,
|
||||
rateLimit,
|
||||
addressIds,
|
||||
accountPassword,
|
||||
...data
|
||||
} = _data as z.infer<typeof schema>
|
||||
clearFlashes()
|
||||
try {
|
||||
const server = await createServer({
|
||||
...data,
|
||||
vmid: vmid !== '' ? vmid : null,
|
||||
limits: {
|
||||
cpu,
|
||||
memory: memory * 1048576,
|
||||
disk: disk * 1048576,
|
||||
snapshots: snapshotLimit !== '' ? snapshotLimit : null,
|
||||
backups: backupLimit !== '' ? backupLimit : null,
|
||||
bandwidth:
|
||||
bandwidthLimit !== '' ? bandwidthLimit * 1048576 : null,
|
||||
rateLimit: rateLimit !== '' ? Number(rateLimit) : null,
|
||||
addressIds,
|
||||
},
|
||||
accountPassword: accountPassword ? accountPassword : null,
|
||||
})
|
||||
|
||||
mutate(data => {
|
||||
if (!data) return data
|
||||
|
||||
return {
|
||||
...data,
|
||||
items: [server, ...data.items],
|
||||
} as ServerResponse
|
||||
}, false)
|
||||
|
||||
handleClose()
|
||||
} catch (e) {
|
||||
clearAndAddHttpError(e as Error)
|
||||
}
|
||||
}
|
||||
|
||||
const handleClose = () => {
|
||||
clearFlashes()
|
||||
form.reset()
|
||||
onClose()
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal open={open} onClose={handleClose}>
|
||||
<Modal.Header>
|
||||
<Modal.Title>{t('create_modal.title')}</Modal.Title>
|
||||
</Modal.Header>
|
||||
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(submit)}>
|
||||
<Modal.Body>
|
||||
<FlashMessageRender
|
||||
className='mb-5'
|
||||
byKey={'admin.servers.create'}
|
||||
/>
|
||||
<TextInputForm
|
||||
name={'name'}
|
||||
label={tStrings('display_name')}
|
||||
/>
|
||||
{nodeId ? null : <NodesSelectForm />}
|
||||
{userId ? null : <UsersSelectForm />}
|
||||
<TextInputForm
|
||||
name={'vmid'}
|
||||
label={'VMID'}
|
||||
placeholder={
|
||||
t('vmid_placeholder') ??
|
||||
'Leave blank for random VMID'
|
||||
}
|
||||
/>
|
||||
<TextInputForm
|
||||
name={'hostname'}
|
||||
label={tStrings('hostname')}
|
||||
/>
|
||||
<AddressesMultiSelectForm
|
||||
disabled={watchNodeId === ''}
|
||||
/>
|
||||
<div className={'grid grid-cols-2 gap-3'}>
|
||||
<TextInputForm
|
||||
name={'cpu'}
|
||||
label={tStrings('cpu')}
|
||||
/>
|
||||
<TextInputForm
|
||||
name={'memory'}
|
||||
label={`${tStrings('memory')} (MiB)`}
|
||||
/>
|
||||
</div>
|
||||
<TextInputForm
|
||||
name={'disk'}
|
||||
label={`${tStrings('disk')} (MiB)`}
|
||||
/>
|
||||
<div className={'grid grid-cols-2 gap-3'}>
|
||||
<TextInputForm
|
||||
name={'backupLimit'}
|
||||
label={t('backup_limit')}
|
||||
placeholder={
|
||||
t('limit_placeholder') ??
|
||||
'Leave blank for no limit'
|
||||
}
|
||||
/>
|
||||
<TextInputForm
|
||||
name={'bandwidthLimit'}
|
||||
label={`${t('bandwidth_limit')} (MiB)`}
|
||||
placeholder={
|
||||
t('limit_placeholder') ??
|
||||
'Leave blank for no limit'
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<TextInputForm
|
||||
name={'rateLimit'}
|
||||
label={`${t('rate_limit')} (MiB/s)`}
|
||||
placeholder={
|
||||
t('limit_placeholder') ??
|
||||
'Leave blank for no limit'
|
||||
}
|
||||
/>
|
||||
<TextInputForm
|
||||
name={'accountPassword'}
|
||||
label={tStrings('system_os_password')}
|
||||
type={'password'}
|
||||
/>
|
||||
<CheckboxForm
|
||||
name={'shouldCreateServer'}
|
||||
label={t('should_create_vm')}
|
||||
className={'mt-3 relative'}
|
||||
/>
|
||||
<TemplatesSelectForm
|
||||
disabled={
|
||||
!watchShouldCreateServer || watchNodeId === ''
|
||||
}
|
||||
/>
|
||||
<CheckboxForm
|
||||
name={'startOnCompletion'}
|
||||
label={t('start_server_after_installing')}
|
||||
className={'mt-3 relative'}
|
||||
/>
|
||||
</Modal.Body>
|
||||
<Modal.Actions>
|
||||
<Modal.Action type='button' onClick={handleClose}>
|
||||
{tStrings('cancel')}
|
||||
</Modal.Action>
|
||||
<Modal.Action
|
||||
type='submit'
|
||||
loading={form.formState.isSubmitting}
|
||||
>
|
||||
{tStrings('create')}
|
||||
</Modal.Action>
|
||||
</Modal.Actions>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
export default CreateServerModal
|
||||
260
Speed/NetworkService.php
Normal file
260
Speed/NetworkService.php
Normal file
@ -0,0 +1,260 @@
|
||||
<?php
|
||||
|
||||
namespace Convoy\Services\Servers;
|
||||
|
||||
use Convoy\Data\Server\Deployments\CloudinitAddressConfigData;
|
||||
use Convoy\Data\Server\Eloquent\ServerAddressesData;
|
||||
use Convoy\Data\Server\MacAddressData;
|
||||
use Convoy\Enums\Network\AddressType;
|
||||
use Convoy\Models\Address;
|
||||
use Convoy\Models\Server;
|
||||
use Convoy\Repositories\Eloquent\AddressRepository;
|
||||
use Convoy\Repositories\Proxmox\Server\ProxmoxCloudinitRepository;
|
||||
use Convoy\Repositories\Proxmox\Server\ProxmoxConfigRepository;
|
||||
use Convoy\Repositories\Proxmox\Server\ProxmoxFirewallRepository;
|
||||
use Illuminate\Support\Arr;
|
||||
use function collect;
|
||||
use function is_null;
|
||||
|
||||
class NetworkService
|
||||
{
|
||||
public function __construct(
|
||||
private AddressRepository $repository,
|
||||
private ProxmoxFirewallRepository $firewallRepository,
|
||||
private CloudinitService $cloudinitService,
|
||||
private ProxmoxCloudinitRepository $cloudinitRepository,
|
||||
private ProxmoxConfigRepository $allocationRepository,
|
||||
) {
|
||||
}
|
||||
|
||||
public function deleteIpset(Server $server, string $name)
|
||||
{
|
||||
$this->firewallRepository->setServer($server);
|
||||
|
||||
$addresses = array_column($this->firewallRepository->getLockedIps($name), 'cidr');
|
||||
|
||||
foreach ($addresses as $address) {
|
||||
$this->firewallRepository->unlockIp($name, $address);
|
||||
}
|
||||
|
||||
return $this->firewallRepository->deleteIpset($name);
|
||||
}
|
||||
|
||||
public function clearIpsets(Server $server): void
|
||||
{
|
||||
$this->firewallRepository->setServer($server);
|
||||
|
||||
$ipSets = array_column($this->firewallRepository->getIpsets(), 'name');
|
||||
|
||||
foreach ($ipSets as $ipSet) {
|
||||
$this->deleteIpset($server, $ipSet);
|
||||
}
|
||||
}
|
||||
|
||||
public function lockIps(Server $server, array $addresses, string $ipsetName): void
|
||||
{
|
||||
$this->firewallRepository->setServer($server);
|
||||
|
||||
$this->firewallRepository->createIpset($ipsetName);
|
||||
|
||||
foreach ($addresses as $address) {
|
||||
$this->firewallRepository->lockIp($ipsetName, $address);
|
||||
}
|
||||
}
|
||||
|
||||
public function getMacAddresses(Server $server, bool $eloquent = true, bool $proxmox = false): MacAddressData
|
||||
{
|
||||
if ($eloquent) {
|
||||
$addresses = $this->getAddresses($server);
|
||||
|
||||
$eloquentMacAddress = $addresses->ipv4->first(
|
||||
)?->mac_address ?? $addresses->ipv6->first()?->mac_address;
|
||||
}
|
||||
|
||||
if ($proxmox) {
|
||||
$config = $this->cloudinitRepository->setServer($server)->getConfig();
|
||||
|
||||
$proxmoxMacAddress = null;
|
||||
if (preg_match(
|
||||
"/\b[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}:[[:xdigit:]]{2}\b/su",
|
||||
Arr::get($config, 'net0', ''),
|
||||
$matches,
|
||||
)) {
|
||||
$proxmoxMacAddress = $matches[0];
|
||||
}
|
||||
}
|
||||
|
||||
return MacAddressData::from([
|
||||
'eloquent' => $eloquentMacAddress ?? null,
|
||||
'proxmox' => $proxmoxMacAddress ?? null,
|
||||
]);
|
||||
}
|
||||
|
||||
public function getAddresses(Server $server): ServerAddressesData
|
||||
{
|
||||
return ServerAddressesData::from([
|
||||
'ipv4' => array_values(
|
||||
$server->addresses->where('type', AddressType::IPV4->value)->toArray(),
|
||||
),
|
||||
'ipv6' => array_values(
|
||||
$server->addresses->where('type', AddressType::IPV6->value)->toArray(),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
public function syncSettings(Server $server): void
|
||||
{
|
||||
$macAddresses = $this->getMacAddresses($server, true, true);
|
||||
$addresses = $this->getAddresses($server);
|
||||
|
||||
$this->clearIpsets($server);
|
||||
$this->cloudinitService->updateIpConfig($server, CloudinitAddressConfigData::from([
|
||||
'ipv4' => $addresses->ipv4->first()?->toArray(),
|
||||
'ipv6' => $addresses->ipv6->first()?->toArray(),
|
||||
]));
|
||||
$this->lockIps(
|
||||
$server,
|
||||
array_unique(Arr::flatten($server->addresses()->get(['address'])->toArray())),
|
||||
'ipfilter-net0',
|
||||
);
|
||||
$this->firewallRepository->setServer($server)->updateOptions([
|
||||
'enable' => true,
|
||||
'ipfilter' => true,
|
||||
'policy_in' => 'ACCEPT',
|
||||
'policy_out' => 'ACCEPT',
|
||||
]);
|
||||
|
||||
$macAddress = $macAddresses->eloquent ?? $macAddresses->proxmox;
|
||||
|
||||
$this->allocationRepository->setServer($server)->update(
|
||||
['net0' => "virtio={$macAddress},bridge={$server->node->network},firewall=1"],
|
||||
);
|
||||
}
|
||||
|
||||
public function updateRateLimit(Server $server, ?float $mebibytes = null): void
|
||||
{
|
||||
$macAddresses = $this->getMacAddresses($server, true, true);
|
||||
$macAddress = $macAddresses->eloquent ?? $macAddresses->proxmox;
|
||||
$rawConfig = $this->allocationRepository->setServer($server)->getConfig();
|
||||
$networkConfig = collect($rawConfig)->where('key', '=', 'net0')->first();
|
||||
|
||||
if (is_null($networkConfig)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$parsedConfig = $this->parseConfig($networkConfig['value']);
|
||||
|
||||
// List of possible models
|
||||
$models = ['e1000', 'e1000-82540em', 'e1000-82544gc', 'e1000-82545em', 'e1000e', 'i82551', 'i82557b', 'i82559er', 'ne2k_isa', 'ne2k_pci', 'pcnet', 'rtl8139', 'virtio', 'vmxnet3'];
|
||||
|
||||
// Update the model with the new MAC address
|
||||
$modelFound = false;
|
||||
foreach ($parsedConfig as $item) {
|
||||
if (in_array($item->key, $models)) {
|
||||
$item->value = $macAddress;
|
||||
$modelFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If no model key exists, add the default model with the MAC address
|
||||
if (!$modelFound) {
|
||||
$parsedConfig[] = (object) ['key' => 'virtio', 'value' => $macAddress];
|
||||
}
|
||||
|
||||
// Update or create the bridge value
|
||||
$bridgeFound = false;
|
||||
foreach ($parsedConfig as $item) {
|
||||
if ($item->key === 'bridge') {
|
||||
$item->value = $server->node->network;
|
||||
$bridgeFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bridgeFound) {
|
||||
$parsedConfig[] = (object) ['key' => 'bridge', 'value' => $server->node->network];
|
||||
}
|
||||
|
||||
// Update or create the firewall key
|
||||
$firewallFound = false;
|
||||
foreach ($parsedConfig as $item) {
|
||||
if ($item->key === 'firewall') {
|
||||
$item->value = 1;
|
||||
$firewallFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$firewallFound) {
|
||||
$parsedConfig[] = (object) ['key' => 'firewall', 'value' => 1];
|
||||
}
|
||||
|
||||
// Handle the rate limit
|
||||
if (is_null($mebibytes)) {
|
||||
// Remove the 'rate' key if $mebibytes is null
|
||||
$parsedConfig = array_filter($parsedConfig, fn ($item) => $item->key !== 'rate');
|
||||
} else {
|
||||
// Add or update the 'rate' key
|
||||
$rateUpdated = false;
|
||||
foreach ($parsedConfig as $item) {
|
||||
if ($item->key === 'rate') {
|
||||
$item->value = $mebibytes;
|
||||
$rateUpdated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$rateUpdated) {
|
||||
$parsedConfig[] = (object) ['key' => 'rate', 'value' => $mebibytes];
|
||||
}
|
||||
}
|
||||
|
||||
// Rebuild the configuration string
|
||||
$newConfig = implode(',', array_map(fn ($item) => "{$item->key}={$item->value}", $parsedConfig));
|
||||
|
||||
// Update the Proxmox configuration
|
||||
$this->allocationRepository->setServer($server)->update(['net0' => $newConfig]);
|
||||
}
|
||||
|
||||
private function parseConfig(string $config): array
|
||||
{
|
||||
// Split components by commas
|
||||
$components = explode(',', $config);
|
||||
|
||||
// Array to hold the parsed objects
|
||||
$parsedObjects = [];
|
||||
|
||||
foreach ($components as $component) {
|
||||
// Split each component into key and value
|
||||
[$key, $value] = explode('=', $component);
|
||||
|
||||
// Create an associative array (or object) for key-value pairs
|
||||
$parsedObjects[] = (object) ['key' => $key, 'value' => $value];
|
||||
}
|
||||
|
||||
return $parsedObjects;
|
||||
}
|
||||
|
||||
public function updateAddresses(Server $server, array $addressIds): void
|
||||
{
|
||||
$currentAddresses = $server->addresses()->get()->pluck('id')->toArray();
|
||||
|
||||
$addressesToAdd = array_diff($addressIds, $currentAddresses);
|
||||
$addressesToRemove = array_filter(
|
||||
$currentAddresses,
|
||||
fn ($id) => !in_array($id, $addressIds),
|
||||
);
|
||||
|
||||
if (!empty($addressesToAdd)) {
|
||||
$this->repository->attachAddresses($server, $addressesToAdd);
|
||||
}
|
||||
|
||||
if (!empty($addressesToRemove)) {
|
||||
Address::query()
|
||||
->where('server_id', $server->id)
|
||||
->whereIn('id', $addressesToRemove)
|
||||
->update(['server_id' => null]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -38,11 +38,11 @@ class Server extends Model
|
||||
'cpu' => 'required|numeric|min:1',
|
||||
'memory' => 'required|numeric|min:16777216',
|
||||
'disk' => 'required|numeric|min:1',
|
||||
'bandwidth_usage' => 'sometimes|numeric|min:0',
|
||||
'snapshot_limit' => 'present|nullable|integer|min:0',
|
||||
'backup_limit' => 'present|nullable|integer|min:0',
|
||||
'bandwidth_limit' => 'present|nullable|integer|min:0',
|
||||
'rate_limit' => 'sometimes|nullable|numeric|min:0',
|
||||
'bandwidth_usage' => 'nullable|numeric|min:0',
|
||||
'snapshot_limit' => 'nullable|integer|min:0',
|
||||
'backup_limit' => 'nullable|integer|min:0',
|
||||
'bandwidth_limit' => 'nullable|integer|min:0',
|
||||
'rate_limit' => 'nullable|numeric|min:0',
|
||||
'hydrated_at' => 'nullable|date',
|
||||
];
|
||||
|
||||
|
||||
175
Speed/ServerBuildSettingsCard.tsx
Normal file
175
Speed/ServerBuildSettingsCard.tsx
Normal file
@ -0,0 +1,175 @@
|
||||
import { AdminServerContext } from '@/state/admin/server'
|
||||
import { useFlashKey } from '@/util/useFlash'
|
||||
import { zodResolver } from '@hookform/resolvers/zod'
|
||||
import { FormProvider, useForm } from 'react-hook-form'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { z } from 'zod'
|
||||
|
||||
import updateBuild from '@/api/admin/servers/updateBuild'
|
||||
|
||||
import Button from '@/components/elements/Button'
|
||||
import FlashMessageRender from '@/components/elements/FlashMessageRenderer'
|
||||
import FormCard from '@/components/elements/FormCard'
|
||||
import TextInputForm from '@/components/elements/forms/TextInputForm'
|
||||
|
||||
import AddressesMultiSelectForm from '@/components/admin/servers/AddressesMultiSelectForm'
|
||||
|
||||
|
||||
const ServerBuildSettingsCard = () => {
|
||||
const server = AdminServerContext.useStoreState(state => state.server.data!)
|
||||
const setServer = AdminServerContext.useStoreActions(
|
||||
actions => actions.server.setServer
|
||||
)
|
||||
const { clearFlashes, clearAndAddHttpError } = useFlashKey(
|
||||
`admin.servers.${server.uuid}.settings.hardware.build`
|
||||
)
|
||||
const { t: tStrings } = useTranslation('strings')
|
||||
const { t } = useTranslation('admin.servers.settings')
|
||||
const { t: tIndex } = useTranslation('admin.servers.index')
|
||||
|
||||
const pluckedAddressIds = [
|
||||
...server.limits.addresses.ipv4.map(address => address.id.toString()),
|
||||
...server.limits.addresses.ipv6.map(address => address.id.toString()),
|
||||
]
|
||||
|
||||
const schema = z.object({
|
||||
cpu: z.preprocess(Number, z.number().min(1)),
|
||||
memory: z.preprocess(Number, z.number().min(16)),
|
||||
disk: z.preprocess(Number, z.number().min(1)),
|
||||
addressIds: z.array(z.preprocess(Number, z.number())),
|
||||
snapshotLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
backupLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
bandwidthLimit: z.union([
|
||||
z.literal(''),
|
||||
z.preprocess(Number, z.number().min(0)),
|
||||
]),
|
||||
bandwidthUsage: z.preprocess(Number, z.number().min(0)),
|
||||
})
|
||||
|
||||
const form = useForm({
|
||||
resolver: zodResolver(schema),
|
||||
defaultValues: {
|
||||
cpu: server.limits.cpu.toString(),
|
||||
memory: (server.limits.memory / 1048576).toString(),
|
||||
disk: (server.limits.disk / 1048576).toString(),
|
||||
addressIds: pluckedAddressIds,
|
||||
snapshotLimit: server.limits.snapshots?.toString() ?? '',
|
||||
backupLimit: server.limits.backups?.toString() ?? '',
|
||||
bandwidthLimit: server.limits.bandwidth
|
||||
? (server.limits.bandwidth / 1048576).toString()
|
||||
: '',
|
||||
bandwidthUsage: (server.usages.bandwidth / 1048576).toString(),
|
||||
},
|
||||
})
|
||||
|
||||
const submit = async (_data: any) => {
|
||||
const {
|
||||
memory,
|
||||
disk,
|
||||
snapshotLimit,
|
||||
backupLimit,
|
||||
bandwidthLimit,
|
||||
bandwidthUsage,
|
||||
...data
|
||||
} = _data as z.infer<typeof schema>
|
||||
clearFlashes()
|
||||
|
||||
try {
|
||||
const newServer = await updateBuild(server.uuid, {
|
||||
memory: memory * 1048576,
|
||||
disk: disk * 1048576,
|
||||
snapshotLimit: snapshotLimit !== '' ? snapshotLimit : null,
|
||||
backupLimit: backupLimit !== '' ? backupLimit : null,
|
||||
bandwidthLimit:
|
||||
bandwidthLimit !== '' ? bandwidthLimit * 1048576 : null,
|
||||
bandwidthUsage: bandwidthUsage * 1048576,
|
||||
...data,
|
||||
})
|
||||
|
||||
setServer(newServer)
|
||||
|
||||
form.reset({
|
||||
cpu: data.cpu.toString(),
|
||||
memory: memory.toString(),
|
||||
disk: disk.toString(),
|
||||
addressIds: data.addressIds.map(id => id.toString()),
|
||||
snapshotLimit: snapshotLimit.toString() ?? '',
|
||||
backupLimit: backupLimit.toString() ?? '',
|
||||
bandwidthLimit:
|
||||
bandwidthLimit !== '' ? bandwidthLimit.toString() : '',
|
||||
bandwidthUsage: bandwidthUsage.toString(),
|
||||
})
|
||||
} catch (error) {
|
||||
clearAndAddHttpError(error as any)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<FormCard className='w-full'>
|
||||
<FormProvider {...form}>
|
||||
<form onSubmit={form.handleSubmit(submit)}>
|
||||
<FormCard.Body>
|
||||
<FormCard.Title>{t('build.title')}</FormCard.Title>
|
||||
<div className='space-y-3 mt-3'>
|
||||
<FlashMessageRender
|
||||
byKey={`admin.servers.${server.uuid}.settings.hardware.build`}
|
||||
/>
|
||||
<TextInputForm name='cpu' label={tStrings('cpu')} />
|
||||
<TextInputForm
|
||||
name='memory'
|
||||
label={`${tStrings('memory')} (MiB)`}
|
||||
/>
|
||||
<TextInputForm
|
||||
name='disk'
|
||||
label={`${tStrings('disk')} (MiB)`}
|
||||
/>
|
||||
<AddressesMultiSelectForm nodeId={server.nodeId} />
|
||||
<TextInputForm
|
||||
name='snapshotLimit'
|
||||
label={tIndex('snapshot_limit')}
|
||||
placeholder={'Leave blank for no limit'}
|
||||
/>
|
||||
<TextInputForm
|
||||
name='backupLimit'
|
||||
label={tIndex('backup_limit')}
|
||||
placeholder={'Leave blank for no limit'}
|
||||
/>
|
||||
<TextInputForm
|
||||
name='bandwidthLimit'
|
||||
label={`${tIndex('bandwidth_limit')} (MiB)`}
|
||||
placeholder={
|
||||
tIndex('limit_placeholder') ??
|
||||
'Leave blank for no limit'
|
||||
}
|
||||
/>
|
||||
<TextInputForm
|
||||
name='bandwidthUsage'
|
||||
label={`${tIndex('bandwidth_usage')} (MiB)`}
|
||||
/>
|
||||
</div>
|
||||
</FormCard.Body>
|
||||
<FormCard.Footer>
|
||||
<Button
|
||||
loading={form.formState.isSubmitting}
|
||||
disabled={!form.formState.isDirty}
|
||||
type='submit'
|
||||
variant='filled'
|
||||
color='success'
|
||||
size='sm'
|
||||
>
|
||||
{tStrings('save')}
|
||||
</Button>
|
||||
</FormCard.Footer>
|
||||
</form>
|
||||
</FormProvider>
|
||||
</FormCard>
|
||||
)
|
||||
}
|
||||
|
||||
export default ServerBuildSettingsCard
|
||||
163
Speed/ServerController.php
Normal file
163
Speed/ServerController.php
Normal file
@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
namespace Convoy\Http\Controllers\Admin;
|
||||
|
||||
use Convoy\Enums\Server\Status;
|
||||
use Convoy\Enums\Server\SuspensionAction;
|
||||
use Convoy\Exceptions\Repository\Proxmox\ProxmoxConnectionException;
|
||||
use Convoy\Http\Controllers\ApiController;
|
||||
use Convoy\Http\Requests\Admin\Servers\Settings\UpdateBuildRequest;
|
||||
use Convoy\Http\Requests\Admin\Servers\Settings\UpdateGeneralInfoRequest;
|
||||
use Convoy\Http\Requests\Admin\Servers\StoreServerRequest;
|
||||
use Convoy\Models\Filters\FiltersServerByAddressPoolId;
|
||||
use Convoy\Models\Filters\FiltersServerWildcard;
|
||||
use Convoy\Models\Server;
|
||||
use Convoy\Services\Servers\CloudinitService;
|
||||
use Convoy\Services\Servers\NetworkService;
|
||||
use Convoy\Services\Servers\ServerCreationService;
|
||||
use Convoy\Services\Servers\ServerDeletionService;
|
||||
use Convoy\Services\Servers\ServerSuspensionService;
|
||||
use Convoy\Services\Servers\SyncBuildService;
|
||||
use Convoy\Transformers\Admin\ServerBuildTransformer;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Spatie\QueryBuilder\AllowedFilter;
|
||||
use Spatie\QueryBuilder\QueryBuilder;
|
||||
use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
|
||||
|
||||
class ServerController extends ApiController
|
||||
{
|
||||
public function __construct(
|
||||
private ConnectionInterface $connection,
|
||||
private ServerDeletionService $deletionService,
|
||||
private NetworkService $networkService,
|
||||
private ServerSuspensionService $suspensionService,
|
||||
private ServerCreationService $creationService,
|
||||
private CloudinitService $cloudinitService,
|
||||
private SyncBuildService $buildModificationService,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$servers = QueryBuilder::for(Server::query())
|
||||
->with(['addresses', 'user', 'node'])
|
||||
->defaultSort('-id')
|
||||
->allowedFilters(
|
||||
[
|
||||
AllowedFilter::custom(
|
||||
'*', new FiltersServerWildcard(),
|
||||
),
|
||||
AllowedFilter::custom(
|
||||
'address_pool_id',
|
||||
new FiltersServerByAddressPoolId(),
|
||||
),
|
||||
AllowedFilter::exact('node_id'),
|
||||
AllowedFilter::exact('user_id'),
|
||||
'name',
|
||||
],
|
||||
)
|
||||
->paginate(min($request->query('per_page', 50), 100))->appends(
|
||||
$request->query(),
|
||||
);
|
||||
|
||||
return fractal($servers, new ServerBuildTransformer())->parseIncludes($request->include)
|
||||
->respond();
|
||||
}
|
||||
|
||||
public function show(Request $request, Server $server)
|
||||
{
|
||||
$server->load(['addresses', 'user', 'node']);
|
||||
|
||||
return fractal($server, new ServerBuildTransformer())->parseIncludes($request->include)
|
||||
->respond();
|
||||
}
|
||||
|
||||
public function store(StoreServerRequest $request)
|
||||
{
|
||||
$server = $this->creationService->handle($request->validated());
|
||||
|
||||
$server->load(['addresses', 'user', 'node']);
|
||||
|
||||
return fractal($server, new ServerBuildTransformer())->parseIncludes(['user', 'node'])
|
||||
->respond();
|
||||
}
|
||||
|
||||
public function update(UpdateGeneralInfoRequest $request, Server $server)
|
||||
{
|
||||
$this->connection->transaction(function () use ($request, $server) {
|
||||
if ($request->hostname !== $server->hostname && !empty($request->hostname)) {
|
||||
try {
|
||||
$this->cloudinitService->updateHostname($server, $request->hostname);
|
||||
} catch (ProxmoxConnectionException) {
|
||||
throw new ServiceUnavailableHttpException(
|
||||
message: "Server {$server->uuid} failed to sync hostname.",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$server->update($request->validated());
|
||||
});
|
||||
|
||||
$server->load(['addresses', 'user', 'node']);
|
||||
|
||||
return fractal($server, new ServerBuildTransformer())->parseIncludes(['user', 'node'])
|
||||
->respond();
|
||||
}
|
||||
|
||||
public function updateBuild(UpdateBuildRequest $request, Server $server)
|
||||
{
|
||||
$validated = $request->validated();
|
||||
|
||||
// Handle address_ids separately
|
||||
$addressIds = $validated['address_ids'] ?? null;
|
||||
unset($validated['address_ids']);
|
||||
|
||||
// Ensure rate_limit is always present in update data to avoid validation errors
|
||||
if (!array_key_exists('rate_limit', $validated)) {
|
||||
$validated['rate_limit'] = $server->rate_limit ?? null;
|
||||
}
|
||||
|
||||
// Update server with validated data, skip model validation as request validation is already done
|
||||
$server->skipValidation()->update($validated);
|
||||
|
||||
$this->networkService->updateAddresses($server, $addressIds ?? []);
|
||||
|
||||
try {
|
||||
$this->buildModificationService->handle($server);
|
||||
} catch (ProxmoxConnectionException $e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
$server->load(['addresses', 'user', 'node']);
|
||||
|
||||
return fractal($server, new ServerBuildTransformer())->parseIncludes(['user', 'node'])
|
||||
->respond();
|
||||
}
|
||||
|
||||
public function suspend(Server $server)
|
||||
{
|
||||
$this->suspensionService->toggle($server);
|
||||
|
||||
return $this->returnNoContent();
|
||||
}
|
||||
|
||||
public function unsuspend(Server $server)
|
||||
{
|
||||
$this->suspensionService->toggle($server, SuspensionAction::UNSUSPEND);
|
||||
|
||||
return $this->returnNoContent();
|
||||
}
|
||||
|
||||
public function destroy(Request $request, Server $server)
|
||||
{
|
||||
$this->connection->transaction(function () use ($server, $request) {
|
||||
$server->update(['status' => Status::DELETING->value]);
|
||||
|
||||
$this->deletionService->handle($server, $request->input('no_purge', false));
|
||||
});
|
||||
|
||||
return $this->returnNoContent();
|
||||
}
|
||||
}
|
||||
49
Speed/ServerUsagesSyncService.php
Normal file
49
Speed/ServerUsagesSyncService.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
namespace Convoy\Services\Nodes;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Convoy\Models\Node;
|
||||
use Convoy\Models\Server;
|
||||
use Convoy\Enums\Server\MetricTimeframe;
|
||||
use Convoy\Repositories\Proxmox\Server\ProxmoxMetricsRepository;
|
||||
use Convoy\Exceptions\Repository\Proxmox\ProxmoxConnectionException;
|
||||
|
||||
class ServerUsagesSyncService
|
||||
{
|
||||
public function __construct(private ProxmoxMetricsRepository $repository)
|
||||
{
|
||||
}
|
||||
|
||||
public function handle(Node $node)
|
||||
{
|
||||
$servers = $node->servers;
|
||||
|
||||
$servers->each(function (Server $server) {
|
||||
try {
|
||||
$metrics = $this->repository->setServer($server)->getMetrics(MetricTimeframe::HOUR);
|
||||
|
||||
$bandwidth = $server->bandwidth_usage;
|
||||
$endingDate = $server->hydrated_at ? Carbon::parse($server->hydrated_at) : Carbon::now()->firstOfMonth();
|
||||
|
||||
foreach ($metrics as $metric) {
|
||||
if (Carbon::createFromTimestamp($metric['time'])->gt($endingDate)) {
|
||||
// we multiply it by 60 seconds because each metric is
|
||||
// recorded every 1 minute but the values like netin and
|
||||
// netout are in bytes/sec
|
||||
$bandwidth += (int) $metric['netin'] * 60 + (int) $metric['netout'] * 60;
|
||||
}
|
||||
}
|
||||
|
||||
if ($bandwidth > 0) {
|
||||
$server->skipValidation()->update([
|
||||
'bandwidth_usage' => $bandwidth,
|
||||
'hydrated_at' => now(),
|
||||
]);
|
||||
}
|
||||
} catch (ProxmoxConnectionException $e) {
|
||||
// do nothing
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,16 @@ use Illuminate\Validation\Validator;
|
||||
*/
|
||||
class StoreServerRequest extends BaseApiRequest
|
||||
{
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
// Map camelCase to snake_case for rateLimit
|
||||
$this->merge([
|
||||
'limits' => [
|
||||
'rate_limit' => $this->input('limits.rateLimit'),
|
||||
],
|
||||
]);
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$rules = Server::getRules();
|
||||
|
||||
@ -10,6 +10,14 @@ use Convoy\Http\Requests\BaseApiRequest;
|
||||
|
||||
class UpdateBuildRequest extends BaseApiRequest
|
||||
{
|
||||
protected function prepareForValidation()
|
||||
{
|
||||
// Map camelCase to snake_case for rateLimit
|
||||
$this->merge([
|
||||
'rate_limit' => $this->input('rateLimit'),
|
||||
]);
|
||||
}
|
||||
|
||||
public function rules(): array
|
||||
{
|
||||
$server = $this->parameter('server', Server::class);
|
||||
|
||||
@ -9,11 +9,24 @@ convoy/app/Services/Servers/ServerCreationService.php
|
||||
|
||||
convoy/app/Services/Servers/SyncBuildService.php
|
||||
|
||||
convoy/app/Http/Controllers/Admin/ServerController.php
|
||||
|
||||
convoy/app/Services/Nodes/ServerUsagesSyncService.php
|
||||
|
||||
#FRONT END REBUILD
|
||||
npm install
|
||||
npm run build
|
||||
|
||||
#DATA BASE Migration (Convoy)
|
||||
php artisan migrate
|
||||
|
||||
#USE DOCKER (Convoy)
|
||||
#DATA BASE Migration USE DOCKER (Convoy)
|
||||
docker compose exec workspace bash -c "php artisan migrate"
|
||||
docker compose exec workspace bash -c "php artisan cache:clear"
|
||||
docker compose exec workspace bash -c "php artisan config:clear"
|
||||
docker compose exec workspace bash -c "php artisan route:clear"
|
||||
docker compose exec workspace bash -c "php artisan horizon:terminate"
|
||||
docker compose exec workspace bash -c "php artisan queue:restart"
|
||||
|
||||
#RESTART (Convoy)
|
||||
docker compose down
|
||||
|
||||
43
Speed/updateBuild.ts
Normal file
43
Speed/updateBuild.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import { rawDataToAdminServer } from '@/api/admin/servers/getServer'
|
||||
import http from '@/api/http'
|
||||
|
||||
interface UpdateServerBuildParameters {
|
||||
cpu: number
|
||||
memory: number
|
||||
disk: number
|
||||
addressIds: number[]
|
||||
snapshotLimit: number | null
|
||||
backupLimit: number | null
|
||||
bandwidthLimit: number | null
|
||||
rateLimit: number | null
|
||||
bandwidthUsage: number
|
||||
}
|
||||
|
||||
const updateBuild = async (
|
||||
serverUuid: string,
|
||||
{
|
||||
addressIds,
|
||||
snapshotLimit,
|
||||
backupLimit,
|
||||
bandwidthLimit,
|
||||
rateLimit,
|
||||
bandwidthUsage,
|
||||
...params
|
||||
}: UpdateServerBuildParameters
|
||||
) => {
|
||||
const {
|
||||
data: { data },
|
||||
} = await http.patch(`/api/admin/servers/${serverUuid}/settings/build`, {
|
||||
address_ids: addressIds,
|
||||
snapshot_limit: snapshotLimit,
|
||||
backup_limit: backupLimit,
|
||||
bandwidth_limit: bandwidthLimit,
|
||||
rate_limit: rateLimit,
|
||||
bandwidth_usage: bandwidthUsage,
|
||||
...params,
|
||||
})
|
||||
|
||||
return rawDataToAdminServer(data)
|
||||
}
|
||||
|
||||
export default updateBuild
|
||||
Loading…
x
Reference in New Issue
Block a user