Implemented the host name auto suggestion, and added Preset feature
This commit is contained in:
@@ -15,8 +15,9 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import Exception from "./exception.js";
|
||||
import * as subscribe from "../stream/subscribe.js";
|
||||
import Exception from "./exception.js";
|
||||
import * as presets from "./presets.js";
|
||||
|
||||
export const NEXT_PROMPT = 1;
|
||||
export const NEXT_WAIT = 2;
|
||||
@@ -131,8 +132,12 @@ const defField = {
|
||||
type: "",
|
||||
value: "",
|
||||
example: "",
|
||||
readonly: false,
|
||||
suggestions(input) {
|
||||
return [];
|
||||
},
|
||||
verify(v) {
|
||||
return "OK";
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -155,19 +160,21 @@ export function field(def, f) {
|
||||
}
|
||||
|
||||
for (let i in f) {
|
||||
if (typeof n[i] !== typeof f[i]) {
|
||||
throw new Exception(
|
||||
'Field data type for "' +
|
||||
i +
|
||||
'" was not unmatched. Expecting "' +
|
||||
typeof def[i] +
|
||||
'", got "' +
|
||||
typeof f[i] +
|
||||
'" instead'
|
||||
);
|
||||
if (typeof n[i] === typeof f[i]) {
|
||||
n[i] = f[i];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
n[i] = f[i];
|
||||
throw new Exception(
|
||||
'Field data type for "' +
|
||||
i +
|
||||
'" was unmatched. Expecting "' +
|
||||
typeof n[i] +
|
||||
'", got "' +
|
||||
typeof f[i] +
|
||||
'" instead'
|
||||
);
|
||||
}
|
||||
|
||||
if (!n["name"]) {
|
||||
@@ -206,6 +213,31 @@ export function fields(definitions, fs) {
|
||||
return fss;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build command fields with preset data
|
||||
*
|
||||
* @param {object} definitions Definition of a group of fields
|
||||
* @param {object} fieldsData field data object, formated like a `defField`
|
||||
* @param {presets.Preset} presetData Preset data
|
||||
*
|
||||
* @returns {object}
|
||||
*
|
||||
*/
|
||||
export function fieldsWithPreset(definitions, fieldsData, presetData) {
|
||||
let newFields = fields(definitions, fieldsData);
|
||||
|
||||
for (let i in newFields) {
|
||||
try {
|
||||
newFields[i].value = presetData.meta(newFields[i].name);
|
||||
newFields[i].readonly = true;
|
||||
} catch (e) {
|
||||
// Do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return newFields;
|
||||
}
|
||||
|
||||
class Prompt {
|
||||
/**
|
||||
* constructor
|
||||
@@ -457,7 +489,7 @@ class Wizard {
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* @param {function} builder Command builder
|
||||
* @param {object} built Command executer
|
||||
* @param {subscribe.Subscribe} subs Wizard step subscriber
|
||||
* @param {function} done Callback which will be called when the wizard
|
||||
* is done
|
||||
@@ -468,6 +500,8 @@ class Wizard {
|
||||
this.subs = subs;
|
||||
this.done = done;
|
||||
this.closed = false;
|
||||
|
||||
this.built.run();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -583,8 +617,14 @@ class Builder {
|
||||
*/
|
||||
constructor(command) {
|
||||
this.cid = command.id();
|
||||
this.builder = (n, i, r, u, y, x, l) => {
|
||||
return command.builder(n, i, r, u, y, x, l);
|
||||
this.represeter = n => {
|
||||
return command.represet(n);
|
||||
};
|
||||
this.wizarder = (n, i, r, u, y, x, l) => {
|
||||
return command.wizard(n, i, r, u, y, x, l);
|
||||
};
|
||||
this.executer = (n, i, r, u, y, x, l) => {
|
||||
return command.execute(n, i, r, u, y, x, l);
|
||||
};
|
||||
this.launchCmd = (n, i, r, u, y, x) => {
|
||||
return command.launch(n, i, r, u, y, x);
|
||||
@@ -638,7 +678,38 @@ class Builder {
|
||||
}
|
||||
|
||||
/**
|
||||
* Build command wizard
|
||||
* Execute an automatic command wizard
|
||||
*
|
||||
* @param {stream.Streams} streams
|
||||
* @param {controls.Controls} controls
|
||||
* @param {history.History} history
|
||||
* @param {presets.Preset} preset
|
||||
* @param {object} session
|
||||
* @param {function} done Callback which will be called when wizard is done
|
||||
*
|
||||
* @returns {Wizard} Command wizard
|
||||
*
|
||||
*/
|
||||
wizard(streams, controls, history, preset, session, done) {
|
||||
let subs = new subscribe.Subscribe();
|
||||
|
||||
return new Wizard(
|
||||
this.wizarder(
|
||||
new Info(this),
|
||||
preset,
|
||||
session,
|
||||
streams,
|
||||
subs,
|
||||
controls,
|
||||
history
|
||||
),
|
||||
subs,
|
||||
done
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute an automatic command wizard
|
||||
*
|
||||
* @param {stream.Streams} streams
|
||||
* @param {controls.Controls} controls
|
||||
@@ -650,11 +721,11 @@ class Builder {
|
||||
* @returns {Wizard} Command wizard
|
||||
*
|
||||
*/
|
||||
build(streams, controls, history, config, session, done) {
|
||||
execute(streams, controls, history, config, session, done) {
|
||||
let subs = new subscribe.Subscribe();
|
||||
|
||||
return new Wizard(
|
||||
this.builder(
|
||||
this.executer(
|
||||
new Info(this),
|
||||
config,
|
||||
session,
|
||||
@@ -707,19 +778,44 @@ class Builder {
|
||||
launcher(config) {
|
||||
return this.name() + ":" + encodeURI(this.launcherCmd(config));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconfigure the preset data for the command wizard
|
||||
*
|
||||
* @param {presets.Preset} n preset
|
||||
*
|
||||
* @return {presets.Preset} modified new preset
|
||||
*/
|
||||
represet(n) {
|
||||
return this.represeter(n);
|
||||
}
|
||||
}
|
||||
|
||||
export class Preset {
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* @param {presets.Preset} preset preset
|
||||
* @param {Builder} command executor
|
||||
*
|
||||
*/
|
||||
constructor(preset, command) {
|
||||
this.preset = preset;
|
||||
this.command = command;
|
||||
}
|
||||
}
|
||||
|
||||
export class Commands {
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* @param {[]object} commands Command array
|
||||
* @param {Array<object>} commands Command array
|
||||
*
|
||||
*/
|
||||
constructor(commands) {
|
||||
this.commands = [];
|
||||
|
||||
for (let i in commands) {
|
||||
for (let i = 0; i < commands.length; i++) {
|
||||
this.commands.push(new Builder(commands[i]));
|
||||
}
|
||||
}
|
||||
@@ -727,7 +823,7 @@ export class Commands {
|
||||
/**
|
||||
* Return all commands
|
||||
*
|
||||
* @returns {[]Builder} A group of command
|
||||
* @returns {Array<Builder>} A group of command
|
||||
*
|
||||
*/
|
||||
all() {
|
||||
@@ -745,4 +841,28 @@ export class Commands {
|
||||
select(id) {
|
||||
return this.commands[id];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns presets with merged command
|
||||
*
|
||||
* @param {presets.Presets} ps
|
||||
*
|
||||
* @returns {Array<Preset>}
|
||||
*
|
||||
*/
|
||||
mergePresets(ps) {
|
||||
let pp = [];
|
||||
|
||||
for (let i = 0; i < this.commands.length; i++) {
|
||||
const fetched = ps.fetch(this.commands[i].name());
|
||||
|
||||
for (let j = 0; j < fetched.length; j++) {
|
||||
pp.push(
|
||||
new Preset(this.commands[i].represet(fetched[j]), this.commands[i])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return pp;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,16 @@
|
||||
|
||||
import * as command from "./commands.js";
|
||||
|
||||
function metaContains(data, metaName, valContains) {
|
||||
switch (typeof data[metaName]) {
|
||||
case "string":
|
||||
return data[metaName].indexOf(valContains) >= 0;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class History {
|
||||
/**
|
||||
* constructor
|
||||
@@ -206,4 +216,39 @@ export class History {
|
||||
|
||||
this.store();
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for partly matched results
|
||||
*
|
||||
* @param {string} type of the history record
|
||||
* @param {string} metaName name of the meta data
|
||||
* @param {string} keyword keyword to search
|
||||
* @param {number} max max results
|
||||
*/
|
||||
search(type, metaName, keyword, max) {
|
||||
let maxResults = max > this.records.length ? this.records.length : max;
|
||||
let s = [];
|
||||
|
||||
if (maxResults < 0) {
|
||||
maxResults = this.records.length;
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.records.length && s.length < maxResults; i++) {
|
||||
if (this.records[i].type !== type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!this.records[i].data) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!metaContains(this.records[i].data, metaName, keyword)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
s.push(this.records[i]);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
292
ui/commands/presets.js
Normal file
292
ui/commands/presets.js
Normal file
@@ -0,0 +1,292 @@
|
||||
// Sshwifty - A Web SSH client
|
||||
//
|
||||
// Copyright (C) 2019-2020 Rui NI <nirui@gmx.com>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import Exception from "./exception.js";
|
||||
|
||||
/**
|
||||
* Default preset item, contains data of a default preset
|
||||
*
|
||||
*/
|
||||
const presetItem = {
|
||||
title: "",
|
||||
type: "",
|
||||
host: "",
|
||||
meta: {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify Preset Item Meta
|
||||
*
|
||||
* @param {object} preset
|
||||
*
|
||||
*/
|
||||
function verifyPresetItemMeta(preset) {
|
||||
for (let i in preset.meta) {
|
||||
if (typeof preset.meta[i] === "string") {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
'The data type of meta field "' +
|
||||
i +
|
||||
'" was "' +
|
||||
typeof preset.meta[i] +
|
||||
'" instead of expected "string"'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and verify the given preset, return a valid preset
|
||||
*
|
||||
* @param {object} item
|
||||
*
|
||||
* @throws {Exception} when invalid data is given
|
||||
*
|
||||
* @return {object}
|
||||
*
|
||||
*/
|
||||
function parsePresetItem(item) {
|
||||
let preset = {};
|
||||
|
||||
for (let i in presetItem) {
|
||||
preset[i] = presetItem[i];
|
||||
}
|
||||
|
||||
for (let i in presetItem) {
|
||||
if (typeof presetItem[i] === typeof item[i]) {
|
||||
preset[i] = item[i];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
'Expecting the data type of "' +
|
||||
i +
|
||||
'" is "' +
|
||||
typeof presetItem[i] +
|
||||
'", given "' +
|
||||
typeof item[i] +
|
||||
'" instead'
|
||||
);
|
||||
}
|
||||
|
||||
verifyPresetItemMeta(preset.meta);
|
||||
|
||||
return preset;
|
||||
}
|
||||
|
||||
/**
|
||||
* Preset data
|
||||
*
|
||||
*/
|
||||
export class Preset {
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* @param {object} preset preset data
|
||||
*
|
||||
*/
|
||||
constructor(preset) {
|
||||
this.preset = parsePresetItem(preset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the title of the preset
|
||||
*
|
||||
* @returns {string}
|
||||
*
|
||||
*/
|
||||
title() {
|
||||
return this.preset.title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the type of the preset
|
||||
*
|
||||
* @returns {string}
|
||||
*
|
||||
*/
|
||||
type() {
|
||||
return this.preset.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the host of the preset
|
||||
*
|
||||
* @returns {string}
|
||||
*
|
||||
*/
|
||||
host() {
|
||||
return this.preset.host;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the given meta of current preset
|
||||
*
|
||||
* @param {string} name name of the meta data
|
||||
*
|
||||
* @throws {Exception} when invalid data is given
|
||||
*
|
||||
* @returns {string}
|
||||
*
|
||||
*/
|
||||
meta(name) {
|
||||
if (typeof this.preset.meta[name] !== "string") {
|
||||
throw new Exception('Meta "' + name + '" was undefined');
|
||||
}
|
||||
|
||||
return this.preset.meta[name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Insert new meta item
|
||||
*
|
||||
* @param {string} name name of the meta data
|
||||
* @param {string} data data of the meta data
|
||||
*
|
||||
* @throws {Exception} when invalid data is given
|
||||
*
|
||||
*/
|
||||
insertMeta(name, data) {
|
||||
if (typeof this.preset.meta[name] !== "undefined") {
|
||||
throw new Exception('Meta "' + name + '" has already been defined');
|
||||
}
|
||||
|
||||
this.preset.meta[name] = data;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an empty preset
|
||||
*
|
||||
* @returns {Preset}
|
||||
*
|
||||
*/
|
||||
export function emptyPreset() {
|
||||
return new Preset({
|
||||
title: "Default",
|
||||
type: "Default",
|
||||
host: "",
|
||||
meta: {}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Command Preset manager
|
||||
*
|
||||
*/
|
||||
export class Presets {
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* @param {Array<object>} presets Array of preset data
|
||||
*
|
||||
*/
|
||||
constructor(presets) {
|
||||
this.presets = [];
|
||||
|
||||
for (let i = 0; i < presets.length; i++) {
|
||||
this.presets.push(new Preset(presets[i]));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all presets of a type
|
||||
*
|
||||
* @param {string} type type of the presets data
|
||||
*
|
||||
* @returns {Array<Preset>}
|
||||
*
|
||||
*/
|
||||
fetch(type) {
|
||||
let presets = [];
|
||||
|
||||
for (let i = 0; i < this.presets.length; i++) {
|
||||
if (this.presets[i].type() !== type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
presets.push(this.presets[i]);
|
||||
}
|
||||
|
||||
return presets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return presets with matched type and meta data
|
||||
*
|
||||
* @param {string} type type of the presets data
|
||||
* @param {string} metaName name of the meta data
|
||||
* @param {string} metaVal value of the meta data
|
||||
*
|
||||
* @returns {Array<Preset>}
|
||||
*
|
||||
*/
|
||||
meta(type, metaName, metaVal) {
|
||||
let presets = [];
|
||||
|
||||
for (let i = 0; i < this.presets.length; i++) {
|
||||
if (this.presets[i].type() !== type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
if (this.presets[i].meta(metaName) !== metaVal) {
|
||||
continue;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!(e instanceof Exception)) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
presets.push(this.presets[i]);
|
||||
}
|
||||
|
||||
return presets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return presets with matched type and host
|
||||
*
|
||||
* @param {string} type type of the presets
|
||||
* @param {string} host host of the presets
|
||||
*
|
||||
* @returns {Array<Preset>}
|
||||
*
|
||||
*/
|
||||
hosts(type, host) {
|
||||
let presets = [];
|
||||
|
||||
for (let i = 0; i < this.presets.length; i++) {
|
||||
if (this.presets[i].type() !== type) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (this.presets[i].host() !== host) {
|
||||
continue;
|
||||
}
|
||||
|
||||
presets.push(this.presets[i]);
|
||||
}
|
||||
|
||||
return presets;
|
||||
}
|
||||
}
|
||||
@@ -15,17 +15,18 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as header from "../stream/header.js";
|
||||
import * as reader from "../stream/reader.js";
|
||||
import * as stream from "../stream/stream.js";
|
||||
import * as address from "./address.js";
|
||||
import * as command from "./commands.js";
|
||||
import * as common from "./common.js";
|
||||
import * as event from "./events.js";
|
||||
import * as reader from "../stream/reader.js";
|
||||
import * as stream from "../stream/stream.js";
|
||||
import * as controls from "./controls.js";
|
||||
import * as header from "../stream/header.js";
|
||||
import * as history from "./history.js";
|
||||
import * as strings from "./string.js";
|
||||
import * as event from "./events.js";
|
||||
import Exception from "./exception.js";
|
||||
import * as history from "./history.js";
|
||||
import * as presets from "./presets.js";
|
||||
import * as strings from "./string.js";
|
||||
|
||||
const AUTHMETHOD_NONE = 0x00;
|
||||
const AUTHMETHOD_PASSPHRASE = 0x01;
|
||||
@@ -57,6 +58,8 @@ const FingerprintPromptVerifyPassed = 0x00;
|
||||
const FingerprintPromptVerifyNoRecord = 0x01;
|
||||
const FingerprintPromptVerifyMismatch = 0x02;
|
||||
|
||||
const HostMaxSearchResults = 3;
|
||||
|
||||
class SSH {
|
||||
/**
|
||||
* constructor
|
||||
@@ -245,6 +248,10 @@ const initialFieldDef = {
|
||||
type: "text",
|
||||
value: "",
|
||||
example: "guest",
|
||||
readonly: false,
|
||||
suggestions(input) {
|
||||
return [];
|
||||
},
|
||||
verify(d) {
|
||||
if (d.length <= 0) {
|
||||
throw new Error("Username must be specified");
|
||||
@@ -265,6 +272,10 @@ const initialFieldDef = {
|
||||
type: "text",
|
||||
value: "",
|
||||
example: "ssh.vaguly.com:22",
|
||||
readonly: false,
|
||||
suggestions(input) {
|
||||
return [];
|
||||
},
|
||||
verify(d) {
|
||||
if (d.length <= 0) {
|
||||
throw new Error("Hostname must be specified");
|
||||
@@ -295,6 +306,10 @@ const initialFieldDef = {
|
||||
type: "select",
|
||||
value: "utf-8",
|
||||
example: common.charsetPresets.join(","),
|
||||
readonly: false,
|
||||
suggestions(input) {
|
||||
return [];
|
||||
},
|
||||
verify(d) {
|
||||
for (let i in common.charsetPresets) {
|
||||
if (common.charsetPresets[i] !== d) {
|
||||
@@ -315,6 +330,10 @@ const initialFieldDef = {
|
||||
"SSH session is handled by the backend. Traffic will be decrypted " +
|
||||
"on the backend server and then transmit back to your client.",
|
||||
example: "",
|
||||
readonly: false,
|
||||
suggestions(input) {
|
||||
return [];
|
||||
},
|
||||
verify(d) {
|
||||
return "";
|
||||
}
|
||||
@@ -325,6 +344,10 @@ const initialFieldDef = {
|
||||
type: "password",
|
||||
value: "",
|
||||
example: "----------",
|
||||
readonly: false,
|
||||
suggestions(input) {
|
||||
return [];
|
||||
},
|
||||
verify(d) {
|
||||
if (d.length <= 0) {
|
||||
throw new Error("Password must be specified");
|
||||
@@ -356,6 +379,10 @@ const initialFieldDef = {
|
||||
type: "textfile",
|
||||
value: "",
|
||||
example: "",
|
||||
readonly: false,
|
||||
suggestions(input) {
|
||||
return [];
|
||||
},
|
||||
verify(d) {
|
||||
if (d.length <= 0) {
|
||||
throw new Error("Private Key must be specified");
|
||||
@@ -410,6 +437,10 @@ const initialFieldDef = {
|
||||
type: "radio",
|
||||
value: "",
|
||||
example: "Password,Private Key,None",
|
||||
readonly: false,
|
||||
suggestions(input) {
|
||||
return [];
|
||||
},
|
||||
verify(d) {
|
||||
switch (d) {
|
||||
case "Password":
|
||||
@@ -431,6 +462,10 @@ const initialFieldDef = {
|
||||
type: "textdata",
|
||||
value: "",
|
||||
example: "",
|
||||
readonly: false,
|
||||
suggestions(input) {
|
||||
return [];
|
||||
},
|
||||
verify(d) {
|
||||
return "";
|
||||
}
|
||||
@@ -468,7 +503,7 @@ class Wizard {
|
||||
* constructor
|
||||
*
|
||||
* @param {command.Info} info
|
||||
* @param {object} config
|
||||
* @param {presets.Preset} preset
|
||||
* @param {object} session
|
||||
* @param {streams.Streams} streams
|
||||
* @param {subscribe.Subscribe} subs
|
||||
@@ -476,11 +511,11 @@ class Wizard {
|
||||
* @param {history.History} history
|
||||
*
|
||||
*/
|
||||
constructor(info, config, session, streams, subs, controls, history) {
|
||||
constructor(info, preset, session, streams, subs, controls, history) {
|
||||
this.info = info;
|
||||
this.preset = preset;
|
||||
this.hasStarted = false;
|
||||
this.streams = streams;
|
||||
this.config = config;
|
||||
this.session = session
|
||||
? session
|
||||
: {
|
||||
@@ -489,7 +524,9 @@ class Wizard {
|
||||
this.step = subs;
|
||||
this.controls = controls.get("SSH");
|
||||
this.history = history;
|
||||
}
|
||||
|
||||
run() {
|
||||
this.step.resolve(this.stepInitialPrompt());
|
||||
}
|
||||
|
||||
@@ -680,26 +717,6 @@ class Wizard {
|
||||
stepInitialPrompt() {
|
||||
let self = this;
|
||||
|
||||
if (this.config) {
|
||||
self.hasStarted = true;
|
||||
|
||||
self.streams.request(COMMAND_ID, sd => {
|
||||
return self.buildCommand(
|
||||
sd,
|
||||
{
|
||||
user: this.config.user,
|
||||
authentication: this.config.authentication,
|
||||
host: this.config.host,
|
||||
charset: this.config.charset ? this.config.charset : "utf-8",
|
||||
fingerprint: this.config.fingerprint
|
||||
},
|
||||
this.session
|
||||
);
|
||||
});
|
||||
|
||||
return self.stepWaitForAcceptWait();
|
||||
}
|
||||
|
||||
return command.prompt(
|
||||
"SSH",
|
||||
"Secure Shell Host",
|
||||
@@ -717,26 +734,57 @@ class Wizard {
|
||||
charset: r.encoding,
|
||||
fingerprint: ""
|
||||
},
|
||||
this.session
|
||||
self.session
|
||||
);
|
||||
});
|
||||
|
||||
self.step.resolve(self.stepWaitForAcceptWait());
|
||||
},
|
||||
() => {},
|
||||
command.fields(initialFieldDef, [
|
||||
{ name: "User" },
|
||||
{ name: "Host" },
|
||||
{ name: "Authentication" },
|
||||
{ name: "Encoding" },
|
||||
{ name: "Notice" }
|
||||
])
|
||||
command.fieldsWithPreset(
|
||||
initialFieldDef,
|
||||
[
|
||||
{ name: "User" },
|
||||
{
|
||||
name: "Host",
|
||||
suggestions(input) {
|
||||
const hosts = self.history.search(
|
||||
"SSH",
|
||||
"host",
|
||||
input,
|
||||
HostMaxSearchResults
|
||||
);
|
||||
|
||||
let sugg = [];
|
||||
|
||||
for (let i = 0; i < hosts.length; i++) {
|
||||
sugg.push({
|
||||
title: hosts[i].title,
|
||||
value: hosts[i].data.host,
|
||||
meta: {
|
||||
User: hosts[i].data.user,
|
||||
Authentication: hosts[i].data.authentication,
|
||||
Encoding: hosts[i].data.charset
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return sugg;
|
||||
}
|
||||
},
|
||||
{ name: "Authentication" },
|
||||
{ name: "Encoding" },
|
||||
{ name: "Notice" }
|
||||
],
|
||||
self.preset
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
async stepFingerprintPrompt(rd, sd, verify, newFingerprint) {
|
||||
let self = this,
|
||||
fingerprintData = new TextDecoder("utf-8").decode(
|
||||
const self = this;
|
||||
|
||||
let fingerprintData = new TextDecoder("utf-8").decode(
|
||||
await reader.readCompletely(rd)
|
||||
),
|
||||
fingerprintChanged = false;
|
||||
@@ -745,7 +793,7 @@ class Wizard {
|
||||
case FingerprintPromptVerifyPassed:
|
||||
sd.send(CLIENT_CONNECT_RESPOND_FINGERPRINT, new Uint8Array([0]));
|
||||
|
||||
return this.stepContinueWaitForEstablishWait();
|
||||
return self.stepContinueWaitForEstablishWait();
|
||||
|
||||
case FingerprintPromptVerifyMismatch:
|
||||
fingerprintChanged = true;
|
||||
@@ -783,8 +831,9 @@ class Wizard {
|
||||
}
|
||||
|
||||
async stepCredentialPrompt(rd, sd, config, newCredential) {
|
||||
let self = this,
|
||||
fields = [];
|
||||
const self = this;
|
||||
|
||||
let fields = [];
|
||||
|
||||
if (config.credential.length > 0) {
|
||||
sd.send(
|
||||
@@ -792,7 +841,7 @@ class Wizard {
|
||||
new TextEncoder().encode(config.credential)
|
||||
);
|
||||
|
||||
return this.stepContinueWaitForEstablishWait();
|
||||
return self.stepContinueWaitForEstablishWait();
|
||||
}
|
||||
|
||||
switch (config.auth) {
|
||||
@@ -836,11 +885,61 @@ class Wizard {
|
||||
)
|
||||
);
|
||||
},
|
||||
command.fields(initialFieldDef, fields)
|
||||
command.fieldsWithPreset(initialFieldDef, fields, self.preset)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Executer extends Wizard {
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* @param {command.Info} info
|
||||
* @param {config} config
|
||||
* @param {object} session
|
||||
* @param {streams.Streams} streams
|
||||
* @param {subscribe.Subscribe} subs
|
||||
* @param {controls.Controls} controls
|
||||
* @param {history.History} history
|
||||
*
|
||||
*/
|
||||
constructor(info, config, session, streams, subs, controls, history) {
|
||||
super(
|
||||
info,
|
||||
presets.emptyPreset(),
|
||||
session,
|
||||
streams,
|
||||
subs,
|
||||
controls,
|
||||
history
|
||||
);
|
||||
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
stepInitialPrompt() {
|
||||
const self = this;
|
||||
|
||||
self.hasStarted = true;
|
||||
|
||||
self.streams.request(COMMAND_ID, sd => {
|
||||
return self.buildCommand(
|
||||
sd,
|
||||
{
|
||||
user: self.config.user,
|
||||
authentication: self.config.authentication,
|
||||
host: self.config.host,
|
||||
charset: self.config.charset ? self.config.charset : "utf-8",
|
||||
fingerprint: self.config.fingerprint
|
||||
},
|
||||
self.session
|
||||
);
|
||||
});
|
||||
|
||||
return self.stepWaitForAcceptWait();
|
||||
}
|
||||
}
|
||||
|
||||
export class Command {
|
||||
constructor() {}
|
||||
|
||||
@@ -860,8 +959,20 @@ export class Command {
|
||||
return "#3c8";
|
||||
}
|
||||
|
||||
builder(info, config, session, streams, subs, controls, history) {
|
||||
return new Wizard(info, config, session, streams, subs, controls, history);
|
||||
wizard(info, preset, session, streams, subs, controls, history) {
|
||||
return new Wizard(info, preset, session, streams, subs, controls, history);
|
||||
}
|
||||
|
||||
execute(info, config, session, streams, subs, controls, history) {
|
||||
return new Executer(
|
||||
info,
|
||||
config,
|
||||
session,
|
||||
streams,
|
||||
subs,
|
||||
controls,
|
||||
history
|
||||
);
|
||||
}
|
||||
|
||||
launch(info, launcher, streams, subs, controls, history) {
|
||||
@@ -893,7 +1004,7 @@ export class Command {
|
||||
);
|
||||
}
|
||||
|
||||
return this.builder(
|
||||
return this.execute(
|
||||
info,
|
||||
{
|
||||
user: user,
|
||||
@@ -920,4 +1031,14 @@ export class Command {
|
||||
(config.charset ? config.charset : "utf-8")
|
||||
);
|
||||
}
|
||||
|
||||
represet(preset) {
|
||||
const host = preset.host();
|
||||
|
||||
if (host.length > 0) {
|
||||
preset.insertMeta("Host", host);
|
||||
}
|
||||
|
||||
return preset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,17 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
import * as header from "../stream/header.js";
|
||||
import * as reader from "../stream/reader.js";
|
||||
import * as stream from "../stream/stream.js";
|
||||
import * as address from "./address.js";
|
||||
import * as command from "./commands.js";
|
||||
import * as common from "./common.js";
|
||||
import * as event from "./events.js";
|
||||
import * as reader from "../stream/reader.js";
|
||||
import * as stream from "../stream/stream.js";
|
||||
import * as controls from "./controls.js";
|
||||
import * as history from "./history.js";
|
||||
import * as header from "../stream/header.js";
|
||||
import * as event from "./events.js";
|
||||
import Exception from "./exception.js";
|
||||
import * as history from "./history.js";
|
||||
import * as presets from "./presets.js";
|
||||
|
||||
const COMMAND_ID = 0x00;
|
||||
|
||||
@@ -36,6 +37,8 @@ const SERVER_DIAL_CONNECTED = 0x02;
|
||||
|
||||
const DEFAULT_PORT = 23;
|
||||
|
||||
const HostMaxSearchResults = 3;
|
||||
|
||||
class Telnet {
|
||||
/**
|
||||
* constructor
|
||||
@@ -185,6 +188,10 @@ const initialFieldDef = {
|
||||
type: "text",
|
||||
value: "",
|
||||
example: "telnet.vaguly.com:23",
|
||||
readonly: false,
|
||||
suggestions(input) {
|
||||
return [];
|
||||
},
|
||||
verify(d) {
|
||||
if (d.length <= 0) {
|
||||
throw new Error("Hostname must be specified");
|
||||
@@ -215,6 +222,10 @@ const initialFieldDef = {
|
||||
type: "select",
|
||||
value: "utf-8",
|
||||
example: common.charsetPresets.join(","),
|
||||
readonly: false,
|
||||
suggestions(input) {
|
||||
return [];
|
||||
},
|
||||
verify(d) {
|
||||
for (let i in common.charsetPresets) {
|
||||
if (common.charsetPresets[i] !== d) {
|
||||
@@ -234,7 +245,7 @@ class Wizard {
|
||||
* constructor
|
||||
*
|
||||
* @param {command.Info} info
|
||||
* @param {object} config
|
||||
* @param {presets.Preset} preset
|
||||
* @param {object} session
|
||||
* @param {streams.Streams} streams
|
||||
* @param {subscribe.Subscribe} subs
|
||||
@@ -242,16 +253,18 @@ class Wizard {
|
||||
* @param {history.History} history
|
||||
*
|
||||
*/
|
||||
constructor(info, config, session, streams, subs, controls, history) {
|
||||
constructor(info, preset, session, streams, subs, controls, history) {
|
||||
this.info = info;
|
||||
this.preset = preset;
|
||||
this.hasStarted = false;
|
||||
this.streams = streams;
|
||||
this.config = config;
|
||||
this.session = session;
|
||||
this.step = subs;
|
||||
this.controls = controls.get("Telnet");
|
||||
this.history = history;
|
||||
}
|
||||
|
||||
run() {
|
||||
this.step.resolve(this.stepInitialPrompt());
|
||||
}
|
||||
|
||||
@@ -378,24 +391,7 @@ class Wizard {
|
||||
}
|
||||
|
||||
stepInitialPrompt() {
|
||||
let self = this;
|
||||
|
||||
if (this.config) {
|
||||
self.hasStarted = true;
|
||||
|
||||
self.streams.request(COMMAND_ID, sd => {
|
||||
return self.buildCommand(
|
||||
sd,
|
||||
{
|
||||
host: this.config.host,
|
||||
charset: this.config.charset ? this.config.charset : "utf-8"
|
||||
},
|
||||
this.session
|
||||
);
|
||||
});
|
||||
|
||||
return self.stepWaitForAcceptWait();
|
||||
}
|
||||
const self = this;
|
||||
|
||||
return command.prompt(
|
||||
"Telnet",
|
||||
@@ -411,18 +407,96 @@ class Wizard {
|
||||
host: r.host,
|
||||
charset: r.encoding
|
||||
},
|
||||
this.session
|
||||
self.session
|
||||
);
|
||||
});
|
||||
|
||||
self.step.resolve(self.stepWaitForAcceptWait());
|
||||
},
|
||||
() => {},
|
||||
command.fields(initialFieldDef, [{ name: "Host" }, { name: "Encoding" }])
|
||||
command.fieldsWithPreset(
|
||||
initialFieldDef,
|
||||
[
|
||||
{
|
||||
name: "Host",
|
||||
suggestions(input) {
|
||||
const hosts = self.history.search(
|
||||
"Telnet",
|
||||
"host",
|
||||
input,
|
||||
HostMaxSearchResults
|
||||
);
|
||||
|
||||
let sugg = [];
|
||||
|
||||
for (let i = 0; i < hosts.length; i++) {
|
||||
sugg.push({
|
||||
title: hosts[i].title,
|
||||
value: hosts[i].data.host,
|
||||
meta: {
|
||||
Encoding: hosts[i].data.charset
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return sugg;
|
||||
}
|
||||
},
|
||||
{ name: "Encoding" }
|
||||
],
|
||||
self.preset
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Executor extends Wizard {
|
||||
/**
|
||||
* constructor
|
||||
*
|
||||
* @param {command.Info} info
|
||||
* @param {object} config
|
||||
* @param {object} session
|
||||
* @param {streams.Streams} streams
|
||||
* @param {subscribe.Subscribe} subs
|
||||
* @param {controls.Controls} controls
|
||||
* @param {history.History} history
|
||||
*
|
||||
*/
|
||||
constructor(info, config, session, streams, subs, controls, history) {
|
||||
super(
|
||||
info,
|
||||
presets.emptyPreset(),
|
||||
session,
|
||||
streams,
|
||||
subs,
|
||||
controls,
|
||||
history
|
||||
);
|
||||
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
stepInitialPrompt() {
|
||||
const self = this;
|
||||
|
||||
self.hasStarted = true;
|
||||
|
||||
self.streams.request(COMMAND_ID, sd => {
|
||||
return self.buildCommand(
|
||||
sd,
|
||||
{
|
||||
host: self.config.host,
|
||||
charset: self.config.charset ? self.config.charset : "utf-8"
|
||||
},
|
||||
self.session
|
||||
);
|
||||
});
|
||||
|
||||
return self.stepWaitForAcceptWait();
|
||||
}
|
||||
}
|
||||
|
||||
export class Command {
|
||||
constructor() {}
|
||||
|
||||
@@ -442,8 +516,20 @@ export class Command {
|
||||
return "#6ac";
|
||||
}
|
||||
|
||||
builder(info, config, session, streams, subs, controls, history) {
|
||||
return new Wizard(info, config, session, streams, subs, controls, history);
|
||||
wizard(info, preset, session, streams, subs, controls, history) {
|
||||
return new Wizard(info, preset, session, streams, subs, controls, history);
|
||||
}
|
||||
|
||||
execute(info, config, session, streams, subs, controls, history) {
|
||||
return new Executor(
|
||||
info,
|
||||
config,
|
||||
session,
|
||||
streams,
|
||||
subs,
|
||||
controls,
|
||||
history
|
||||
);
|
||||
}
|
||||
|
||||
launch(info, launcher, streams, subs, controls, history) {
|
||||
@@ -476,7 +562,7 @@ export class Command {
|
||||
}
|
||||
}
|
||||
|
||||
return this.builder(
|
||||
return this.execute(
|
||||
info,
|
||||
{
|
||||
host: d[0],
|
||||
@@ -493,4 +579,14 @@ export class Command {
|
||||
launcher(config) {
|
||||
return config.host + "|" + (config.charset ? config.charset : "utf-8");
|
||||
}
|
||||
|
||||
represet(preset) {
|
||||
const host = preset.host();
|
||||
|
||||
if (host.length > 0) {
|
||||
preset.insertMeta("Host", host);
|
||||
}
|
||||
|
||||
return preset;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user