Skip to content
This repository has been archived by the owner on Sep 20, 2023. It is now read-only.

Commit

Permalink
added new terminal commands (#67):
Browse files Browse the repository at this point in the history
  credits, dl, shutdown,ping
fixed some missing type warnings
added check for UUID args
  • Loading branch information
Akida31 committed Jun 2, 2021
1 parent cf3cc5d commit bc9d2a7
Show file tree
Hide file tree
Showing 10 changed files with 189 additions and 29 deletions.
30 changes: 20 additions & 10 deletions src/app/desktop/windows/terminal/terminal-api.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,64 @@
import { SafeHtml } from '@angular/platform-browser';
import {Device} from 'src/app/api/devices/device';

export interface TerminalAPI {
/**
* Outputs html to the terminal (followed by a line break)
* @param html HTML string
*/
output(html: string);
output(html: string): void;

/**
* Outputs html without a line break to the terminal
* @param html HTML string
*/
outputRaw(html: string);
outputRaw(html: string): void;

/**
* Outputs text to the terminal
* @param text Raw text
*/
outputText(text: string);
outputText(text: string): void;

/**
* Outputs a html node to the terminal
* @param node `Node`
*/
outputNode(node: Node);
outputNode(node: Node): void;

/**
* Closes the terminal window
*/
closeTerminal();
closeTerminal(): void;

/**
* Clears the complete terminal
*/
clear();
clear(): void;

changePrompt(prompt: string | SafeHtml, trust?: boolean);
changePrompt(prompt: string | SafeHtml, trust?: boolean): void;

pushState(state: TerminalState);
pushState(state: TerminalState): void;

popState(): TerminalState;

/**
* Shutdowns the current device
*
* @returns: if the shutdown was successful
*/
shutdown(): Promise<boolean>;

getOwnerDevice(): Device;
}


export interface TerminalState {
execute(command: string);
execute(command: string): void;

autocomplete(content: string): Promise<string>;

getHistory(): string[];

refreshPrompt();
refreshPrompt(): void;
}
17 changes: 9 additions & 8 deletions src/app/desktop/windows/terminal/terminal-states.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {WindowDelegate} from '../../window/window-delegate';
import {File} from '../../../api/files/file';
import {Shell} from 'src/app/shell/shell';
import {ShellApi} from 'src/app/shell/shellapi';
import {DeviceService} from 'src/app/api/devices/device.service';


function escapeHtml(html: string): string {
Expand Down Expand Up @@ -331,8 +332,8 @@ export class DefaultTerminalState extends CommandTerminalState {
working_dir: string = Path.ROOT; // UUID of the working directory

constructor(protected websocket: WebsocketService, private settings: SettingsService, private fileService: FileService,
private domSanitizer: DomSanitizer, protected windowDelegate: WindowDelegate, protected activeDevice: Device,
protected terminal: TerminalAPI, public promptColor: string = null) {
private deviceService: DeviceService, private domSanitizer: DomSanitizer, protected windowDelegate: WindowDelegate,
protected activeDevice: Device, protected terminal: TerminalAPI, public promptColor: string = null) {
super();
}

Expand Down Expand Up @@ -1454,8 +1455,8 @@ export class DefaultTerminalState extends CommandTerminalState {
this.websocket.ms('device', ['device', 'info'], {device_uuid: args[0]}).subscribe(infoData => {
this.websocket.ms('service', ['part_owner'], {device_uuid: args[0]}).subscribe(partOwnerData => {
if (infoData['owner'] === this.websocket.account.uuid || partOwnerData['ok'] === true) {
this.terminal.pushState(new DefaultTerminalState(this.websocket, this.settings, this.fileService, this.domSanitizer,
this.windowDelegate, infoData, this.terminal, '#DD2C00'));
this.terminal.pushState(new DefaultTerminalState(this.websocket, this.settings, this.fileService, this.deviceService,
this.domSanitizer, this.windowDelegate, infoData, this.terminal, '#DD2C00'));
this.setExitCode(0);
} else {
iohandler.stderr('Access denied');
Expand Down Expand Up @@ -1957,7 +1958,7 @@ export class DefaultTerminalState extends CommandTerminalState {
msh(_: IOHandler) {
this.terminal.pushState(
new ShellTerminal(
this.websocket, this.settings, this.fileService,
this.websocket, this.settings, this.fileService, this.deviceService,
this.domSanitizer, this.windowDelegate, this.activeDevice,
this.terminal, this.promptColor
)
Expand Down Expand Up @@ -2165,11 +2166,11 @@ class ShellTerminal implements TerminalState {
private shell: Shell;

constructor(private websocket: WebsocketService, private settings: SettingsService, private fileService: FileService,
private domSanitizer: DomSanitizer, windowDelegate: WindowDelegate, private activeDevice: Device,
private terminal: TerminalAPI, private promptColor: string = null
private deviceService: DeviceService, private domSanitizer: DomSanitizer, windowDelegate: WindowDelegate,
private activeDevice: Device, private terminal: TerminalAPI, private promptColor: string = null
) {
const shellApi = new ShellApi(
this.websocket, this.settings, this.fileService,
this.websocket, this.settings, this.fileService, this.deviceService,
this.domSanitizer, windowDelegate, this.activeDevice,
terminal, this.promptColor, this.refreshPrompt.bind(this),
Path.ROOT
Expand Down
22 changes: 21 additions & 1 deletion src/app/desktop/windows/terminal/terminal.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,11 @@ import {TerminalAPI, TerminalState} from './terminal-api';
import {DefaultTerminalState} from './terminal-states';
import {WebsocketService} from '../../../websocket.service';
import {DomSanitizer, SafeHtml} from '@angular/platform-browser';
import {DeviceService} from '../../../api/devices/device.service';
import {FileService} from '../../../api/files/file.service';
import {WindowManager} from '../../window-manager/window-manager';
import {Router} from '@angular/router';
import {Device} from 'src/app/api/devices/device';

// noinspection AngularMissingOrInvalidDeclarationInModule
@Component({
Expand All @@ -28,8 +31,10 @@ export class TerminalComponent extends WindowComponent implements OnInit, Termin
private websocket: WebsocketService,
private settings: SettingsService,
private fileService: FileService,
private deviceService: DeviceService,
private windowManager: WindowManager,
private domSanitizer: DomSanitizer
private domSanitizer: DomSanitizer,
private router: Router,
) {
super();
}
Expand All @@ -40,6 +45,7 @@ export class TerminalComponent extends WindowComponent implements OnInit, Termin
this.websocket,
this.settings,
this.fileService,
this.deviceService,
this.domSanitizer,
this.delegate,
this.delegate.device,
Expand Down Expand Up @@ -168,6 +174,20 @@ export class TerminalComponent extends WindowComponent implements OnInit, Termin
clear() {
this.history.nativeElement.value = '';
}

async shutdown(): Promise<boolean> {
const uuid = this.delegate.device['uuid'];
try {
await this.deviceService.togglePower(uuid).toPromise();
} catch {
return false;
}
return await this.router.navigate(['device'], {queryParams: {device: uuid}});
}

getOwnerDevice(): Device {
return this.delegate.device;
}
}

export class TerminalWindowDelegate extends WindowDelegate {
Expand Down
6 changes: 5 additions & 1 deletion src/app/shell/builtins/builtins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ import {Cd} from './cd';
import {Ls} from './ls';
import {Clear} from './clear';
import {Exit} from './exit';
import {Dl} from './dl';
import {Shutdown} from './shutdown';
import {Ping} from './ping';
import {Credits} from './credits';

export const BUILTINS = [Status, Hostname, Miner, Cd, Ls, Clear, Exit];
export const BUILTINS = [Status, Hostname, Miner, Cd, Ls, Clear, Exit, Dl, Shutdown, Ping, Credits];

18 changes: 18 additions & 0 deletions src/app/shell/builtins/credits.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import {Command, IOHandler} from '../command';
import {ShellApi} from '../shellapi';

export class Credits extends Command {
constructor(shellApi: ShellApi) {
super('credits', shellApi);
this.addDescription('list all contributors');
}

async run(iohandler: IOHandler): Promise<number> {
const data = await fetch('https://api.admin.staging.cryptic-game.net/website/team');
const members = JSON.parse(await data.text()).sort(() => Math.random() - 0.5);
members.forEach((member: any) => {
iohandler.stdout(member.name);
});
return 0;
}
}
51 changes: 51 additions & 0 deletions src/app/shell/builtins/dl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {Command, IOHandler, ArgType} from '../command';
import {ShellApi} from '../shellapi';
import {Path} from 'src/app/api/files/path';
import {File} from 'src/app/api/files/file';

export class Dl extends Command {
constructor(shellApi: ShellApi) {
super('dl', shellApi);
this.addDescription('download a file to your own device');
this.addPositionalArgument({name: 'source', argType: ArgType.FILE});
this.addPositionalArgument({name: 'destination'});
}

async run(iohandler: IOHandler): Promise<number> {
let srcFile: File;
let dstPath: Path;
const ownerUuid = this.shellApi.terminal.getOwnerDevice()['uuid'];
try {
const srcPath = Path.fromString(iohandler.positionalArgs[0], this.shellApi.working_dir);
srcFile = await this.shellApi.fileService.getFromPath(this.shellApi.activeDevice['uuid'], srcPath).toPromise();
} catch {
iohandler.stderr('The source file was not found');
return 1;
}
if (srcFile.is_directory) {
iohandler.stderr('Cannot download a directory');
return 1;
}
try {
dstPath = Path.fromString(iohandler.positionalArgs[1], this.shellApi.working_dir);
} catch {
iohandler.stderr('The specified destination path is not valid');
return 1;
}

try {
await this.shellApi.fileService.getFromPath(ownerUuid, dstPath).toPromise();
iohandler.stderr('That file already exists');
return 1;
} catch {}

const dstFileName = dstPath.path[dstPath.path.length - 1];
try {
await this.shellApi.fileService.createFile(ownerUuid, dstFileName, srcFile.content, dstPath.parentUUID).toPromise();
return 0;
} catch {
iohandler.stderr('Could not create file');
return 1;
}
}
}
22 changes: 22 additions & 0 deletions src/app/shell/builtins/ping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import {Command, IOHandler, ArgType} from '../command';
import {ShellApi} from '../shellapi';

export class Ping extends Command {
constructor(shellApi: ShellApi) {
super('ping', shellApi);
this.addDescription('ping a device');
this.addPositionalArgument({name: 'uuid', argType: ArgType.UUID});
}

async run(iohandler: IOHandler): Promise<number> {
const uuid = iohandler.positionalArgs[0];
try {
const status = await this.shellApi.deviceService.getDeviceState(uuid).toPromise();
iohandler.stdout(`Device is ${status.online ? '' : 'not '}online`);
return 0;
} catch {
iohandler.stderr('Device not found');
return 1;
}
}
}
13 changes: 13 additions & 0 deletions src/app/shell/builtins/shutdown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {Command, IOHandler} from '../command';
import {ShellApi} from '../shellapi';

export class Shutdown extends Command {
constructor(shellApi: ShellApi) {
super('shutdown', shellApi);
this.addDescription('shutdown your own device');
}

async run(_: IOHandler): Promise<number> {
return await this.shellApi.terminal.shutdown() ? 0 : 1;
}
}
37 changes: 28 additions & 9 deletions src/app/shell/command.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,17 @@ import {ShellApi} from './shellapi';
export enum ArgType {
RAW, // just a String
PATH, // FILE or DIRECTORY
FILE, // only FILE
DIRECTORY // only DIRECTORY
FILE, // only file
DIRECTORY, // only directory
UUID
}

export function checkArgType(argType: ArgType, arg: string): boolean {
if (argType === ArgType.UUID) {
return arg.match(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/) !== null;
} else {
return true;
}
}

export interface PositionalArgument {
Expand Down Expand Up @@ -60,8 +69,9 @@ export abstract class Command {
if (this.subcommands.size > 0) {
stdout('subcommands:');
this.subcommands.forEach((subcommand: Command, name: string) => {
// TODO use \t
// TODO align the descriptions
// TODO:
// use \t
// align the descriptions
stdout(` ${name} - ${subcommand.description}`);
});
}
Expand All @@ -82,14 +92,23 @@ export abstract class Command {
this.showHelp(iohandler.stdout);
return 0;
}

const posArgsLen = this.positionalArgs.length;
if (posArgsLen < args.length && this.capturesAllArgs
|| args.length <= posArgsLen && args.length >= posArgsLen - this.optionalArgs
if (!(posArgsLen < args.length && this.capturesAllArgs
|| args.length <= posArgsLen && args.length >= posArgsLen - this.optionalArgs)
) {
return await this.run(iohandler);
this.showHelp(iohandler.stdout);
return 1;
}
// check all args for validity
for (let i = 0; i < args.length; i++) {
const arg = i >= posArgsLen ? this.positionalArgs[posArgsLen - 1] : this.positionalArgs[i];
if (!checkArgType(arg.argType, args[i])) {
iohandler.stderr(`Arg "${args[i]}" is invalid`);
return 1;
}
}
this.showHelp(iohandler.stdout);
return 1;
return await this.run(iohandler);
}


Expand Down
2 changes: 2 additions & 0 deletions src/app/shell/shellapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import {FileService} from '../api/files/file.service';
import {Device} from '../api/devices/device';
import {WindowDelegate} from '../desktop/window/window-delegate';
import {File} from 'src/app/api/files/file';
import {DeviceService} from '../api/devices/device.service';


export class ShellApi {
constructor(
public websocket: WebsocketService,
public settings: SettingsService,
public fileService: FileService,
public deviceService: DeviceService,
public domSanitizer: DomSanitizer,
public windowDelegate: WindowDelegate,
public activeDevice: Device,
Expand Down

0 comments on commit bc9d2a7

Please sign in to comment.