Konsole updates
All checks were successful
Build Website / Build NPM Project (push) Successful in 20s
Build Website / Build & Push Dockerfile (push) Successful in 1m5s

This commit is contained in:
Zakary Timson 2024-01-05 01:40:17 -05:00
parent ae279b478b
commit 88f2a716b9
8 changed files with 349 additions and 220 deletions

View File

@ -1,216 +1,43 @@
<script setup>
<script setup lang="ts">
import '../misc/konsole';
import {onMounted} from 'vue';
const hostname = 'virtual';
let history = [];
let historyIndex = 0;
let prompt;
let input;
let output;
function focus() {
input.focus();
}
function disable() {
input.disabled = true;
prompt.style.visibility = 'hidden';
}
function enable() {
input.disabled = false;
input.focus();
prompt.style.visibility = 'visible';
}
function banner() {
stdOut(`Konsole 0.2.0 LTS virtual tty1<br><br>${hostname} login: root<br>password:<br><br>`);
}
function process(command) {
(Array.isArray(command) ? command.join(' ') : command).split(';').filter(c => !!c).forEach(c => {
const parts = c.split(' ').filter(c => !!c);
if(window.cli[parts[0]] == undefined || window.cli[parts[0]].run == undefined) {
stdErr(`${parts[0]}: command not found`);
} else {
try {
const out = window.cli[parts[0]].run(parts.slice(1));
if(!!out) stdOut(out);
} catch(err) {
console.error(err)
stdErr(`${parts[0]}: exited with a non-zero status`);
}
}
});
}
function stdErr(text) {
const p = document.createElement('p');
p.classList.add('console-output-line');
p.classList.add('console-output-error');
p.innerText = text;
output.appendChild(p);
}
function stdOut(text, html=true) {
const p = document.createElement('p');
p.classList.add('console-output-line');
p[html ? 'innerHTML' : 'innerText'] = text;
output.appendChild(p);
}
function stdIn(event) {
if(event.key == "Enter") {
disable();
let inputValue = input.value;
input.value = '';
stdOut(`root@localhost:~ # ${inputValue}`, false);
if(!!inputValue) {
history.push(inputValue);
historyIndex = history.length;
process(inputValue)
}
enable();
} else if(event.key == 'Up' || event.key == 'ArrowUp') {
if(historyIndex > 0) historyIndex--;
input.value = historyIndex == history.length ? '' : history[historyIndex];
setTimeout(() => {
const end = input.value.length;
input.setSelectionRange(end, end);
input.focus();
}, 1)
} else if(event.key == 'Down' || event.key == 'ArrowDown') {
if(historyIndex < history.length) historyIndex++;
input.value = historyIndex == history.length ? '' : history[historyIndex];
setTimeout(() => {
const end = input.value.length;
input.setSelectionRange(end, end);
input.focus();
}, 1)
}
}
onMounted(() => {
prompt = document.getElementsByClassName('console-input-prompt')[0];
input = document.getElementsByClassName('console-input-field')[0];
output = document.getElementsByClassName('console-output')[0];
banner();
(<any>window).cli?.build('#konsole');
});
window.cli = {};
window.cli['clear'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Clear console output';
},
run: args => {
output.innerHTML = '';
}
}
window.cli['echo'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Output text to console';
},
run: args => {
return args.join(' ');
}
}
window.cli['exit'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'End session';
},
run: args => {
process('clear');
history = [];
historyIndex = 0;
banner();
}
}
window.cli['help'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Display all commands';
},
run: args => {
return Object.keys(window.cli).map(command => `${command} - ${window.cli[command].help()}`).join('<br>') + '<br><br>';
}
}
window.cli['hostname'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Get computer hostname';
},
run: args => {
return 'localhost'
}
}
window.cli['man'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Command manual';
},
run: args => {
return window.cli[args[0]].help();
}
}
window.cli['whoami'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Get username';
},
run: args => {
return 'root'
}
}
</script>
<style>
.console {
display: flex;
flex-direction: column;
.cli-container {
padding: 1rem;
background: #333;
font-family: monospace !important;
overflow-y: auto;
min-height: 150px;
max-height: 300px;
.console-output {
.cli-stdout {
flex-grow: 1;
color: #0f0;
}
.console-output-line {
.cli-stdout-line {
margin: 0;
padding: 0;
min-height: 1.25rem;
}
}
.console-input {
.cli-stdin {
display: flex;
margin: 0;
.console-input-prompt {
.cli-stdin-prompt {
padding-right: 0.55em;
text-wrap: nowrap;
color: #0f0;
}
.console-input-field {
.cli-stdin-input {
border: none;
outline: none;
font-size: 1rem;
@ -255,12 +82,5 @@ window.cli['whoami'] = {
</style>
<template>
<div class="console">
<div class="console-output"></div>
<div class="console-input" @click=" focus()">
<div class="console-input-prompt">root@{{hostname}}:~ #</div>
<label for="console-input-field" class="hidden-label"><!-- Accessibility -->CLI Input</label>
<input id="console-input-field" class="console-input-field" type="text" @keydown="stdIn($event)">
</div>
</div>
<div id="konsole"></div>
</template>

View File

@ -1,11 +1,5 @@
<script setup lang="ts">
export interface Project {
icon?: string;
name: string;
description: string;
link: string;
source?: string;
}
import type {Project} from '@/models/project';
defineProps({
projects: {type: Array as () => Project[], required: true}

View File

@ -1,3 +1,3 @@
export const environment = {
postMailKey: (<any>window)?.env?.APP_POSTMAIL_KEY || import.meta.env.APP_POSTMAIL_ACCESS_TOKEN,
postMailKey: (<any>window)?.env?.APP_POSTMAIL_KEY || (<any>import.meta).env.APP_POSTMAIL_ACCESS_TOKEN,
}

315
src/misc/konsole.js Normal file
View File

@ -0,0 +1,315 @@
window.cli = {
// Element references
_input: null,
_parent: null,
_prompt: null,
_output: null,
// CLI State
_history: [],
_index: 0,
pwd: '/',
hostname: 'virtual',
env: {},
exec: {},
filesystem: {},
user: 'root',
version: '0.3.0',
build: (elementId) => {
window.cli._parent = document.querySelector(elementId);
if(!window.cli._parent)
throw new Error(`Could not create konsole, element "${elementId}" does not exist`);
window.cli._parent.innerHTML = `
<div class="cli-container">
<div class="cli-stdout"></div>
<div class="cli-stdin" onclick="window.cli._input.focus()">
<label for="${elementId}-cli-stdin-input" class="cli-stdin-prompt">${window.cli.prompt()}</label>
<input id="${elementId}-cli-stdin-input" class="cli-stdin-input" type="text" />
</div>
</div>`;
window.cli._input = document.querySelector(elementId + ' .cli-stdin-input');
window.cli._prompt = document.querySelector(elementId + ' .cli-stdin-prompt');
window.cli._output = document.querySelector(elementId + ' .cli-stdout');
window.cli._input.addEventListener('keyup', (e) => {
if(e.key == "Enter") {
window.cli.disable();
window.cli.stdOut(`${window.cli.prompt()} ${window.cli._input.value}`);
if(!!window.cli._input.value) {
window.cli._history.push(window.cli._input.value);
window.cli._index = window.cli._history.length;
window.cli.stdIn(window.cli._input.value)
}
window.cli._input.value = '';
window.cli.enable();
} else if(e.key == 'Up' || e.key == 'ArrowUp') {
if(window.cli._index > 0) window.cli._index--;
window.cli._input.value = window.cli._index == window.cli._history.length ? '' : window.cli._history[window.cli._index];
setTimeout(() => {
const end = window.cli._input.value.length;
window.cli._input.setSelectionRange(end, end);
window.cli._input.focus();
}, 1)
} else if(e.key == 'Down' || e.key == 'ArrowDown') {
if(window.cli._index < window.cli._history.length) window.cli._index++;
window.cli._input.value = window.cli._index == window.cli._history.length ? '' : window.cli._history[window.cli._index];
setTimeout(() => {
const end = window.cli._input.value.length;
window.cli._input.setSelectionRange(end, end);
window.cli._input.focus();
}, 1)
}
});
setTimeout(() => window.cli.exec['banner'].run(), 1);
},
disable: () => {
window.cli._input.disabled = true;
window.cli._prompt.style.visibility = 'hidden';
},
enable: () => {
window.cli._input.disabled = false;
window.cli._input.focus();
window.cli._prompt.style.visibility = 'visible';
},
path: (path=window.cli.pwd) => {
let p = path[0] == '/'? path : (window.cli.pwd + (window.cli.pwd.endsWith('/') ? '' : '/') + path.replace('./', ''))
.replaceAll('//', '/');
const parts = p.split('/').filter(p => !!p);
for(let i = 0; i < parts.length; i++) {
if(parts[i] == '..') {
i--;
parts.splice(i, 2);
i--;
}
}
return '/' + (parts.length ? parts.join('/') : '');
},
prompt: () => `${window.cli.user}@${window.cli.hostname}:${window.cli.pwd}${window.cli.user == 'root' ? '#' : '$'}`,
fs: (path, set) => {
let target = window.cli.filesystem;
const parts = window.cli.path(path).split('/').filter(p => !!p);
parts.forEach((p, i, arr) => {
if(!target[p] && set !== undefined) {
if(i + 1 != arr.length) target[p] = {};
else target[p] = set == null ? undefined : set;
}
target = target[p];
});
return target;
},
stdErr: (text) => {
const p = document.createElement('p');
p.classList.add('cli-stdout-line');
p.classList.add('cli-stdout-error');
p.innerText = text;
window.cli._output.appendChild(p);
},
stdIn:(command) => {
(Array.isArray(command) ? command.join(' ') : command).split(';').filter(c => !!c).forEach(c => {
const parts = c.match(/(?:[^\s"]+|"[^"]*")+/g);
if(!parts) return;
const exec = window.cli.exec[parts[0]];
if(!exec?.run) {
window.cli.stdErr(`${parts[0]}: command not found`);
} else {
try {
const args = parts.slice(1).map(a => (a[0] == '"' || a[0] == "'") ? a.slice(1, -1) : a);
const out = exec.run(args);
if(!!out) window.cli.stdOut(out);
} catch(err) {
console.error(err);
window.cli.stdErr(err.message || `${parts[0]}: exited with a non-zero status`);
}
}
});
},
stdOut: (text, html=false) => {
const p = document.createElement('p');
p.classList.add('cli-stdout-line');
p[html ? 'innerHTML' : 'innerText'] = text;
window.cli._output.appendChild(p);
},
};
window.cli.exec['banner'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Display login banner';
},
run: args => {
window.cli.stdOut(`Konsole ${window.cli.version} LTS ${window.cli.hostname} tty1\n\n${window.cli.hostname} login: ${window.cli.user}\npassword:\n\n`);
}
}
window.cli.exec['cd'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Clear console output';
},
run: args => {
const path = window.cli.fs(args[0]);
if(!path) throw new Error(`cd: \'${args[0]}\': No such file or directory`)
window.cli.pwd = window.cli.path(args[0]);
window.cli._prompt.innerText = window.cli.prompt();
}
}
window.cli.exec['clear'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Clear console output';
},
run: args => {
window.cli._output.innerHTML = '';
}
}
window.cli.exec['echo'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Output text to console';
},
run: args => {
return args.join(' ');
}
}
window.cli.exec['exit'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'End session';
},
run: args => {
window.cli.stdIn('clear');
window.cli._history = [];
window.cli._index = 0;
window.cli.stdIn('banner');
}
}
window.cli.exec['help'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Display all commands';
},
run: args => {
return `Konsole v${window.cli.version} - A prototype bash emulator written by Zakary Timson\n\n` +
Object.keys(window.cli.exec).map(command => `${command} - ${window.cli.exec[command].help()}`).join('\n') + '\n\n';
}
}
window.cli.exec['hostname'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Get computer hostname';
},
run: args => {
return window.cli.hostname;
}
}
window.cli.exec['ls'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Display directory contents';
},
run: args => {
const target = window.cli.fs(args[0]);
if(!target) throw new Error(`ls: cannot access \'${args[0]}\': No such file or directory`)
return Object.keys(target).reduce((acc, p) => acc + `${p}\n`, '');
}
}
window.cli.exec['man'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'View command\'s manual';
},
run: args => {
return window.cli.exec[args[0]].help();
}
}
window.cli.exec['mkdir'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Create new directory';
},
run: args => {
if(!args[0]) throw new Error('mkdir: missing operand');
window.cli.fs(args[0], {});
}
}
window.cli.exec['pwd'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Get present working directory';
},
run: args => {
return window.cli.pwd;
}
}
window.cli.exec['rm'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Delete file or directory';
},
run: args => {
if(!args[0]) throw new Error('rm: missing operand');
window.cli.fs(args[0], null);
}
}
window.cli.exec['shower-thought'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Random shower thought';
},
run: args => {
const motd = [
'Why do kamikaze pilots wear helmets?',
'How are giraffes real, but unicorns made up',
'When you are a kid you don\'t realize you are also watching your parents grow up',
'Some one at Google was like "Yea, just have someone drive down every road on earth!"',
'The number of people older than you never goes up',
'When you brush your teeth you are cleaning your skeleton',
'Pregnancy is like a group project where one person get\'s stuck with all the work',
'If the universe wasn\'t infinite it would be even scarier',
'Either we are alone in the universe or we are not. both are terrifying',
'The object of golf is to play the least amount of golf.'
];
return motd[~~(Math.random() * motd.length)];
}
}
window.cli.exec['whoami'] = {
autocomplete: () => {
return [];
},
help: () => {
return 'Get current user account';
},
run: args => {
return window.cli.user;
}
}

