Merge branch 'develop' into feature/react-admin
This commit is contained in:
commit
01c03b6b77
19 changed files with 614 additions and 274 deletions
|
@ -76,6 +76,7 @@ export default ({ server, className }: { server: Server; className?: string }) =
|
|||
|
||||
const diskLimit = server.limits.disk !== 0 ? megabytesToHuman(server.limits.disk) : 'Unlimited';
|
||||
const memoryLimit = server.limits.memory !== 0 ? megabytesToHuman(server.limits.memory) : 'Unlimited';
|
||||
const cpuLimit = server.limits.cpu !== 0 ? server.limits.cpu + ' %' : 'Unlimited';
|
||||
|
||||
return (
|
||||
<StatusIndicatorBox as={Link} to={`/server/${server.id}`} className={className} $status={stats?.status}>
|
||||
|
@ -130,11 +131,14 @@ export default ({ server, className }: { server: Server; className?: string }) =
|
|||
<Spinner size={'small'}/>
|
||||
:
|
||||
<React.Fragment>
|
||||
<div css={tw`flex-1 flex md:ml-4 sm:flex hidden justify-center`}>
|
||||
<Icon icon={faMicrochip} $alarm={alarms.cpu}/>
|
||||
<IconDescription $alarm={alarms.cpu}>
|
||||
{stats.cpuUsagePercent} %
|
||||
</IconDescription>
|
||||
<div css={tw`flex-1 ml-4 sm:block hidden`}>
|
||||
<div css={tw`flex justify-center`}>
|
||||
<Icon icon={faMicrochip} $alarm={alarms.cpu}/>
|
||||
<IconDescription $alarm={alarms.cpu}>
|
||||
{stats.cpuUsagePercent.toFixed(2)} %
|
||||
</IconDescription>
|
||||
</div>
|
||||
<p css={tw`text-xs text-neutral-600 text-center mt-1`}>of {cpuLimit}</p>
|
||||
</div>
|
||||
<div css={tw`flex-1 ml-4 sm:block hidden`}>
|
||||
<div css={tw`flex justify-center`}>
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React, { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { ITerminalOptions, Terminal } from 'xterm';
|
||||
import { Terminal, ITerminalOptions } from 'xterm';
|
||||
import { FitAddon } from 'xterm-addon-fit';
|
||||
import { SearchAddon } from 'xterm-addon-search';
|
||||
import { SearchBarAddon } from 'xterm-addon-search-bar';
|
||||
import { WebLinksAddon } from 'xterm-addon-web-links';
|
||||
import { ScrollDownHelperAddon } from '@/plugins/XtermScrollDownHelperAddon';
|
||||
import SpinnerOverlay from '@/components/elements/SpinnerOverlay';
|
||||
import { ServerContext } from '@/state/server';
|
||||
import styled from 'styled-components/macro';
|
||||
|
@ -73,6 +74,7 @@ export default () => {
|
|||
const searchAddon = new SearchAddon();
|
||||
const searchBar = new SearchBarAddon({ searchAddon });
|
||||
const webLinksAddon = new WebLinksAddon();
|
||||
const scrollDownHelperAddon = new ScrollDownHelperAddon();
|
||||
const { connected, instance } = ServerContext.useStoreState(state => state.socket);
|
||||
const [ canSendCommands ] = usePermissions([ 'control.console' ]);
|
||||
const serverId = ServerContext.useStoreState(state => state.server.data!.id);
|
||||
|
@ -140,6 +142,7 @@ export default () => {
|
|||
terminal.loadAddon(searchAddon);
|
||||
terminal.loadAddon(searchBar);
|
||||
terminal.loadAddon(webLinksAddon);
|
||||
terminal.loadAddon(scrollDownHelperAddon);
|
||||
|
||||
terminal.open(ref.current);
|
||||
fitAddon.fit();
|
||||
|
@ -201,7 +204,7 @@ export default () => {
|
|||
|
||||
return (
|
||||
<div css={tw`text-xs font-mono relative`}>
|
||||
<SpinnerOverlay visible={!connected} size={'large'}/>
|
||||
<SpinnerOverlay visible={!connected} size={'large'} />
|
||||
<div
|
||||
css={[
|
||||
tw`rounded-t p-2 bg-black w-full`,
|
||||
|
@ -209,21 +212,21 @@ export default () => {
|
|||
]}
|
||||
style={{ minHeight: '16rem' }}
|
||||
>
|
||||
<TerminalDiv id={'terminal'} ref={ref}/>
|
||||
<TerminalDiv id={'terminal'} ref={ref} />
|
||||
</div>
|
||||
{canSendCommands &&
|
||||
<div css={tw`rounded-b bg-neutral-900 text-neutral-100 flex items-baseline`}>
|
||||
<div css={tw`flex-shrink-0 p-2 font-bold`}>$</div>
|
||||
<div css={tw`w-full`}>
|
||||
<CommandInput
|
||||
type={'text'}
|
||||
placeholder={'Type a command...'}
|
||||
aria-label={'Console command input.'}
|
||||
disabled={!instance || !connected}
|
||||
onKeyDown={handleCommandKeyDown}
|
||||
/>
|
||||
<div css={tw`rounded-b bg-neutral-900 text-neutral-100 flex items-baseline`}>
|
||||
<div css={tw`flex-shrink-0 p-2 font-bold`}>$</div>
|
||||
<div css={tw`w-full`}>
|
||||
<CommandInput
|
||||
type={'text'}
|
||||
placeholder={'Type a command...'}
|
||||
aria-label={'Console command input.'}
|
||||
disabled={!instance || !connected}
|
||||
onKeyDown={handleCommandKeyDown}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -74,6 +74,7 @@ const ServerDetailsBlock = () => {
|
|||
|
||||
const diskLimit = limits.disk ? megabytesToHuman(limits.disk) : 'Unlimited';
|
||||
const memoryLimit = limits.memory ? megabytesToHuman(limits.memory) : 'Unlimited';
|
||||
const cpuLimit = limits.cpu ? limits.cpu + '%' : 'Unlimited';
|
||||
|
||||
return (
|
||||
<TitledGreyBox css={tw`break-words`} title={name} icon={faServer}>
|
||||
|
@ -96,6 +97,7 @@ const ServerDetailsBlock = () => {
|
|||
</CopyOnClick>
|
||||
<p css={tw`text-xs mt-2`}>
|
||||
<FontAwesomeIcon icon={faMicrochip} fixedWidth css={tw`mr-1`}/> {stats.cpu.toFixed(2)}%
|
||||
<span css={tw`text-neutral-500`}> / {cpuLimit}</span>
|
||||
</p>
|
||||
<p css={tw`text-xs mt-2`}>
|
||||
<FontAwesomeIcon icon={faMemory} fixedWidth css={tw`mr-1`}/> {bytesToHuman(stats.memory)}
|
||||
|
|
|
@ -96,7 +96,7 @@ export default () => {
|
|||
|
||||
setCpu(
|
||||
new Chart(node.getContext('2d')!, chartDefaults({
|
||||
callback: (value) => `${value}%`,
|
||||
callback: (value) => `${value}% `,
|
||||
})),
|
||||
);
|
||||
}, []);
|
||||
|
|
|
@ -62,7 +62,7 @@ export default () => {
|
|||
</p>
|
||||
}
|
||||
<Can action={'database.create'}>
|
||||
<div css={tw`mt-6 sm:flex items-center justify-end`}>
|
||||
<div css={tw`mt-6 flex items-center justify-end`}>
|
||||
{(databaseLimit > 0 && databases.length > 0) &&
|
||||
<p css={tw`text-sm text-neutral-300 mb-4 sm:mr-6 sm:mb-0`}>
|
||||
{databases.length} of {databaseLimit} databases have been allocated to this
|
||||
|
@ -70,7 +70,7 @@ export default () => {
|
|||
</p>
|
||||
}
|
||||
{databaseLimit > 0 && databaseLimit !== databases.length &&
|
||||
<CreateDatabaseButton css={tw`w-full sm:w-auto`}/>
|
||||
<CreateDatabaseButton css={tw`flex justify-end mt-6`}/>
|
||||
}
|
||||
</div>
|
||||
</Can>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import React from 'react';
|
||||
import PortaledModal, { ModalProps } from '@/components/elements/Modal';
|
||||
import ModalContext, { ModalContextValues } from '@/context/ModalContext';
|
||||
import isEqual from 'react-fast-compare';
|
||||
|
||||
export interface AsModalProps {
|
||||
visible: boolean;
|
||||
|
@ -47,13 +48,13 @@ function asModal<P extends {}> (modalProps?: SettableModalProps | ((props: P) =>
|
|||
/**
|
||||
* @this {React.PureComponent<P & AsModalProps, State>}
|
||||
*/
|
||||
componentDidUpdate (prevProps: Readonly<P & AsModalProps>) {
|
||||
componentDidUpdate (prevProps: Readonly<P & AsModalProps>, prevState: Readonly<State>) {
|
||||
if (prevProps.visible && !this.props.visible) {
|
||||
this.setState({ visible: false, showSpinnerOverlay: false });
|
||||
} else if (!prevProps.visible && this.props.visible) {
|
||||
this.setState({ render: true, visible: true });
|
||||
}
|
||||
if (!this.state.render) {
|
||||
if (!this.state.render && !isEqual(prevState.propOverrides, this.state.propOverrides)) {
|
||||
this.setState({ propOverrides: {} });
|
||||
}
|
||||
}
|
||||
|
|
68
resources/scripts/plugins/XtermScrollDownHelperAddon.ts
Normal file
68
resources/scripts/plugins/XtermScrollDownHelperAddon.ts
Normal file
|
@ -0,0 +1,68 @@
|
|||
import { Terminal, ITerminalAddon } from 'xterm';
|
||||
|
||||
export class ScrollDownHelperAddon implements ITerminalAddon {
|
||||
private terminal: Terminal = new Terminal();
|
||||
private element?: HTMLDivElement;
|
||||
|
||||
activate (terminal: Terminal): void {
|
||||
this.terminal = terminal;
|
||||
|
||||
this.terminal.onScroll(() => {
|
||||
if (this.isScrolledDown()) {
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
|
||||
this.terminal.onLineFeed(() => {
|
||||
if (!this.isScrolledDown()) {
|
||||
this.show();
|
||||
}
|
||||
});
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
dispose (): void {
|
||||
// ignore
|
||||
}
|
||||
|
||||
show (): void {
|
||||
if (!this.terminal || !this.terminal.element) {
|
||||
return;
|
||||
}
|
||||
if (this.element) {
|
||||
this.element.style.visibility = 'visible';
|
||||
return;
|
||||
}
|
||||
|
||||
this.terminal.element.style.position = 'relative';
|
||||
|
||||
this.element = document.createElement('div');
|
||||
this.element.innerHTML = '<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="bell" class="svg-inline--fa fa-bell fa-w-14" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path fill="currentColor" d="M224 512c35.32 0 63.97-28.65 63.97-64H160.03c0 35.35 28.65 64 63.97 64zm215.39-149.71c-19.32-20.76-55.47-51.99-55.47-154.29 0-77.7-54.48-139.9-127.94-155.16V32c0-17.67-14.32-32-31.98-32s-31.98 14.33-31.98 32v20.84C118.56 68.1 64.08 130.3 64.08 208c0 102.3-36.15 133.53-55.47 154.29-6 6.45-8.66 14.16-8.61 21.71.11 16.4 12.98 32 32.1 32h383.8c19.12 0 32-15.6 32.1-32 .05-7.55-2.61-15.27-8.61-21.71z"></path></svg>';
|
||||
this.element.style.position = 'absolute';
|
||||
this.element.style.right = '1.5rem';
|
||||
this.element.style.bottom = '.5rem';
|
||||
this.element.style.padding = '.5rem';
|
||||
this.element.style.fontSize = '1.25em';
|
||||
this.element.style.boxShadow = '0 2px 8px #000';
|
||||
this.element.style.backgroundColor = '#252526';
|
||||
this.element.style.zIndex = '999';
|
||||
this.element.style.cursor = 'pointer';
|
||||
|
||||
this.element.addEventListener('click', () => {
|
||||
this.terminal.scrollToBottom();
|
||||
});
|
||||
|
||||
this.terminal.element.appendChild(this.element);
|
||||
}
|
||||
|
||||
hide (): void {
|
||||
if (this.element) {
|
||||
this.element.style.visibility = 'hidden';
|
||||
}
|
||||
}
|
||||
|
||||
isScrolledDown (): boolean {
|
||||
return this.terminal.buffer.active.viewportY === this.terminal.buffer.active.baseY;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue