refactor terminal

This commit is contained in:
Christoph Hartmann 2016-09-19 09:32:23 +01:00
parent b8cc700495
commit bc2ac4a80a
21 changed files with 601 additions and 420 deletions

View file

@ -1,13 +1,9 @@
.main {
overflow: auto;
}
.terminal-nav {
.tutorial-nav {
display: inline-block;
position: fixed;
top: 0;
top: 10;
right: 0;
width: 120px;
width: 150px;
height: 60px;
color: #888;
text-align: center;
@ -25,23 +21,38 @@
img {
padding-top: 5px;
width: 160px;
height: 40px;
width: 100px;
padding-bottom: 5px;
position: fixed;
bottom: 10;
right: 30px;
z-index: 100;
}
.guide {
-webkit-font-smoothing: antialiased;
.tutorial {
position: fixed;
width: 100%;
top: 0;
left: 0;
z-index: 5;
}
.tutorial-wrapper {
max-width: 1000px;
min-width: 400px;
margin: 10px auto;
padding: 10px;
}
.tutorial-guide {
padding: 10px;
color: #DDDDDD;
background-color: #444;
font-family: 'Ubuntu Mono', 'Monaco', sans-serif;
letter-spacing: 1.2px;
font-size: 15px;
max-width: 1000px;
margin: auto;
padding: 1rem;
background-color: #444;
text-align: left;
line-height: 1.4;
border-radius: 5px;
}
.title {
@ -57,15 +68,13 @@ I'm unsure why, but attempting to set the style of code in this setting is just
not working....but this works! */
:host /deep/ code {
font-weight: bold;
font-size: 16px;
font-size: 15px;
}
.cli {
-webkit-font-smoothing: antialiased;
font-family: monospace;
font-size: 1.2rem;
white-space: pre-wrap;
word-wrap: break-word;
max-width: 1200px;
margin: auto;
position: fixed;
top: 250px;
right: 0;
left: 0;
bottom: 0;
}

View file

@ -1,17 +1,18 @@
<div class="terminal-nav">
<span (click)="updateInstructions('prev')"> < </span>
<span (click)="updateInstructions('next')"> > </span>
<span> x </span>
<div class="tutorial">
<div class="tutorial-nav">
<i (click)="updateInstructions('prev')" class="icon-angle-circled-left"></i>
<i (click)="updateInstructions('next')" class="icon-angle-circled-right"></i>
<i class="icon-cancel-circled"></i>
</div>
<div class="tutorial-wrapper">
<div class="tutorial-guide">
<p class="title">{{title}}</p>
<p [innerHTML]="formatInstructions()"></p>
</div>
</div>
</div>
<img src="inspec-logo.png" alt="Inspec Logo">
<div class="main">
<div class="guide">
<p class="title">{{title}}</p>
<p [innerHTML]="formatInstructions()"></p>
</div>
<xterm-terminal class="cli" (stdin)="evalCommand(command=$event)" [stdout]="stdout" [prompt]="prompt"></xterm-terminal>
<div class="cli">
<xterm-terminal (command)="evalCommand(command=$event)" [response]="response" [shell]="shell"></xterm-terminal>
</div>
</div>
<img src="inspec-logo-white.png" alt="InSpec Logo">

View file

@ -1,10 +1,12 @@
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, EventEmitter } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { HTTP_PROVIDERS } from '@angular/http';
import { XtermTerminalComponent } from './xterm-terminal/xterm-terminal.component';
declare var require: any;
var shellwords = require("shellwords");
const SH_PROMPT = '$ ';
const INSPEC_PROMPT = 'inspec> ';
@Component({
selector: 'my-app',
@ -15,170 +17,110 @@ var shellwords = require("shellwords");
})
export class AppComponent implements OnInit {
// template values
instructions: any;
// all available commands parsed from json files
commands: any = [];
// we support two shell modes: 'sh' and 'inspec'
shellMode: string = 'sh'
// all tutorial instructions
instructionsArray: any = [];
// title of the current tutorial step
title: string;
// instruction of the current step
instruction: any;
// keeps track of step number count
counter: number = 0;
// taken as input by xterm terminal componen
response: string;
shell: string;
stdout: EventEmitter<string> = new EventEmitter<string>();
// blue regular shell prompt
prompt: string = SH_PROMPT;
// colors for responses
red: string = "";
white: string = "";
black: string = "";
matchFound: boolean; // helps to handle no match found response
counter: number = 0; // keeps track of step number count
userCommand: string; // used to display better error msg when no match is found
// arrays of data parsed from json files
commands: any = [];
instructionsArray: any = [];
// inspec shell depth
shell_depth = 0;
shell_command = ''
constructor(private http: Http) { }
ngOnInit() {
// load json files
this.getInstructions();
this.getCommands();
// load content
this.loadInstructions();
this.loadCommands();
}
ngAfterViewChecked() {
window.scrollTo( 0, document.body.scrollHeight );
}
// called when command entered is 'next' or 'prev'
// modifies value of counter and calls displayInstructions
updateInstructions(step) {
let totalSteps = this.instructionsArray.length - 1;
let msg = Math.random();
if (step === 'next') {
if (this.counter <= totalSteps) {
this.counter += 1;
}
this.response = this.black + 'next' + msg;
} else if (step === 'prev') {
if (this.counter > 0) {
this.counter -= 1;
}
this.response = this.black + 'prev' + msg;
} else if (step === 'last') {
this.counter = totalSteps;
this.response = this.black + 'last' + msg;
}
this.displayInstructions();
}
// determines all commands that are not part of the tutorial
extraCmds() {
let cmds = this.commands
let extra = Object.keys(cmds).filter(function(key){
return cmds[key]['extra'] == true
let extra = this.commands.filter(function(item){
return item['extra'] == true
});
return extra
}
// display instructions based on value of counter and
// format text to remove triple backticks. if the user has reached
// then end of the demo, display a message containing extra commands that have been
// enabled in the demo
displayInstructions() {
if (this.counter === this.instructionsArray.length) {
this.title = "the end; that's all folks!";
this.instructions = "here are some other commands you can try out: \r\n\r\n" + this.extraCmds();
} else {
if (this.instructionsArray[this.counter][1]) {
this.title = this.instructionsArray[this.counter][0];
this.instructions = this.instructionsArray[this.counter][1];
} else {
this.instructions = 'Sorry, something seems to have gone wrong. Please try refreshing your browser.';
}
}
return extra.map(function(item){return item.command})
}
formatInstructions() {
return this.instructions || '';
return this.instruction || '';
}
// called when a new value is emitted for command
// checks for a match, calls parseInspecShell if shell is inspec-shell
// and calls checkCommand if none of the first commands match
evalCommand(command) {
this.userCommand = command;
if (command.match(/^next\s*/)) {
this.updateInstructions('next');
// called when tutorial commands need to be displayed
updateInstructions(step = null) {
// if step is given, we calculate the new index
let totalSteps = this.instructionsArray.length - 1;
switch(step) {
case "next":
if (this.counter < totalSteps) {
this.counter += 1;
}
break;
case 'prev':
if (this.counter > 0) {
this.counter -= 1;
}
break;
case 'first':
this.counter = 1;
break
case 'last':
this.counter = totalSteps;
break
}
else if (command.match(/^prev\s*/)) {
this.updateInstructions('prev');
}
else if (command.match(/^last\s*/)) {
this.updateInstructions('last');
}
else if (command.match(/^inspec\s*shell\s*$/)) {
this.shell = 'inspec-shell';
this.response = this.white + 'Welcome to the interactive InSpec Shell\r\nTo find out how to use it, type: help\r\nTo exit, type: exit\r\n';
}
else if (this.shell === 'inspec-shell') {
this.parseInspecShell(command);
}
else {
this.checkCommand(command);
if (this.counter === this.instructionsArray.length - 1) {
this.title = "the end; that's all folks!";
this.instruction = "here are some other commands you can try out: \r\n\r\n" + this.extraCmds();
} else if (this.instructionsArray[this.counter][1]) {
this.title = this.instructionsArray[this.counter][0];
this.instruction = this.instructionsArray[this.counter][1];
} else {
this.instruction = 'Sorry, something seems to have gone wrong. Please try refreshing your browser.';
}
}
// if the shell is inspec-shell, we want to exit the shell env on 'exit'
// format the command for checkCommand by using shellwords.escape and
// adding 'echo' and 'inspec shell' to match command from commands.json
parseInspecShell(command) {
if (command.match(/^exit\s*/)) {
this.shell = '';
this.response = '';
}
else if (command.match(/^pwd\s*/)) {
this.response = this.white + "anonymous-web-user/inspec-shell";
}
else {
let escaped_cmd;
let formatted_cmd;
// TODO: make this better
// I don't really like what we're doing here when we have a
// describe and control block, but I had a lot of trouble getting this
// to work because of the new lines carriage returns in the command from commands.json
// so at the moment we're splitting the command on "do" and assuming a match if that first
// result group matches (everything before do) :/
if (command.match(/^describe.*|^control/)) {
let split_cmd = command.split('do');
escaped_cmd = shellwords.escape(split_cmd[0]);
formatted_cmd = 'echo.*' + escaped_cmd ;
} else {
escaped_cmd = shellwords.escape(command)
formatted_cmd = 'echo.*' + escaped_cmd + '.*inspec.*shell';
}
let regex_compatible = formatted_cmd.replace(/\W+/g, '.*');
this.checkCommand(regex_compatible);
}
}
// takes the command as input, replaces all white space with regex whitespace matcher
// and creates a new regex. check if the regex matches any of the keys in the commands
// if it matches, we set matchFound to true and call displayResult. if it doesn't match,
// takes the command as input. checks if the regex matches any of the keys in the commands
// if it matches, we set matchFound to true and call printOnStdout. if it doesn't match,
// we display a default error message
checkCommand(command) {
execCommand(command, shell) {
let response = ''
let dir = 'app/responses/';
let cmd = command.replace(/ /g,'\\s*')
let regexcmd = new RegExp(('^'+cmd+'$'), 'm')
this.matchFound = false;
var matchFound = false;
// iterate over commands and try to match the command with the input
let cmds = Object.keys(this.commands)
for (var i = 0; i < cmds.length; i++) {
let cmd = cmds[i];
if (cmd.match(regexcmd)) {
this.matchFound = true;
let key = this.commands[cmd]['key'];
this.http.get(dir + key).subscribe(data => {
this.displayResult(command, data);
for (var i = 0; i < this.commands.length; i++) {
let item = this.commands[i]
if (item.regex.exec(command) && item.shell == shell) {
matchFound = true;
this.http.get(dir + item.key).subscribe(data => {
this.printOnStdout(data['_body'])
},
err => console.error(err));
}
@ -186,46 +128,119 @@ export class AppComponent implements OnInit {
// if no match is found, we check if the command entered was inspec exec something
// and if it is respond appropriately ('could not fetch inspec profile in ''), otherwise
// respond with 'invalid command' and the command entered
if (this.matchFound === false) {
let msg = Math.random();
if (matchFound === false) {
console.log('no match found')
if (command.match(/^inspec exec\s*.*/)) {
let target = command.match(/^inspec exec\s*(.*)/)
this.response = this.red + "Could not fetch inspec profile in '" + target[1] + "' " + this.black + msg;
response = this.red + "Could not fetch inspec profile in '" + target[1] + "' " + this.black;
} else {
this.response = this.red + 'invalid command: ' + this.userCommand + this.black + msg;
response = this.red + 'command not found: ' + command;
}
this.printOnStdout(response + "\n\r")
}
}
// if the command if inspec shell, we also need to set the shell variable to
// inspec shell. print response value and random msg (to ensure recognition of value change by xterm
// terminal component)
displayResult(command, data) {
if (command.match(/^inspec\s*shell\s*$/)) {
this.shell = 'inspec-shell';
// handles a stdin command and prints on terminal stdout
evalCommand(command) {
let self = this
// tutorial commands
var m = /^\s*(next|prev|first|last)\s*$/.exec(command)
let multiline = /\s*(describe|control|end)\s*/.exec(command)
if (m) {
// update the instructions widget
// we are not calling this from the ng event loop, therefore we need to
// trigger angular to update the tutorial section
setTimeout(function(){
self.updateInstructions(m[1]);
}, 0);
// send an empty response to get the command prompt back
this.printOnStdout('')
}
let msg = Math.random();
this.response = this.white + data['_body'] + this.black + msg;
// switch to InSpec shell commands
// TODO, support targets for InSpec shell
else if (/^\s*inspec\s*shell\s*$/.exec(command)) {
// switch to InSpec shell
this.shellMode = 'inspec'
// switch prompt
this.prompt = INSPEC_PROMPT;
// output inspec hello text
let init = "Welcome to the interactive InSpec Shell\n\rTo find out how to use it, type: help\n\r"
this.printOnStdout(init);
}
// leave InSpec shell
else if (/^\s*exit\s*$/.exec(command) && this.shellMode == 'inspec') {
this.shellMode = 'sh'
// switch prompt
this.prompt = SH_PROMPT;
this.printOnStdout("\n\r");
}
else if (this.shellMode == 'inspec' && multiline) {
// count control + describe
let mstart = command.match(/describe|control/g)
if (mstart) {
this.shell_depth += mstart.length
}
// end
let mend = command.match(/end/g)
if (mend) {
this.shell_depth -= mend.length
}
this.shell_command += command + "\n"
if (mend && this.shell_depth == 0) {
command = this.shell_command
this.shell_depth = 0
this.shell_command = ''
this.execCommand(command, this.shellMode);
}
}
// default sh commands
else if ((this.shell_depth == 0) || this.shellMode == 'sh') {
this.execCommand(command, this.shellMode);
}
// store command in cache, must be an inspec shell command
else {
this.shell_command += command + "\n"
}
}
// submit stdout data to terminal
printOnStdout(data){
this.stdout.emit(data)
this.stdout.emit(this.prompt)
}
// load json file for instructions and save to instructionsArray
// call displayInstructions to load first set of instructions
getInstructions() {
// call updateInstructions to load first set of instructions
loadInstructions() {
let self = this
this.http.get('app/responses/instructions.json')
.subscribe(data => {
this.instructionsArray = JSON.parse(data['_body']);
this.displayInstructions();
self.instructionsArray = JSON.parse(data['_body']);
self.updateInstructions();
},
err => console.error(err)
);
}
// load json file for commands and push each object to commands
getCommands() {
loadCommands() {
let self = this;
this.http.get('app/responses/commands.json')
.subscribe(data => {
let result = JSON.parse(data['_body']);
this.commands = result
let commands = JSON.parse(data['_body']);
// generate regular expression for each entry
for (var i = 0; i < commands.length; i++) {
// preps a string for use in regular expressions
// @see http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
let cmd = commands[i].command.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
cmd = cmd.replace(/ /g,"\\s*")
cmd = cmd.replace(/\n/g,"\\s*")
let pattern = '^\\s*'+cmd+'\\s*$'
commands[i].regex = new RegExp(pattern, 'im')
}
this.commands = commands
},
err => console.error(err)
);

View file

@ -1 +1,206 @@
{"inspec":{"key":"inspec.txt","extra":false},"inspec help":{"key":"inspec_help.txt","extra":false},"inspec help version":{"key":"inspec_help_version.txt","extra":false},"inspec help detect":{"key":"inspec_help_detect.txt","extra":false},"inspec help exec":{"key":"inspec_help_exec.txt","extra":false},"inspec check examples/profile":{"key":"inspec_check_examples_profile.txt","extra":false},"inspec exec examples/profile":{"key":"inspec_exec_examples_profile.txt","extra":false},"inspec exec examples/profile -t ssh://bob@host.node -i bob.rsa":{"key":"inspec_exec_examples_profile_t_ssh_bob_host_node_i_bob_rsa.txt","extra":false},"inspec exec examples/profile -b ssh --host host.node --user bob -i bob.rsa":{"key":"inspec_exec_examples_profile_b_ssh_host_host_node_user_bob_i_bob_rsa.txt","extra":false},"inspec exec examples/profile -t winrm://alice:pass@windows.node":{"key":"inspec_exec_examples_profile_t_winrm_alice_pass_windows_node.txt","extra":false},"inspec exec examples/profile -t winrm://alice:pass@windows.node --ssl --self-signed":{"key":"inspec_exec_examples_profile_t_winrm_alice_pass_windows_node_ssl_self_signed.txt","extra":false},"inspec exec examples/profile -t docker://abcdef123":{"key":"inspec_exec_examples_profile_t_docker_abcdef123.txt","extra":false},"inspec detect":{"key":"inspec_detect.txt","extra":false},"inspec detect -t ssh://bob@host.node -i bob.rsa":{"key":"inspec_detect_t_ssh_bob_host_node_i_bob_rsa.txt","extra":false},"inspec shell -c 'os.params'":{"key":"inspec_shell_c_os_params_.txt","extra":false},"inspec shell -c 'sshd_config.Protocol'":{"key":"inspec_shell_c_sshd_config_Protocol_.txt","extra":false},"inspec shell -c 'sshd_config.Protocol' -t ssh://bob@host.node -i bob.rsa":{"key":"inspec_shell_c_sshd_config_Protocol_t_ssh_bob_host_node_i_bob_rsa.txt","extra":false},"inspec shell -c 'os.params' -t docker://abcdef123":{"key":"inspec_shell_c_os_params_t_docker_abcdef123.txt","extra":false},"ls":{"key":"ls.txt","extra":true},"cat README.md":{"key":"cat_README_md.txt","extra":true},"inspec help supermarket":{"key":"inspec_help_supermarket.txt","extra":true},"inspec help compliance":{"key":"inspec_help_compliance.txt","extra":true},"inspec version":{"key":"inspec_version.txt","extra":true},"inspec detect --format json":{"key":"inspec_detect_format_json.txt","extra":true},"inspec exec examples/profile --format json":{"key":"inspec_exec_examples_profile_format_json.txt","extra":true},"inspec exec examples/profile --format json | jq":{"key":"inspec_exec_examples_profile_format_json_jq.txt","extra":true},"inspec json examples/profile":{"key":"inspec_json_examples_profile.txt","extra":true},"inspec archive examples/profile":{"key":"inspec_archive_examples_profile.txt","extra":true},"inspec env":{"key":"inspec_env.txt","extra":true},"inspec exec examples/inheritance":{"key":"inspec_exec_examples_inheritance.txt","extra":true},"inspec exec test/unit/mock/profiles/failures":{"key":"inspec_exec_test_unit_mock_profiles_failures.txt","extra":true},"echo help | inspec shell":{"key":"echo_help_inspec_shell.txt","extra":false},"echo help\\ resources | inspec shell":{"key":"echo_help_resources_inspec_shell.txt","extra":false},"echo help\\ file | inspec shell":{"key":"echo_help_file_inspec_shell.txt","extra":false},"echo help\\ command | inspec shell":{"key":"echo_help_command_inspec_shell.txt","extra":false},"echo help\\ os | inspec shell":{"key":"echo_help_os_inspec_shell.txt","extra":false},"echo command\\(\\'uname\\ -a\\'\\).stdout | inspec shell":{"key":"echo_command_uname_a_stdout_inspec_shell.txt","extra":false},"echo file\\(\\'/proc/cpuinfo\\'\\).owner | inspec shell":{"key":"echo_file_proc_cpuinfo_owner_inspec_shell.txt","extra":false},"echo sshd_config.params | inspec shell":{"key":"echo_sshd_config_params_inspec_shell.txt","extra":false},"echo describe\\ file\\(\\'/root\\'\\)\\ do'\n'\\ \\ it\\ \\{\\ should\\ exist\\ \\}'\n'\\ \\ its\\(\\'mode\\'\\)\\ \\{\\ should\\ cmp\\ \\'0750\\'\\}'\n'end | inspec shell":{"key":"echo_describe_file_root_do_it_should_exist_its_mode_should_cmp_0750_end_inspec_shell.txt","extra":false},"echo control\\ \\\"id\\\"\\ do'\n'\\ \\ title\\ \\\"Check\\ permissions\\ on\\ /root\\!\\\"'\n'\\ \\ impact\\ 0.5'\n'\\ \\ describe\\ file\\(\\'/root\\'\\)\\ do'\n'\\ \\ \\ \\ its\\(\\'mode\\'\\)\\ \\{\\ should\\ cmp\\ \\'0750\\'\\}'\n'\\ \\ end'\n'end | inspec shell":{"key":"echo_control_id_do_title_Check_permissions_on_root_impact_0_5_describe_file_root_do_its_mode_should_cmp_0750_end_end_inspec_shell.txt","extra":false}}
{
"inspec": {
"key": "inspec.txt",
"extra": false,
"shell" : "sh"
},
"inspec help": {
"key": "inspec_help.txt",
"extra": false,
"shell" : "sh"
},
"inspec help version": {
"key": "inspec_help_version.txt",
"extra": false,
"shell" : "sh"
},
"inspec help detect": {
"key": "inspec_help_detect.txt",
"extra": false,
"shell" : "sh"
},
"inspec help exec": {
"key": "inspec_help_exec.txt",
"extra": false,
"shell" : "sh"
},
"inspec check examples/profile": {
"key": "inspec_check_examples_profile.txt",
"extra": false,
"shell" : "sh"
},
"inspec exec examples/profile": {
"key": "inspec_exec_examples_profile.txt",
"extra": false,
"shell" : "sh"
},
"inspec exec examples/profile -t ssh://bob@host.node -i bob.rsa": {
"key": "inspec_exec_examples_profile_t_ssh_bob_host_node_i_bob_rsa.txt",
"extra": false,
"shell" : "sh"
},
"inspec exec examples/profile -b ssh --host host.node --user bob -i bob.rsa": {
"key": "inspec_exec_examples_profile_b_ssh_host_host_node_user_bob_i_bob_rsa.txt",
"extra": false,
"shell" : "sh"
},
"inspec exec examples/profile -t winrm://alice:pass@windows.node": {
"key": "inspec_exec_examples_profile_t_winrm_alice_pass_windows_node.txt",
"extra": false,
"shell" : "sh"
},
"inspec exec examples/profile -t winrm://alice:pass@windows.node --ssl --self-signed": {
"key": "inspec_exec_examples_profile_t_winrm_alice_pass_windows_node_ssl_self_signed.txt",
"extra": false,
"shell" : "sh"
},
"inspec exec examples/profile -t docker://abcdef123": {
"key": "inspec_exec_examples_profile_t_docker_abcdef123.txt",
"extra": false,
"shell" : "sh"
},
"inspec detect": {
"key": "inspec_detect.txt",
"extra": false,
"shell" : "sh"
},
"inspec detect -t ssh://bob@host.node -i bob.rsa": {
"key": "inspec_detect_t_ssh_bob_host_node_i_bob_rsa.txt",
"extra": false,
"shell" : "sh"
},
"inspec shell -c 'os.params'": {
"key": "inspec_shell_c_os_params_.txt",
"extra": false,
"shell" : "sh"
},
"inspec shell -c 'sshd_config.Protocol'": {
"key": "inspec_shell_c_sshd_config_Protocol_.txt",
"extra": false,
"shell" : "sh"
},
"inspec shell -c 'sshd_config.Protocol' -t ssh://bob@host.node -i bob.rsa": {
"key": "inspec_shell_c_sshd_config_Protocol_t_ssh_bob_host_node_i_bob_rsa.txt",
"extra": false,
"shell" : "sh"
},
"inspec shell -c 'os.params' -t docker://abcdef123": {
"key": "inspec_shell_c_os_params_t_docker_abcdef123.txt",
"extra": false,
"shell" : "sh"
},
"ls": {
"key": "ls.txt",
"extra": true,
"shell" : "sh"
},
"cat README.md": {
"key": "cat_README_md.txt",
"extra": true,
"shell" : "sh"
},
"inspec help supermarket": {
"key": "inspec_help_supermarket.txt",
"extra": true,
"shell" : "sh"
},
"inspec help compliance": {
"key": "inspec_help_compliance.txt",
"extra": true,
"shell" : "sh"
},
"inspec version": {
"key": "inspec_version.txt",
"extra": true,
"shell" : "sh"
},
"inspec detect --format json": {
"key": "inspec_detect_format_json.txt",
"extra": true,
"shell" : "sh"
},
"inspec exec examples/profile --format json": {
"key": "inspec_exec_examples_profile_format_json.txt",
"extra": true,
"shell" : "sh"
},
"inspec exec examples/profile --format json | jq": {
"key": "inspec_exec_examples_profile_format_json_jq.txt",
"extra": true,
"shell" : "sh"
},
"inspec json examples/profile": {
"key": "inspec_json_examples_profile.txt",
"extra": true,
"shell" : "sh"
},
"inspec archive examples/profile": {
"key": "inspec_archive_examples_profile.txt",
"extra": true,
"shell" : "sh"
},
"inspec env": {
"key": "inspec_env.txt",
"extra": true,
"shell" : "sh"
},
"inspec exec examples/inheritance": {
"key": "inspec_exec_examples_inheritance.txt",
"extra": true
},
"inspec exec test/unit/mock/profiles/failures": {
"key": "inspec_exec_test_unit_mock_profiles_failures.txt",
"extra": true,
"shell" : "sh"
},
"help": {
"key": "echo_help_inspec_shell.txt",
"extra": false,
"shell" : "inspec"
},
"help resources": {
"key": "echo_help_resources_inspec_shell.txt",
"extra": false,
"shell" : "inspec"
},
"help file": {
"key": "echo_help_file_inspec_shell.txt",
"extra": false,
"shell" : "inspec"
},
"help command": {
"key": "echo_help_command_inspec_shell.txt",
"extra": false,
"shell" : "inspec"
},
"help os": {
"key": "echo_help_os_inspec_shell.txt",
"extra": false,
"shell" : "inspec"
},
"command('uname -a').stdout": {
"key": "echo_command_uname_a_stdout_inspec_shell.txt",
"extra": false,
"shell" : "inspec"
},
"file('/proc/cpuinfo').owner": {
"key": "echo_file_proc_cpuinfo_owner_inspec_shell.txt",
"extra": false,
"shell" : "inspec"
},
"sshd_config.params": {
"key": "echo_sshd_config_params_inspec_shell.txt",
"extra": false,
"shell" : "inspec"
},
"describe\\ file\\(\\'/root\\'\\)\\ do'\n'\\ \\ it\\ \\{\\ should\\ exist\\ \\}'\n'\\ \\ its\\(\\'mode\\'\\)\\ \\{\\ should\\ cmp\\ \\'0750\\'\\}'\n'end": {
"key": "echo_describe_file_root_do_it_should_exist_its_mode_should_cmp_0750_end_inspec_shell.txt",
"extra": false,
"shell" : "inspec"
},
"control\\ \\\"id\\\"\\ do'\n'\\ \\ title\\ \\\"Check\\ permissions\\ on\\ /root\\!\\\"'\n'\\ \\ impact\\ 0.5'\n'\\ \\ describe\\ file\\(\\'/root\\'\\)\\ do'\n'\\ \\ \\ \\ its\\(\\'mode\\'\\)\\ \\{\\ should\\ cmp\\ \\'0750\\'\\}'\n'\\ \\ end'\n'end": {
"key": "echo_control_id_do_title_Check_permissions_on_root_impact_0_5_describe_file_root_do_its_mode_should_cmp_0750_end_end_inspec_shell.txt",
"extra": false,
"shell" : "inspec"
}
}

View file

@ -1,5 +1 @@
inspec> command('uname -a').stdout
Welcome to the interactive InSpec Shell
To find out how to use it, type: help
inspec> => "Linux 2e958a7a89d6 4.4.19-moby #1 SMP Mon Aug 22 23:30:19 UTC 2016 x86_64 Linux\n"
inspec> => "Linux 2e958a7a89d6 4.4.19-moby #1 SMP Mon Aug 22 23:30:19 UTC 2016 x86_64 Linux\n"

View file

@ -5,13 +5,10 @@
inspec>  its('mode') { should cmp '0750'}
inspec>  end
inspec>  end
Welcome to the interactive InSpec Shell
To find out how to use it, type: help
inspec>  ✖ id: File /root mode should cmp "0750" (
expected: "0750"
got: 0700
(compared using `cmp` matcher)
)

View file

@ -2,9 +2,6 @@
inspec>  it { should exist }
inspec>  its('mode') { should cmp '0750'}
inspec>  end
Welcome to the interactive InSpec Shell
To find out how to use it, type: help
inspec> 
File /root
 ✔ should exist

View file

@ -1,5 +1 @@
inspec> file('/proc/cpuinfo').owner
Welcome to the interactive InSpec Shell
To find out how to use it, type: help
inspec> => "root"
inspec> => "root"

View file

@ -1,7 +1,3 @@
inspec> help command
inspec> Welcome to the interactive InSpec Shell
To find out how to use it, type: help
Name: command
Description:
@ -19,9 +15,8 @@ end
describe command('ls') do
it { should exist }
end
Web Reference:
https://github.com/chef/inspec/blob/master/docs/resources.rst#command

View file

@ -1,7 +1,3 @@
inspec> help file
inspec> Welcome to the interactive InSpec Shell
To find out how to use it, type: help
Name: file
Description:
@ -18,9 +14,8 @@ describe file('path') do
it { should be_owned_by 'root' }
its('mode') { should cmp '0644' }
end
Web Reference:
https://github.com/chef/inspec/blob/master/docs/resources.rst#file

View file

@ -1,7 +1,3 @@
inspec> help
inspec> Welcome to the interactive InSpec Shell
To find out how to use it, type: help

Available commands:
@ -20,4 +16,3 @@ You are currently running on:
OS platform: alpine
OS family: alpine
OS release: 3.4.0

View file

@ -1,7 +1,3 @@
inspec> help os
inspec> Welcome to the interactive InSpec Shell
To find out how to use it, type: help
Name: os
Description:
@ -19,9 +15,8 @@ end
describe os.linux? do
it { should eq true }
end
Web Reference:
https://github.com/chef/inspec/blob/master/docs/resources.rst#os

View file

@ -1,5 +1 @@
inspec> help resources
inspec> Welcome to the interactive InSpec Shell
To find out how to use it, type: help
apache apache_conf apt ppa audit_policy auditd_conf auditd_rules command bash file bond bridge directory etc_group gem group grub_conf host iis_site inetd_conf interface iptables json kernel_module kernel_parameter linux_kernel_parameter limits_conf login_defs mount mssql_session mysql mysql_conf mysql_session npm ntp_conf oneget os os_env package parse_config parse_config_file passwd pip port postgres postgres_conf postgres_session powershell script processes registry_key windows_registry_key security_policy service systemd_service upstart_service sysv_service bsd_service launchd_service runit_service shadow ssl ssh_config sshd_config sys_info users user vbscript windows_feature xinetd_conf wmi yum yumrepo yaml csv ini
apache apache_conf apt ppa audit_policy auditd_conf auditd_rules command bash file bond bridge directory etc_group gem group grub_conf host iis_site inetd_conf interface iptables json kernel_module kernel_parameter linux_kernel_parameter limits_conf login_defs mount mssql_session mysql mysql_conf mysql_session npm ntp_conf oneget os os_env package parse_config parse_config_file passwd pip port postgres postgres_conf postgres_session powershell script processes registry_key windows_registry_key security_policy service systemd_service upstart_service sysv_service bsd_service launchd_service runit_service shadow ssl ssh_config sshd_config sys_info users user vbscript windows_feature xinetd_conf wmi yum yumrepo yaml csv ini

View file

@ -1,6 +1,2 @@
inspec> sshd_config.params
Welcome to the interactive InSpec Shell
To find out how to use it, type: help
inspec> => {"authorizedkeysfile"=>[".ssh/authorized_keys"],
"subsystem"=>["sftp\t/usr/lib/ssh/sftp-server"]}

View file

@ -1,9 +1,11 @@
#terminal-container {
max-width: 1200px;
height: 350px;
margin: 0 auto;
padding: 2px;
font-size: 16px;
max-width: 1000px;
-webkit-font-smoothing: antialiased;
font-family: monospace;
font-size: 1.2rem;
}
#terminal-container .terminal {
@ -26,4 +28,4 @@ so we're deep assigning here to make sure that 0 is marked as black */
on the black background so we're deep assigning to make 6 a lightskyblu*/
:host /deep/ .xterm-color-6 {
color: lightskyblue;
}
}

View file

@ -1 +1 @@
<div (keyup)="onKey($event)" (keydown)="handleDelete($event)" id="terminal-container"></div>
<div id="terminal-container" (window:resize)="onResize($event)"></div>

View file

@ -1,4 +1,12 @@
import { Component, OnInit, Input, Output, EventEmitter, SimpleChange } from '@angular/core';
import {
Component,
OnInit,
OnChanges,
Input,
Output,
EventEmitter,
SimpleChange
} from '@angular/core';
declare var Terminal: any;
@Component({
@ -6,19 +14,22 @@ declare var Terminal: any;
templateUrl: 'app/xterm-terminal/xterm-terminal.component.html',
styleUrls: ['app/xterm-terminal/xterm-terminal.component.css']
})
export class XtermTerminalComponent implements OnInit {
@Input() response: string;
@Input() shell: string;
@Output() command: EventEmitter<string> = new EventEmitter<string>();
export class XtermTerminalComponent implements OnInit, OnChanges{
// handle up/down arrow functionality
previousCommands: any = [];
poppedCommands: any = [];
// This Xterm implementation emits all commands, once the users presses 'Enter'
@Output() stdin: EventEmitter<string> = new EventEmitter<string>();
// waits for events that should be printed on the terminal
@Input() stdout: EventEmitter<string>;
// sets the shell prompt
@Input() prompt: string;
// handle up/down command history functionality
history: any = [];
historyIndex: number;
last: number;
// handle user input
buffer: string = ''; // the buffer is our string of user input
blockCmd: string = ''; // handle describe and control blocks
// caches stdin, until the user presses Enter
buffer: string = '';
// xterm variables
terminalContainer: any;
@ -27,200 +38,184 @@ export class XtermTerminalComponent implements OnInit {
cols: string;
rows: string;
// we cannot use \33[2K\r since strict mode recognize it as ocal number
clearLine = "\u001b[2K\r"
ngOnInit() {
// set up Xterm terminal (https://github.com/sourcelair/xterm.js)
this.terminalContainer = document.getElementById('terminal-container');
this.cols = '70';
this.rows = '70';
this.createTerminal();
}
this.initializeTerminal();
}
// watch for changes on the value of response and call printResponse
ngOnChanges(changes: {[propertyName: string]: SimpleChange}) {
if (changes['response'] && this.term) {
let currentResponse = changes['response'].currentValue;
this.printResponse(currentResponse);
ngOnChanges(changes: {[propKey: string]: SimpleChange}) {
let log: string[] = [];
for (let propName in changes) {
let changedProp = changes[propName];
// reprint prompt
if (propName == 'prompt' && this.term != null) {
this.term.write(this.clearLine+this.prompt)
}
}
}
onResize(event) {
this.updateTerminalSize()
}
updateTerminalSize() {
// recalculate cols and rows
let charwidth = 24;
// read size from parent wrapping element
let cols = 80
let rows = Math.floor(this.terminalContainer.parentElement.clientHeight / charwidth)
this.term.resize(cols, rows);
}
// create Xterm terminal (https://github.com/sourcelair/xterm.js)
createTerminal() {
initializeTerminal() {
while (this.terminalContainer.children.length) {
this.terminalContainer.removeChild(this.terminalContainer.children[0]);
}
this.term = new Terminal({
cursorBlink: true
cursorBlink: true,
});
this.term.open(this.terminalContainer);
this.term.fit();
var initialGeometry = this.term.proposeGeometry(),
cols = initialGeometry.cols,
rows = initialGeometry.rows;
this.cols = cols;
this.rows = rows;
this.runFakeTerminal()
}
// more Xterm terminal stuff. we're faking it. faking it can be a good thing ;)
runFakeTerminal() {
// start registering the event listener, only need to be done the first time
if (this.term._initialized) {
return;
}
this.term._initialized = true;
this.setPrompt();
this.writePrompt();
// determine rows for terminal
this.updateTerminalSize()
let self = this
// raw data
this.term.on('data', function(data){
self.writeBuffer(data)
})
// overwrite keydown handler to be able to customize cursor handling
this.term.attachCustomKeydownHandler(function(ev){
return self.keydown(ev);
})
// handle stream events from the app
this.stdout.subscribe(function(data) {
self.onStdout(data)
}, function(err) {
console.error(err)
})
}
// if the response matches the special message we send when the user
// has entered 'next' or 'prev' (to navigate through the demo)
// then no need to print the response, just set the prompt. otherwise,
// print response and set prompt
printResponse(response) {
if (response.match(/.30mnext|.30mprev|.30mlast/)) {
// call createTerminal to clear terminal screen on next and prev
this.createTerminal();
} else {
this.term.writeln(response);
this.setPrompt();
}
// this writes the raw buffer and includes invisible charaters
writeBuffer(data) {
this.term.write(data);
this.buffer += data;
}
// the value of shell is taken as an input. the default
// shellprompt is displayed unless shell is set to 'inspec-shell'
setPrompt() {
this.buffer = '';
if (this.shell === 'inspec-shell') {
if (this.blockCmd != '') {
this.term.write(' inspec>*'); // green inspec shell prompt with * to denote multi-line command
// reset the command buffer
resetBuffer() {
this.buffer = ''
}
// print out stdout data
onStdout(data) {
let res = data
this.term.write(res);
}
// print out error
onStderr(response) {
this.term.writeln(response);
}
// writes the command prompt, a prompt is not part of the input buffer
writePrompt() {
this.term.write(this.prompt);
}
// stores a command in commmand history
storeInHistory(cmd) {
// remove cariage returns from history commands
cmd = cmd.replace(/\r/,'')
this.history.push(cmd);
this.historyIndex = this.history.length
}
// prints the current selected command at history index
historyCmdtoStdout() {
this.buffer = this.history[this.historyIndex]
this.reprintCommand(this.buffer )
}
// reprint a command in the current line
reprintCommand(cmd){
this.term.write(this.clearLine+this.prompt + cmd)
}
// resets the terminal
clear() {
this.term.reset()
this.writePrompt()
}
// terminal event handler
// be aware that we need to decouple the event loops, therefore we need to
// use setTimeout to reliable print content on the terminal
keydown(ev) {
// console.log(ev);
let self = this
if (ev.keyCode === 13) {
let cmd = self.buffer
// special handling for clear and cls
if (/^\s*clear|cls\s*/.exec(cmd) != null) {
// reset terminal
setTimeout(function(){
self.clear()
}, 0);
} else {
this.term.write(' inspec> '); // green inspec shell prompt
}
} else {
this.term.write('$ '); // blue regular shell prompt
}
}
// delete everything on the line
deleteCharacters() {
// don't delete the prompt
let letters = this.term.x - 2;
for (var i = 0; i < letters; i++) {
this.term.write('\b \b');
}
}
// keydown calls handleDelete to check if the user is holding down the backpace key
// TODO: make this work! the backspace event isn't coming in for some reason :(
handleDelete(ev) {
if (ev.keyCode == 8) {
this.deleteCharacters();
}
}
// handle describe and control blocks. if the command entered matches the
// syntax we would generally expect in a control/describe block, then we
// wait to have the whole string command (as marked by 'end' for a simple describe
// block and end end for a control block) and emit that
handleBlockCommand(buffer) {
this.blockCmd += buffer + '';
if (this.blockCmd.match(/^control/)) {
if (this.blockCmd.match(/end.*end.*/)) {
this.command.emit(this.blockCmd);
this.blockCmd = '';
} else {
this.setPrompt();
}
} else {
if (buffer.match(/^end$/)) {
this.command.emit(this.blockCmd);
this.blockCmd = '';
} else {
this.setPrompt();
}
}
}
// onKey will check if the character is printable (on keyup) and add it to the buffer. if the enter key is hit
// the value of the buffer is emitted as 'command'. onKey also supports proper backspace handling and
// the ability to up-arrow through previous commands/down arrow back through them.
onKey(ev) {
var shell = null
// determine if the character is a printable one
var printable = ['Alt', 'Control', 'Meta', 'Shift', 'CapsLock', 'Tab', 'Escape', 'ArrowLeft', 'ArrowRight'].indexOf(ev.key) == -1
// on enter, save command to array and send current value of buffer
// to parent component (app)
if (ev.keyCode == 13) {
if ((this.buffer === 'clear') || (this.buffer === 'cls')) {
this.createTerminal();
} else {
this.previousCommands.push(this.buffer);
this.term.write('\r\n');
if (this.shell === 'inspec-shell') {
// if the command entered matches any of the typical describe and
// control blocks, then we call handleBlockCommand to not emit the
// value until we have the whole multi-line command
if (this.buffer.match(/^describe.*|^it.*|^end$|^impact.*|^title.*|^control.*/)) {
this.handleBlockCommand(this.buffer);
} else {this.command.emit(this.buffer);}
} else {
this.command.emit(this.buffer);
}
// decouple from event loop, write a new line
setTimeout(function(){
self.term.writeln('')
self.stdin.emit(cmd);
self.storeInHistory(cmd)
}, 0);
}
// reset index to last item +1
self.resetBuffer()
}
// on backspace, pop characters from buffer
else if (ev.keyCode == 8) {
// if inspec shell is being used, this needs to be set to 9 to account for the extra letters
if (this.shell === 'inspec-shell') {
if (this.term.x > 8) {
this.buffer = this.buffer.substr(0, this.buffer.length-1);
this.term.write('\b \b');
}
} else {
// setting the value here to 3 ensures that we don't delete the promp '$' or the space after it
if (this.term.x > 2) {
this.buffer = this.buffer.substr(0, this.buffer.length-1);
this.term.write('\b \b');
}
}
self.buffer = self.buffer.substr(0, self.buffer.length-1);
setTimeout(function(){
self.reprintCommand(self.buffer)
}, 0);
}
// on up arrow, delete anything on line, print previous command
// and push last to poppedCommands
else if (ev.keyCode === 38) {
let last;
this.buffer = '';
if (this.previousCommands.length > 0) {
last = this.previousCommands.pop();
this.poppedCommands.push(last);
} else {
last = '';
}
this.deleteCharacters();
this.buffer = last;
this.term.write(last);
}
// on down arrow, delete anything on line, print last item from poppedCommands
// and push previous to previousCommands
// press the up key, find previous item in cmd history
else if (ev.keyCode === 40) {
let previous;
this.buffer = '';
if (this.poppedCommands.length > 0) {
previous = this.poppedCommands.pop();
this.previousCommands.push(previous);
} else {
previous = '';
}
this.deleteCharacters();
this.buffer = previous;
this.term.write(previous);
setTimeout(function(){
if (self.historyIndex < self.history.length - 1) {
self.historyIndex += 1
self.historyCmdtoStdout()
}
}, 0);
}
// if the character is printable and none of the event key codes from above were matched
// then we write the character on the prompt line and add the character to the buffer
else if (printable) {
this.term.write(ev.key);
this.buffer += ev.key;
// press the down key, find next item in cmd history
else if (ev.keyCode === 38) {
setTimeout(function(){
if (self.historyIndex >= 1) {
self.historyIndex -= 1
self.historyCmdtoStdout()
}
}, 0);
}
// this forces xterm not to handle the key events with its own event handler
return false;
}
}

View file

@ -22,7 +22,6 @@ gulp.task('copy', function(){
'app/**/*.css',
'app/**/*.html',
'tutorial_files/**',
'node_modules/shellwords/src/shellwords.coffee',
])
.pipe(gcopy('dist/'));
})

View file

@ -1,20 +1,18 @@
<html>
<head>
<title>InSpec Demo</title>
<title>InSpec Interactive Tutorial</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- build:css css/combined.css -->
<link rel="stylesheet" href="styles.css">
<link rel="stylesheet" href="node_modules/xterm/src/xterm.css">
<link rel="stylesheet" href="assets/font-awesome/css/font-awesome.css">
<!-- endbuild -->
<link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet">
</head>
<body>
<!-- FIXME I need compilation and integration vv -->
<script src="node_modules/shellwords/src/shellwords.coffee"></script>
<!-- build:js scripts/combined.js -->
<script src="node_modules/xterm/src/xterm.js"></script>
<script src="node_modules/xterm/addons/fit/fit.js"></script>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

View file

@ -23,7 +23,6 @@
"core-js": "^2.4.0",
"reflect-metadata": "^0.1.3",
"rxjs": "5.0.0-beta.6",
"shellwords": "^0.1.0",
"systemjs": "0.19.27",
"webpack": "^1.13.2",
"webpack-dev-server": "^1.15.1",