7
src/models/project.ts Normal file
View File

@ -0,0 +1,7 @@
export interface Project {
icon?: string;
name: string;
description: string;
link: string;
source?: string;
}

View File

@ -2,7 +2,7 @@ import { createRouter, createWebHistory } from 'vue-router'
import Home from '@/views/Home.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
history: createWebHistory((<any>import.meta).env.BASE_URL),
routes: [
{path: '/', name: 'home', component: Home}
]

View File

@ -4,15 +4,16 @@ import Contact from '@/components/contact.vue';
import Konsole from '@/components/konsole.vue';
import Projects from '@/components/projects.vue';
import Refrences from '@/components/refrences.vue';
import type {Project as P} from '@/models/project';
import {ref} from 'vue';
const services: Projects[] = [
const services: P[] = [
{name: 'Formula Manager', icon: 'https://git.zakscode.com/avatars/7ec6bfd66b2bf9bad5c43c75a33f9cb3f6609b05c33a31f5d1e524a567cd09c1?size=280', link: 'https://screenprintingsuppliescanada.com/formulation-manager', description: 'A web & computer application used by FH&Sons to record chemical formulas & distribute them to clients'},
{name: 'Map Alliance', icon: 'https://maps.zakscode.com/assets/images/logo.png', link: 'https://maps.zakscode.com', description: 'An online GIS tool which enables users to view, edit & share various "marked-up" maps'},
{name: 'Phone Reminders', icon: 'https://phone-reminders.com/phone-reminders.png', link: 'https://phone-reminders.com', description: 'Automatically call & send SMS reminders to clients for events using Google Calendar'},
];
const openSource: Projects[] = [
const openSource: P[] = [
{name: 'ETF Demo', icon: 'https://git.zakscode.com/repo-avatars/0709db0c51d295d2d29b709865bd95f26e351f72a5c993ca63cd9ec4b4a07f43', link: 'https://etf.zakscode.com', source: 'https://git.zakscode.com/ztimson/etf-demo', description: 'Compare CSV files containing "Electronically Traded Funds" data (Check source for CSV files)'},
{name: 'Legio 30', icon: 'https://git.zakscode.com/repo-avatars/f66e3d6f5ff4646b45e859f6bf00c0e0de0621d8a45a47481d53d67b67700f2a', link: 'https://legio-30.org', source: 'https://git.zakscode.com/ztimson/legio-30', description: 'Website for a non-profit Roman re-enactment group from Southern Ontario'},
{name: 'Pelican Landing', icon: 'https://git.zakscode.com/ztimson/pelican-landing/raw/branch/develop/src/assets/logo.png', link: 'https://pelican-landing.zakscode.com', source: 'https://git.zakscode.com/ztimson/pelican-landing', description: 'Business website for a hunting & fishing lodge on the Lage of Woods in Northern Ontario '},
@ -36,7 +37,7 @@ fetch('https://git.zakscode.com/api/v1/repos/search', {
<template>
<div class="p-3">
<!-- Terminal -->
<konsole class="mb-5" style="max-height: 300px" />
<konsole class="mb-5" />
<!-- Steps -->
<div class="mb-5 pt-5">
@ -58,17 +59,8 @@ fetch('https://git.zakscode.com/api/v1/repos/search', {
<h3 class="mb-0">About</h3>
<hr class="mb-4">
<img alt="Childhood" src="/childhood.jpg" height="150px" width="auto" class="float-end m-3 m-md-0 ml-md-3" style="border-radius: 50%;">
<p>
Zak Timson is a software engineer with over 10 years of professional experience. Zak has had a love for
computers since he was born & taught him self to code at the age of 13. Since then, he has gone to school
for computer science & has worked for both small businesses and large corporations as a developer and team lead.
</p>
<p>
Zak specializes in full-stack web development & server infrastructure, and primarily works on large enterprise
grade "Software as a service" (SaaS) products. As a software architect & team lead he is able to work with
business's to create a road map of their needs, build enterprise grade software solutions that meet those
needs & work with clients to host & deliver automatic updates at scale using continuous integration.
</p>
<p>Zak Timson is a software engineer with over 10 years of professional experience. Zak has had a love for computers since he was born & taught him self to code at the age of 13. Since then, he has gone to school for computer science & has worked for both small businesses and large corporations as a developer and team lead.</p>
<p>Zak specializes in full-stack web development & server infrastructure, and primarily works on large enterprise grade "Software as a service" (SaaS) products. As a software architect & team lead he is able to work with business's to create a road map of their needs, build enterprise grade software solutions that meet those needs & work with clients to host & deliver automatic updates at scale using continuous integration.</p>
<div class="mt-4">
<h4 class="mb-3 text-muted">CSV & References</h4>

View File

@ -10,6 +10,7 @@
"compilerOptions": {
"composite": true,
"noEmit": true,
"module": "ESNext",
"moduleResolution": "Bundler",
"types": [
"node"