mirror of
https://github.com/tsl0922/ttyd.git
synced 2026-02-05 01:24:32 +01:00
html: replace with preact version
This commit is contained in:
51
html/.gitignore
vendored
51
html/.gitignore
vendored
@@ -1,49 +1,4 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
dist/
|
||||
/build
|
||||
/dist
|
||||
/*.log
|
||||
@@ -11,4 +11,4 @@ task('default', () => {
|
||||
return src('dist/index.html')
|
||||
.pipe(inlinesource())
|
||||
.pipe(dest('../src/'));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
|
||||
<title>ttyd - Terminal</title>
|
||||
<link inline rel="icon" type="image/png" href="favicon.png">
|
||||
</head>
|
||||
<body>
|
||||
<div id="terminal-container"></div>
|
||||
<div class="modal" id="modal">
|
||||
<div class="modal-background"></div>
|
||||
<div class="modal-content">
|
||||
<div class="box">
|
||||
<header id="header"></header>
|
||||
<div id="status">
|
||||
<strong>Files remaining: </strong><span id="files-remaining"></span>,
|
||||
<strong>Bytes remaining: </strong><span id="bytes-remaining"></span>
|
||||
</div>
|
||||
<div id="choose" class="file has-name is-fullwidth">
|
||||
<label class="file-label">
|
||||
<input id="files" class="file-input" type="file" multiple>
|
||||
<span class="file-cta">
|
||||
<strong class="file-label">
|
||||
Choose file(s)…
|
||||
</strong>
|
||||
</span>
|
||||
<span id="file-names" class="file-name"></span>
|
||||
</label>
|
||||
</div>
|
||||
<div id="progress">
|
||||
<p id="file-name"></p>
|
||||
<progress id="progress-bar" class="progress" max="100"></progress>
|
||||
<p id="progress-info">
|
||||
<span id="bytes-received">-</span>/<span id="bytes-file">-</span> (<span id="percent-received"></span>)
|
||||
transferred
|
||||
<a id="skip" class="button">Skip</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="auth_token.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
234
html/js/app.ts
234
html/js/app.ts
@@ -1,234 +0,0 @@
|
||||
import '../sass/app.scss';
|
||||
|
||||
import { Terminal, ITerminalOptions, ITheme } from 'xterm';
|
||||
import * as fit from 'xterm/lib/addons/fit/fit';
|
||||
import * as Zmodem from 'zmodem.js/src/zmodem_browser';
|
||||
|
||||
import * as overlay from './overlay';
|
||||
import { Modal } from './zmodem';
|
||||
|
||||
Terminal.applyAddon(fit);
|
||||
Terminal.applyAddon(overlay);
|
||||
|
||||
const enum TtydCommand {
|
||||
// server side
|
||||
OUTPUT = '0',
|
||||
SET_WINDOW_TITLE = '1',
|
||||
SET_PREFERENCES = '2',
|
||||
SET_RECONNECT = '3',
|
||||
// client side
|
||||
INPUT = '0',
|
||||
RESIZE_TERMINAL = '1'
|
||||
}
|
||||
|
||||
interface ITtydTerminal extends Terminal {
|
||||
reconnectTimeout: number;
|
||||
|
||||
showOverlay(msg: string, timeout?: number): void;
|
||||
fit(): void;
|
||||
}
|
||||
|
||||
export interface IWindowWithTerminal extends Window {
|
||||
term: ITtydTerminal;
|
||||
resizeTimeout?: number;
|
||||
tty_auth_token?: string;
|
||||
}
|
||||
declare let window: IWindowWithTerminal;
|
||||
|
||||
const modal = new Modal();
|
||||
const terminalContainer = document.getElementById('terminal-container');
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||
const wsPath = window.location.pathname.endsWith('/') ? 'ws' : '/ws';
|
||||
const url = [protocol, window.location.host, window.location.pathname, wsPath, window.location.search].join('');
|
||||
const textDecoder = new TextDecoder();
|
||||
const textEncoder = new TextEncoder();
|
||||
const termOptions = {
|
||||
fontSize: 13,
|
||||
fontFamily: '"Menlo for Powerline", Menlo, Consolas, "Liberation Mono", Courier, monospace',
|
||||
theme: {
|
||||
foreground: '#d2d2d2',
|
||||
background: '#2b2b2b',
|
||||
cursor: '#adadad',
|
||||
black: '#000000',
|
||||
red: '#d81e00',
|
||||
green: '#5ea702',
|
||||
yellow: '#cfae00',
|
||||
blue: '#427ab3',
|
||||
magenta: '#89658e',
|
||||
cyan: '#00a7aa',
|
||||
white: '#dbded8',
|
||||
brightBlack: '#686a66',
|
||||
brightRed: '#f54235',
|
||||
brightGreen: '#99e343',
|
||||
brightYellow: '#fdeb61',
|
||||
brightBlue: '#84b0d8',
|
||||
brightMagenta: '#bc94b7',
|
||||
brightCyan: '#37e6e8',
|
||||
brightWhite: '#f1f1f0'
|
||||
} as ITheme
|
||||
} as ITerminalOptions;
|
||||
|
||||
const authToken = (typeof window.tty_auth_token !== 'undefined') ? window.tty_auth_token : null;
|
||||
let autoReconnect = -1;
|
||||
let term: ITtydTerminal;
|
||||
let title: string;
|
||||
let wsError: boolean;
|
||||
|
||||
const openWs = function(): void {
|
||||
const ws = new WebSocket(url, ['tty']);
|
||||
const sendMessage = function (message: string): void {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
ws.send(textEncoder.encode(message));
|
||||
}
|
||||
};
|
||||
const unloadCallback = function (event: BeforeUnloadEvent): string {
|
||||
const message = 'Close terminal? this will also terminate the command.';
|
||||
event.returnValue = message;
|
||||
return message;
|
||||
};
|
||||
const resetTerm = function(): void {
|
||||
modal.hide();
|
||||
clearTimeout(term.reconnectTimeout);
|
||||
if (ws.readyState !== WebSocket.CLOSED) {
|
||||
ws.close();
|
||||
}
|
||||
openWs();
|
||||
};
|
||||
|
||||
const zsentry = new Zmodem.Sentry({
|
||||
to_terminal: function(octets: ArrayBuffer): any {
|
||||
const buffer = new Uint8Array(octets).buffer;
|
||||
term.write(textDecoder.decode(buffer));
|
||||
},
|
||||
|
||||
sender: function(octets: number[]): any {
|
||||
// limit max packet size to 4096
|
||||
while (octets.length) {
|
||||
const chunk = octets.splice(0, 4095);
|
||||
const buffer = new Uint8Array(chunk.length + 1);
|
||||
buffer[0] = TtydCommand.INPUT.charCodeAt(0);
|
||||
buffer.set(chunk, 1);
|
||||
ws.send(buffer);
|
||||
}
|
||||
},
|
||||
|
||||
on_retract: function(): any {
|
||||
// console.log('on_retract');
|
||||
},
|
||||
|
||||
on_detect: function(detection: any): any {
|
||||
term.setOption('disableStdin', true);
|
||||
const zsession = detection.confirm();
|
||||
const promise = zsession.type === 'send' ? modal.handleSend(zsession) : modal.handleReceive(zsession);
|
||||
promise.catch(console.error.bind(console)).then(() => {
|
||||
modal.hide();
|
||||
term.setOption('disableStdin', false);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ws.binaryType = 'arraybuffer';
|
||||
|
||||
ws.onopen = function(): void {
|
||||
console.log('[ttyd] websocket opened');
|
||||
wsError = false;
|
||||
sendMessage(JSON.stringify({AuthToken: authToken}));
|
||||
|
||||
if (typeof term !== 'undefined') {
|
||||
term.dispose();
|
||||
}
|
||||
|
||||
// expose term handle for some programatic cases
|
||||
// which need to get the content of the terminal
|
||||
term = window.term = <ITtydTerminal>new Terminal(termOptions);
|
||||
|
||||
term.onResize((size: {cols: number, rows: number}) => {
|
||||
if (ws.readyState === WebSocket.OPEN) {
|
||||
sendMessage(TtydCommand.RESIZE_TERMINAL + JSON.stringify({columns: size.cols, rows: size.rows}));
|
||||
}
|
||||
setTimeout(() => term.showOverlay(size.cols + 'x' + size.rows), 500);
|
||||
});
|
||||
|
||||
term.onTitleChange((data: string) => {
|
||||
if (data && data !== '') {
|
||||
document.title = (data + ' | ' + title);
|
||||
}
|
||||
});
|
||||
|
||||
term.onData((data: string) => sendMessage(TtydCommand.INPUT + data));
|
||||
|
||||
while (terminalContainer.firstChild) {
|
||||
terminalContainer.removeChild(terminalContainer.firstChild);
|
||||
}
|
||||
|
||||
// https://stackoverflow.com/a/27923937/1727928
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(window.resizeTimeout);
|
||||
window.resizeTimeout = <number><any>setTimeout(() => term.fit(), 250);
|
||||
});
|
||||
window.addEventListener('beforeunload', unloadCallback);
|
||||
|
||||
term.open(terminalContainer);
|
||||
term.fit();
|
||||
term.focus();
|
||||
};
|
||||
|
||||
ws.onmessage = function(event: MessageEvent): void {
|
||||
const rawData = new Uint8Array(event.data);
|
||||
const cmd = String.fromCharCode(rawData[0]);
|
||||
const data = rawData.slice(1).buffer;
|
||||
switch (cmd) {
|
||||
case TtydCommand.OUTPUT:
|
||||
try {
|
||||
zsentry.consume(data);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
resetTerm();
|
||||
}
|
||||
break;
|
||||
case TtydCommand.SET_WINDOW_TITLE:
|
||||
title = textDecoder.decode(data);
|
||||
document.title = title;
|
||||
break;
|
||||
case TtydCommand.SET_PREFERENCES:
|
||||
const preferences = JSON.parse(textDecoder.decode(data));
|
||||
Object.keys(preferences).forEach((key) => {
|
||||
console.log('[ttyd] xterm option: ' + key + '=' + preferences[key]);
|
||||
term.setOption(key, preferences[key]);
|
||||
});
|
||||
break;
|
||||
case TtydCommand.SET_RECONNECT:
|
||||
autoReconnect = JSON.parse(textDecoder.decode(data));
|
||||
console.log('[ttyd] reconnect: ' + autoReconnect + ' seconds');
|
||||
break;
|
||||
default:
|
||||
console.log('[ttyd] unknown command: ' + cmd);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
ws.onclose = function(event: CloseEvent): void {
|
||||
console.log('[ttyd] websocket closed, code: ' + event.code);
|
||||
modal.hide();
|
||||
if (term) {
|
||||
if (!wsError) {
|
||||
term.showOverlay('Connection Closed', null);
|
||||
}
|
||||
}
|
||||
window.removeEventListener('beforeunload', unloadCallback);
|
||||
// 1008: POLICY_VIOLATION - Auth failure
|
||||
if (event.code === 1008) {
|
||||
window.location.reload();
|
||||
}
|
||||
// 1000: CLOSE_NORMAL
|
||||
if (event.code !== 1000 && autoReconnect > 0) {
|
||||
term.reconnectTimeout = <number><any>setTimeout(openWs, autoReconnect * 1000);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
if (document.readyState === 'complete' || document.readyState !== 'loading') {
|
||||
openWs();
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', openWs);
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
// ported from hterm.Terminal.prototype.showOverlay
|
||||
// https://chromium.googlesource.com/apps/libapps/+/master/hterm/js/hterm_terminal.js
|
||||
import { Terminal } from 'xterm';
|
||||
|
||||
interface IOverlayAddonTerminal extends Terminal {
|
||||
__overlayNode?: HTMLElement;
|
||||
__overlayTimeout?: number;
|
||||
}
|
||||
|
||||
export function showOverlay(term: Terminal, msg: string, timeout: number): void {
|
||||
const addonTerminal = <IOverlayAddonTerminal> term;
|
||||
if (!addonTerminal.__overlayNode) {
|
||||
if (!term.element) {
|
||||
return;
|
||||
}
|
||||
addonTerminal.__overlayNode = document.createElement('div');
|
||||
addonTerminal.__overlayNode.style.cssText = (
|
||||
'border-radius: 15px;' +
|
||||
'font-size: xx-large;' +
|
||||
'opacity: 0.75;' +
|
||||
'padding: 0.2em 0.5em 0.2em 0.5em;' +
|
||||
'position: absolute;' +
|
||||
'-webkit-user-select: none;' +
|
||||
'-webkit-transition: opacity 180ms ease-in;' +
|
||||
'-moz-user-select: none;' +
|
||||
'-moz-transition: opacity 180ms ease-in;');
|
||||
|
||||
addonTerminal.__overlayNode.addEventListener('mousedown', (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}, true);
|
||||
}
|
||||
addonTerminal.__overlayNode.style.color = '#101010';
|
||||
addonTerminal.__overlayNode.style.backgroundColor = '#f0f0f0';
|
||||
|
||||
addonTerminal.__overlayNode.textContent = msg;
|
||||
addonTerminal.__overlayNode.style.opacity = '0.75';
|
||||
|
||||
if (!addonTerminal.__overlayNode.parentNode) {
|
||||
term.element.appendChild(addonTerminal.__overlayNode);
|
||||
}
|
||||
|
||||
const divSize = term.element.getBoundingClientRect();
|
||||
const overlaySize = addonTerminal.__overlayNode.getBoundingClientRect();
|
||||
|
||||
addonTerminal.__overlayNode.style.top = (divSize.height - overlaySize.height) / 2 + 'px';
|
||||
addonTerminal.__overlayNode.style.left = (divSize.width - overlaySize.width) / 2 + 'px';
|
||||
|
||||
if (addonTerminal.__overlayTimeout) {
|
||||
clearTimeout(addonTerminal.__overlayTimeout);
|
||||
}
|
||||
|
||||
if (timeout === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
addonTerminal.__overlayTimeout = <number><any>setTimeout(() => {
|
||||
addonTerminal.__overlayNode.style.opacity = '0';
|
||||
addonTerminal.__overlayTimeout = <number><any>setTimeout(() => {
|
||||
if (addonTerminal.__overlayNode.parentNode) {
|
||||
addonTerminal.__overlayNode.parentNode.removeChild(addonTerminal.__overlayNode);
|
||||
}
|
||||
addonTerminal.__overlayTimeout = null;
|
||||
addonTerminal.__overlayNode.style.opacity = '0.75';
|
||||
}, 200);
|
||||
}, timeout || 1500);
|
||||
}
|
||||
|
||||
export function apply(terminalConstructor: typeof Terminal): void {
|
||||
(<any>terminalConstructor.prototype).showOverlay = function (msg: string, timeout?: number): void {
|
||||
return showOverlay(this, msg, timeout);
|
||||
};
|
||||
}
|
||||
@@ -1,190 +0,0 @@
|
||||
import * as Zmodem from 'zmodem.js/src/zmodem_browser';
|
||||
|
||||
class Status {
|
||||
element: HTMLElement;
|
||||
filesRemaining: HTMLElement;
|
||||
bytesRemaining: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
this.element = document.getElementById('status');
|
||||
this.filesRemaining = document.getElementById('files-remaining');
|
||||
this.bytesRemaining = document.getElementById('bytes-remaining');
|
||||
}
|
||||
}
|
||||
|
||||
class Choose {
|
||||
element: HTMLElement;
|
||||
files: HTMLInputElement;
|
||||
filesNames: HTMLElement;
|
||||
|
||||
constructor() {
|
||||
this.element = document.getElementById('choose');
|
||||
this.files = <HTMLInputElement>document.getElementById('files');
|
||||
this.filesNames = document.getElementById('file-names');
|
||||
}
|
||||
}
|
||||
|
||||
class Progress {
|
||||
element: HTMLElement;
|
||||
fileName: HTMLElement;
|
||||
progressBar: HTMLProgressElement;
|
||||
bytesReceived: HTMLElement;
|
||||
bytesFile: HTMLElement;
|
||||
percentReceived: HTMLElement;
|
||||
skip: HTMLLinkElement;
|
||||
|
||||
constructor() {
|
||||
this.element = document.getElementById('progress');
|
||||
this.fileName = document.getElementById('file-name');
|
||||
this.progressBar = <HTMLProgressElement>document.getElementById('progress-bar');
|
||||
this.bytesReceived = document.getElementById('bytes-received');
|
||||
this.bytesFile = document.getElementById('bytes-file');
|
||||
this.percentReceived = document.getElementById('percent-received');
|
||||
this.skip = <HTMLLinkElement>document.getElementById('skip');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
function bytesHuman (bytes: any, precision: number): string {
|
||||
if (isNaN(parseFloat(bytes)) || !isFinite(bytes)) return '-';
|
||||
if (bytes === 0) return '0';
|
||||
if (typeof precision === 'undefined') precision = 1;
|
||||
const units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
const num = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (bytes / Math.pow(1024, Math.floor(num))).toFixed(precision) + ' ' + units[num];
|
||||
}
|
||||
|
||||
export class Modal {
|
||||
element: HTMLElement;
|
||||
header: HTMLElement;
|
||||
status: Status;
|
||||
choose: Choose;
|
||||
progress: Progress;
|
||||
|
||||
constructor() {
|
||||
this.element = document.getElementById('modal');
|
||||
this.header = document.getElementById('header');
|
||||
this.status = new Status();
|
||||
this.choose = new Choose();
|
||||
this.progress = new Progress();
|
||||
}
|
||||
|
||||
public reset(title: string): void {
|
||||
this.header.textContent = title;
|
||||
this.status.element.style.display = 'none';
|
||||
this.choose.element.style.display = 'none';
|
||||
this.progress.element.style.display = 'none';
|
||||
this.progress.bytesReceived.textContent = '-';
|
||||
this.progress.percentReceived.textContent = '-%';
|
||||
this.progress.progressBar.textContent = '0%';
|
||||
this.progress.progressBar.value = 0;
|
||||
this.progress.skip.style.display = 'none';
|
||||
}
|
||||
|
||||
public hide(): void {
|
||||
this.element.classList.remove('is-active');
|
||||
}
|
||||
|
||||
public updateFileInfo(fileInfo: any): void {
|
||||
this.status.element.style.display = '';
|
||||
this.choose.element.style.display = 'none';
|
||||
this.progress.element.style.display = '';
|
||||
this.status.filesRemaining.textContent = fileInfo.files_remaining;
|
||||
this.status.bytesRemaining.textContent = bytesHuman(fileInfo.bytes_remaining, 2);
|
||||
this.progress.fileName.textContent = fileInfo.name;
|
||||
}
|
||||
|
||||
public showReceive(xfer: any): void {
|
||||
this.reset('Receiving files');
|
||||
this.updateFileInfo(xfer.get_details());
|
||||
this.progress.skip.disabled = false;
|
||||
this.progress.skip.onclick = function (): void {
|
||||
(<HTMLLinkElement>this).disabled = true;
|
||||
xfer.skip();
|
||||
};
|
||||
this.progress.skip.style.display = '';
|
||||
this.element.classList.add('is-active');
|
||||
}
|
||||
|
||||
public showSend(callback: (files: FileList) => any): void {
|
||||
this.reset('Sending files');
|
||||
this.choose.element.style.display = '';
|
||||
this.choose.files.disabled = false;
|
||||
this.choose.files.value = '';
|
||||
this.choose.filesNames.textContent = '';
|
||||
const self: Modal = this;
|
||||
this.choose.files.onchange = function (): void {
|
||||
(<HTMLInputElement>this).disabled = true;
|
||||
const files: FileList = (<HTMLInputElement>this).files;
|
||||
let fileNames: string = '';
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
if (i === 0) {
|
||||
fileNames = files[i].name;
|
||||
} else {
|
||||
fileNames += ', ' + files[i].name;
|
||||
}
|
||||
}
|
||||
self.choose.filesNames.textContent = fileNames;
|
||||
callback(files);
|
||||
};
|
||||
this.element.classList.add('is-active');
|
||||
}
|
||||
|
||||
public updateProgress(xfer: any): void {
|
||||
const size = xfer.get_details().size;
|
||||
const offset = xfer.get_offset();
|
||||
this.progress.bytesReceived.textContent = bytesHuman(offset, 2);
|
||||
this.progress.bytesFile.textContent = bytesHuman(size, 2);
|
||||
|
||||
const percentReceived = (100 * offset / size).toFixed(2);
|
||||
this.progress.percentReceived.textContent = percentReceived + '%';
|
||||
|
||||
this.progress.progressBar.textContent = percentReceived + '%';
|
||||
this.progress.progressBar.setAttribute('value', percentReceived);
|
||||
}
|
||||
|
||||
public handleSend(zsession: any): Promise<any> {
|
||||
return new Promise((res) => {
|
||||
this.showSend((files) => {
|
||||
Zmodem.Browser.send_files(
|
||||
zsession,
|
||||
files,
|
||||
{
|
||||
on_progress: (obj, xfer) => {
|
||||
this.updateFileInfo(xfer.get_details());
|
||||
this.updateProgress(xfer);
|
||||
},
|
||||
on_file_complete: (obj) => {
|
||||
// console.log(obj);
|
||||
}
|
||||
}
|
||||
).then(
|
||||
zsession.close.bind(zsession),
|
||||
console.error.bind(console)
|
||||
).then(() => res());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public handleReceive(zsession: any): Promise<any> {
|
||||
zsession.on('offer', (xfer) => {
|
||||
this.showReceive(xfer);
|
||||
const fileBuffer = [];
|
||||
xfer.on('input', (payload) => {
|
||||
this.updateProgress(xfer);
|
||||
fileBuffer.push(new Uint8Array(payload));
|
||||
});
|
||||
xfer.accept().then(() => {
|
||||
Zmodem.Browser.save_to_disk(
|
||||
fileBuffer,
|
||||
xfer.get_details().name
|
||||
);
|
||||
}, console.error.bind(console));
|
||||
});
|
||||
const promise = new Promise((res) => {
|
||||
zsession.on('session_end', () => res());
|
||||
});
|
||||
zsession.start();
|
||||
return promise;
|
||||
}
|
||||
}
|
||||
@@ -1,45 +1,48 @@
|
||||
{
|
||||
"name": "ttyd",
|
||||
"version": "1.1.0",
|
||||
"description": "Share your terminal over the web",
|
||||
"main": "js/app.js",
|
||||
"repository": {
|
||||
"url": "git@github.com:tsl0922/ttyd.git",
|
||||
"type": "git"
|
||||
},
|
||||
"author": "Shuanglei Tao <tsl0922@gmail.com>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production webpack --config webpack.prod.js && gulp",
|
||||
"prestart": "gulp clean",
|
||||
"start": "webpack-dev-server --config webpack.dev.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"xterm": "^3.13.2",
|
||||
"zmodem.js": "^0.1.9"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"css-loader": "^2.1.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-clean": "^0.4.0",
|
||||
"gulp-inline-source": "^4.0.0",
|
||||
"html-webpack-inline-source-plugin": "^0.0.10",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"mini-css-extract-plugin": "^0.6.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"terser-webpack-plugin": "^1.2.4",
|
||||
"ts-loader": "^6.0.0",
|
||||
"tslint": "^5.16.0",
|
||||
"tslint-consistent-codestyle": "^1.15.1",
|
||||
"tslint-loader": "^3.5.4",
|
||||
"typescript": "^3.4.5",
|
||||
"webpack": "^4.31.0",
|
||||
"webpack-cli": "^3.3.2",
|
||||
"webpack-dev-server": "^3.3.1",
|
||||
"webpack-merge": "^4.2.1"
|
||||
}
|
||||
"private": true,
|
||||
"name": "ttyd",
|
||||
"version": "1.0.0",
|
||||
"description": "Share your terminal over the web",
|
||||
"repository": {
|
||||
"url": "git@github.com:tsl0922/ttyd.git",
|
||||
"type": "git"
|
||||
},
|
||||
"author": "Shuanglei Tao <tsl0922@gmail.com>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prestart": "gulp clean",
|
||||
"start": "webpack-dev-server",
|
||||
"build": "NODE_ENV=production webpack && gulp",
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"css-loader": "^1.0.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-clean": "^0.4.0",
|
||||
"gulp-inline-source": "^4.0.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"mini-css-extract-plugin": "^0.6.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"terser-webpack-plugin": "^1.3.0",
|
||||
"ts-loader": "^6.0.1",
|
||||
"tslint": "^5.16.0",
|
||||
"tslint-consistent-codestyle": "^1.15.1",
|
||||
"tslint-loader": "^3.5.4",
|
||||
"typescript": "^3.4.5",
|
||||
"webpack": "^4.32.2",
|
||||
"webpack-cli": "^3.3.2",
|
||||
"webpack-dev-server": "^3.4.1",
|
||||
"webpack-merge": "^4.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"decko": "^1.2.0",
|
||||
"preact": "^8.4.2",
|
||||
"preact-compat": "^3.18.5",
|
||||
"preact-router": "^2.6.1",
|
||||
"xterm": "^3.13.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
@import "~xterm/src/xterm.css";
|
||||
@import "modal";
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
min-height: 100%;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#terminal-container {
|
||||
width: auto;
|
||||
height: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 0;
|
||||
background-color: #2b2b2b;
|
||||
.terminal {
|
||||
padding: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
#modal {
|
||||
strong {
|
||||
color: #268bd2;
|
||||
}
|
||||
span {
|
||||
color: #2aa198;
|
||||
}
|
||||
header {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding-bottom: 10px;
|
||||
margin-bottom: 10px;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
}
|
||||
|
||||
#progress {
|
||||
padding-top: 10px;
|
||||
progress {
|
||||
margin: 10px 0;
|
||||
}
|
||||
color: #93a1a1;
|
||||
span {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
#status {
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#choose {
|
||||
padding-top: 10px;
|
||||
.file-name {
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
#file-name {
|
||||
background-color: #fafffd;
|
||||
text-align: center;
|
||||
}
|
||||
@@ -1,149 +0,0 @@
|
||||
.box {
|
||||
background-color: #fff;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 3px hsla(0, 0%, 4%, 0.1), 0 0 0 1px hsla(0, 0%, 4%, 0.1);
|
||||
color: #4a4a4a;
|
||||
display: block;
|
||||
padding: 1.25rem;
|
||||
}
|
||||
|
||||
.progress {
|
||||
border: none;
|
||||
border-radius: 290486px;
|
||||
display: block;
|
||||
height: 1rem;
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
&::-webkit-progress-bar {
|
||||
background-color: #dbdbdb;
|
||||
}
|
||||
&::-webkit-progress-value {
|
||||
background-color: #3273dc;
|
||||
}
|
||||
&::-moz-progress-bar {
|
||||
background-color: #3273dc;
|
||||
}
|
||||
&::-ms-fill {
|
||||
background-color: #3273dc;
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
align-items: center;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
position: fixed;
|
||||
z-index: 40;
|
||||
&.is-active {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.file-input {
|
||||
height: .01em;
|
||||
left: 0;
|
||||
outline: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: .01em;
|
||||
}
|
||||
|
||||
.file-cta, .file-name {
|
||||
align-items: center;
|
||||
box-shadow: none;
|
||||
display: inline-flex;
|
||||
height: 2.25em;
|
||||
justify-content: flex-start;
|
||||
line-height: 1.5;
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
border-color: #dbdbdb;
|
||||
border-radius: 3px;
|
||||
font-size: 1em;
|
||||
padding: calc(.375em - 1px) 1em;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.file-name {
|
||||
border-color: #dbdbdb;
|
||||
border-style: solid;
|
||||
border-width: 1px 1px 1px 0;
|
||||
display: block;
|
||||
max-width: 16em;
|
||||
overflow: hidden;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
&:active, &:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.file-cta {
|
||||
background-color: #f5f5f5;
|
||||
color: #4a4a4a;
|
||||
&:active, &:focus {
|
||||
outline: none;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
float: right;
|
||||
align-items: center;
|
||||
border-radius: 2px;
|
||||
display: inline-flex;
|
||||
font-size: .75rem;
|
||||
height: 2em;
|
||||
line-height: 1.5;
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
background-color: #3273dc;
|
||||
border-color: transparent;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
justify-content: center;
|
||||
padding: calc(.375em - 1px) 0.75em;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
&:active, &:focus {
|
||||
outline: none;
|
||||
}
|
||||
&:hover {
|
||||
background-color: #276cda;
|
||||
border-color: transparent;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-background {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
background-color: hsla(0, 0%, 4%, 0.86);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
margin: 0 20px;
|
||||
max-height: calc(100vh - 160px);
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media print, screen and (min-width: 769px) {
|
||||
.modal-content {
|
||||
margin: 0 auto;
|
||||
max-height: calc(100vh - 40px);
|
||||
width: 640px;
|
||||
}
|
||||
}
|
||||
45
html/src/components/app.tsx
Normal file
45
html/src/components/app.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
import { ITerminalOptions, ITheme } from 'xterm';
|
||||
import Terminal from './terminal';
|
||||
|
||||
if ((module as any).hot) {
|
||||
require('preact/debug');
|
||||
}
|
||||
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||
const wsPath = window.location.pathname.endsWith('/') ? 'ws' : '/ws';
|
||||
const url = [protocol, window.location.host, window.location.pathname, wsPath, window.location.search].join('');
|
||||
const termOptions = {
|
||||
fontSize: 13,
|
||||
fontFamily: '"Menlo for Powerline", Menlo, Consolas, "Liberation Mono", Courier, monospace',
|
||||
theme: {
|
||||
foreground: '#d2d2d2',
|
||||
background: '#2b2b2b',
|
||||
cursor: '#adadad',
|
||||
black: '#000000',
|
||||
red: '#d81e00',
|
||||
green: '#5ea702',
|
||||
yellow: '#cfae00',
|
||||
blue: '#427ab3',
|
||||
magenta: '#89658e',
|
||||
cyan: '#00a7aa',
|
||||
white: '#dbded8',
|
||||
brightBlack: '#686a66',
|
||||
brightRed: '#f54235',
|
||||
brightGreen: '#99e343',
|
||||
brightYellow: '#fdeb61',
|
||||
brightBlue: '#84b0d8',
|
||||
brightMagenta: '#bc94b7',
|
||||
brightCyan: '#37e6e8',
|
||||
brightWhite: '#f1f1f0'
|
||||
} as ITheme
|
||||
} as ITerminalOptions;
|
||||
|
||||
export default class App extends Component {
|
||||
public render() {
|
||||
return (
|
||||
<Terminal id='terminal-container' url={url} options={termOptions} />
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
@@ -1,5 +1,5 @@
|
||||
import './style/index.scss';
|
||||
import { h, render } from 'preact';
|
||||
import App from './components/app';
|
||||
import './style/index.scss';
|
||||
|
||||
render(<App />, document.body);
|
||||
@@ -1,11 +1,22 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/",
|
||||
"sourceMap": true,
|
||||
"module": "commonjs",
|
||||
"target": "es5",
|
||||
"lib": [ "dom", "es5", "es2015.promise"],
|
||||
"target": "ES5",
|
||||
"module": "ESNext",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es5",
|
||||
"es2015.promise"
|
||||
],
|
||||
"allowJs": true,
|
||||
"jsx": "react",
|
||||
"allowJs": true
|
||||
}
|
||||
}
|
||||
"jsxFactory": "h",
|
||||
"sourceMap": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
const path = require('path');
|
||||
const merge = require('webpack-merge');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
const devMode = process.env.NODE_ENV !== 'production';
|
||||
|
||||
module.exports = {
|
||||
const baseConfig = {
|
||||
context: path.resolve(__dirname, 'src'),
|
||||
entry: {
|
||||
app: './js/app.ts'
|
||||
app: './index.tsx'
|
||||
},
|
||||
output: {
|
||||
path: __dirname + '/dist',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
filename: devMode ? '[name].js' : '[name].[hash].js',
|
||||
},
|
||||
module: {
|
||||
@@ -37,11 +44,20 @@ module.exports = {
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
{ from: 'favicon.png', to: '.' }
|
||||
{ from: './favicon.png', to: '.' }
|
||||
], {}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: devMode ? '[name].css' : '[name].[hash].css',
|
||||
chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
inject: false,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
},
|
||||
title: 'ttyd - Terminal',
|
||||
template: './template.html'
|
||||
})
|
||||
],
|
||||
performance : {
|
||||
@@ -49,3 +65,39 @@ module.exports = {
|
||||
},
|
||||
devtool: 'source-map',
|
||||
};
|
||||
|
||||
const devConfig = {
|
||||
mode: 'development',
|
||||
devServer: {
|
||||
contentBase: path.join(__dirname, 'dist'),
|
||||
compress: true,
|
||||
port: 9000,
|
||||
proxy: [{
|
||||
context: ['/auth_token.js', '/ws'],
|
||||
target: 'http://localhost:7681',
|
||||
ws: true
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const prodConfig = {
|
||||
mode: 'production',
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
sourceMap: true
|
||||
}),
|
||||
new OptimizeCSSAssetsPlugin({
|
||||
cssProcessorOptions: {
|
||||
map: {
|
||||
inline: false,
|
||||
annotation: true
|
||||
}
|
||||
}
|
||||
}),
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = merge(baseConfig, devMode ? devConfig : prodConfig);
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
const path = require('path');
|
||||
const merge = require('webpack-merge');
|
||||
const config = require('./webpack.config.js');
|
||||
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
module.exports = merge(config, {
|
||||
mode: 'development',
|
||||
devServer: {
|
||||
contentBase: path.join(__dirname, 'dist'),
|
||||
compress: true,
|
||||
port: 9000,
|
||||
proxy: [{
|
||||
context: ['/auth_token.js', '/ws'],
|
||||
target: 'http://localhost:7681',
|
||||
ws: true
|
||||
}]
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: 'index.html'
|
||||
})
|
||||
]
|
||||
});
|
||||
@@ -1,37 +0,0 @@
|
||||
const merge = require('webpack-merge');
|
||||
const config = require('./webpack.config.js');
|
||||
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const HtmlWebpackInlineSourcePlugin = require('html-webpack-inline-source-plugin');
|
||||
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
module.exports = merge(config, {
|
||||
mode: 'production',
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
sourceMap: true
|
||||
}),
|
||||
new OptimizeCSSAssetsPlugin({
|
||||
cssProcessorOptions: {
|
||||
map: {
|
||||
inline: false,
|
||||
annotation: true
|
||||
}
|
||||
}
|
||||
}),
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
},
|
||||
inlineSource: '.(js|css)$',
|
||||
template: 'index.html',
|
||||
}),
|
||||
new HtmlWebpackInlineSourcePlugin(),
|
||||
]
|
||||
});
|
||||
2465
html/yarn.lock
2465
html/yarn.lock
File diff suppressed because it is too large
Load Diff
4
preact/.gitignore
vendored
4
preact/.gitignore
vendored
@@ -1,4 +0,0 @@
|
||||
node_modules
|
||||
/build
|
||||
/dist
|
||||
/*.log
|
||||
@@ -1,9 +0,0 @@
|
||||
# preact html frontend
|
||||
|
||||
> **This is a work in progress**
|
||||
|
||||
# Development
|
||||
|
||||
1. Install dependencies: `yarn install`
|
||||
2. Start ttyd as websocket backend
|
||||
3. Start dev server: `yarn start`
|
||||
@@ -1,14 +0,0 @@
|
||||
const { src, dest, task } = require("gulp");
|
||||
const clean = require('gulp-clean');
|
||||
const inlinesource = require('gulp-inline-source');
|
||||
|
||||
task('clean', () => {
|
||||
return src('build', {read: false, allowEmpty: true})
|
||||
.pipe(clean());
|
||||
});
|
||||
|
||||
task('default', () => {
|
||||
return src('build/index.html')
|
||||
.pipe(inlinesource())
|
||||
.pipe(dest('../src/'));
|
||||
});
|
||||
@@ -1,48 +0,0 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "ttyd",
|
||||
"version": "1.0.0",
|
||||
"description": "Share your terminal over the web",
|
||||
"repository": {
|
||||
"url": "git@github.com:tsl0922/ttyd.git",
|
||||
"type": "git"
|
||||
},
|
||||
"author": "Shuanglei Tao <tsl0922@gmail.com>",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"prestart": "gulp clean",
|
||||
"start": "webpack-dev-server",
|
||||
"build": "NODE_ENV=production webpack && gulp",
|
||||
"lint": "tslint -c tslint.json 'src/**/*.ts'"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"css-loader": "^1.0.1",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-clean": "^0.4.0",
|
||||
"gulp-inline-source": "^4.0.0",
|
||||
"html-webpack-plugin": "^3.2.0",
|
||||
"mini-css-extract-plugin": "^0.6.0",
|
||||
"node-sass": "^4.12.0",
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.1",
|
||||
"sass-loader": "^7.1.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"terser-webpack-plugin": "^1.3.0",
|
||||
"ts-loader": "^6.0.1",
|
||||
"tslint": "^5.16.0",
|
||||
"tslint-consistent-codestyle": "^1.15.1",
|
||||
"tslint-loader": "^3.5.4",
|
||||
"typescript": "^3.4.5",
|
||||
"webpack": "^4.32.2",
|
||||
"webpack-cli": "^3.3.2",
|
||||
"webpack-dev-server": "^3.4.1",
|
||||
"webpack-merge": "^4.2.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"decko": "^1.2.0",
|
||||
"preact": "^8.4.2",
|
||||
"preact-compat": "^3.18.5",
|
||||
"preact-router": "^2.6.1",
|
||||
"xterm": "^3.13.2"
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
import { h, Component } from 'preact';
|
||||
|
||||
import { ITerminalOptions, ITheme } from 'xterm';
|
||||
import Terminal from './terminal';
|
||||
|
||||
if ((module as any).hot) {
|
||||
require('preact/debug');
|
||||
}
|
||||
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss://' : 'ws://';
|
||||
const wsPath = window.location.pathname.endsWith('/') ? 'ws' : '/ws';
|
||||
const url = [protocol, window.location.host, window.location.pathname, wsPath, window.location.search].join('');
|
||||
const termOptions = {
|
||||
fontSize: 13,
|
||||
fontFamily: '"Menlo for Powerline", Menlo, Consolas, "Liberation Mono", Courier, monospace',
|
||||
theme: {
|
||||
foreground: '#d2d2d2',
|
||||
background: '#2b2b2b',
|
||||
cursor: '#adadad',
|
||||
black: '#000000',
|
||||
red: '#d81e00',
|
||||
green: '#5ea702',
|
||||
yellow: '#cfae00',
|
||||
blue: '#427ab3',
|
||||
magenta: '#89658e',
|
||||
cyan: '#00a7aa',
|
||||
white: '#dbded8',
|
||||
brightBlack: '#686a66',
|
||||
brightRed: '#f54235',
|
||||
brightGreen: '#99e343',
|
||||
brightYellow: '#fdeb61',
|
||||
brightBlue: '#84b0d8',
|
||||
brightMagenta: '#bc94b7',
|
||||
brightCyan: '#37e6e8',
|
||||
brightWhite: '#f1f1f0'
|
||||
} as ITheme
|
||||
} as ITerminalOptions;
|
||||
|
||||
export default class App extends Component {
|
||||
public render() {
|
||||
return (
|
||||
<Terminal id='terminal-container' url={url} options={termOptions} />
|
||||
);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 KiB |
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES5",
|
||||
"module": "ESNext",
|
||||
"lib": [
|
||||
"dom",
|
||||
"es5",
|
||||
"es2015.promise"
|
||||
],
|
||||
"allowJs": true,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "h",
|
||||
"sourceMap": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"experimentalDecorators": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*.tsx",
|
||||
"src/**/*.ts"
|
||||
]
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
{
|
||||
"rulesDirectory": [
|
||||
"tslint-consistent-codestyle"
|
||||
],
|
||||
"rules": {
|
||||
"array-type": [
|
||||
true,
|
||||
"array"
|
||||
],
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"curly": [
|
||||
true,
|
||||
"ignore-same-line"
|
||||
],
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"interface-name": [
|
||||
true,
|
||||
"always-prefix"
|
||||
],
|
||||
"interface-over-type-literal": true,
|
||||
"typedef": [
|
||||
true,
|
||||
"call-signature",
|
||||
"parameter"
|
||||
],
|
||||
"eofline": true,
|
||||
"new-parens": true,
|
||||
"no-duplicate-imports": true,
|
||||
"no-eval": true,
|
||||
"no-internal-module": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"one-variable-per-declaration": true,
|
||||
"no-unsafe-finally": true,
|
||||
"no-var-keyword": true,
|
||||
"prefer-const": true,
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"semicolon": [
|
||||
true,
|
||||
"always"
|
||||
],
|
||||
"trailing-comma": [
|
||||
true,
|
||||
{
|
||||
"multiline": {
|
||||
"objects": "never",
|
||||
"arrays": "never",
|
||||
"functions": "never",
|
||||
"typeLiterals": "ignore"
|
||||
},
|
||||
"esSpecCompliant": true
|
||||
}
|
||||
],
|
||||
"triple-equals": true,
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"variable-name": [
|
||||
true,
|
||||
"ban-keywords",
|
||||
"check-format",
|
||||
"allow-leading-underscore"
|
||||
],
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-module",
|
||||
"check-operator",
|
||||
"check-rest-spread",
|
||||
"check-separator",
|
||||
"check-type",
|
||||
"check-type-operator",
|
||||
"check-preblock"
|
||||
],
|
||||
"naming-convention": [
|
||||
true,
|
||||
{"type": "default", "format": "camelCase", "leadingUnderscore": "forbid"},
|
||||
{"type": "type", "format": "PascalCase"},
|
||||
{"type": "class", "format": "PascalCase"},
|
||||
{"type": "property", "modifiers": ["const"], "format": "UPPER_CASE"},
|
||||
{"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "allow"},
|
||||
{"type": "member", "modifiers": ["protected"], "format": "camelCase", "leadingUnderscore": "require"},
|
||||
{"type": "member", "modifiers": ["private"], "format": "camelCase", "leadingUnderscore": "require"},
|
||||
{"type": "variable", "modifiers": ["const"], "format": ["camelCase", "UPPER_CASE"]},
|
||||
{"type": "interface", "prefix": "I"}
|
||||
],
|
||||
"no-else-after-return": {
|
||||
"options": "allow-else-if"
|
||||
},
|
||||
"prefer-const-enum": [
|
||||
true
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
const path = require('path');
|
||||
const merge = require('webpack-merge');
|
||||
const CopyWebpackPlugin = require('copy-webpack-plugin');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
|
||||
const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
||||
const TerserPlugin = require('terser-webpack-plugin');
|
||||
|
||||
const devMode = process.env.NODE_ENV !== 'production';
|
||||
|
||||
const baseConfig = {
|
||||
context: path.resolve(__dirname, 'src'),
|
||||
entry: {
|
||||
app: './index.tsx'
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'build'),
|
||||
filename: devMode ? '[name].js' : '[name].[hash].js',
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
enforce: 'pre',
|
||||
use: 'tslint-loader',
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: 'ts-loader',
|
||||
exclude: /node_modules/
|
||||
},
|
||||
{
|
||||
test: /\.s?[ac]ss$/,
|
||||
use: [
|
||||
devMode ? 'style-loader' : MiniCssExtractPlugin.loader,
|
||||
'css-loader',
|
||||
'sass-loader',
|
||||
],
|
||||
},
|
||||
]
|
||||
},
|
||||
resolve: {
|
||||
extensions: [ '.tsx', '.ts', '.js' ]
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin([
|
||||
{ from: './favicon.png', to: '.' }
|
||||
], {}),
|
||||
new MiniCssExtractPlugin({
|
||||
filename: devMode ? '[name].css' : '[name].[hash].css',
|
||||
chunkFilename: devMode ? '[id].css' : '[id].[hash].css',
|
||||
}),
|
||||
new HtmlWebpackPlugin({
|
||||
inject: false,
|
||||
minify: {
|
||||
removeComments: true,
|
||||
collapseWhitespace: true,
|
||||
},
|
||||
title: 'ttyd - Terminal',
|
||||
template: './template.html'
|
||||
})
|
||||
],
|
||||
performance : {
|
||||
hints : false
|
||||
},
|
||||
devtool: 'source-map',
|
||||
};
|
||||
|
||||
const devConfig = {
|
||||
mode: 'development',
|
||||
devServer: {
|
||||
contentBase: path.join(__dirname, 'build'),
|
||||
compress: true,
|
||||
port: 9000,
|
||||
proxy: [{
|
||||
context: ['/auth_token.js', '/ws'],
|
||||
target: 'http://localhost:7681',
|
||||
ws: true
|
||||
}]
|
||||
}
|
||||
};
|
||||
|
||||
const prodConfig = {
|
||||
mode: 'production',
|
||||
optimization: {
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
sourceMap: true
|
||||
}),
|
||||
new OptimizeCSSAssetsPlugin({
|
||||
cssProcessorOptions: {
|
||||
map: {
|
||||
inline: false,
|
||||
annotation: true
|
||||
}
|
||||
}
|
||||
}),
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = merge(baseConfig, devMode ? devConfig : prodConfig);
|
||||
8495
preact/yarn.lock
8495
preact/yarn.lock
File diff suppressed because it is too large
Load Diff
2
src/index.html
vendored
2
src/index.html
vendored
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user