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'; 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(() => { onMounted(() => {
prompt = document.getElementsByClassName('console-input-prompt')[0]; (<any>window).cli?.build('#konsole');
input = document.getElementsByClassName('console-input-field')[0];
output = document.getElementsByClassName('console-output')[0];
banner();
}); });
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> </script>
<style> <style>
.console { .cli-container {
display: flex;
flex-direction: column;
padding: 1rem; padding: 1rem;
background: #333; background: #333;
font-family: monospace !important; font-family: monospace !important;
overflow-y: auto; overflow-y: auto;
min-height: 150px;
max-height: 300px;
.console-output { .cli-stdout {
flex-grow: 1; flex-grow: 1;
color: #0f0; color: #0f0;
.cli-stdout-line {
margin: 0;
padding: 0;
min-height: 1.25rem;
}
} }
.console-output-line { .cli-stdin {
margin: 0;
padding: 0;
min-height: 1.25rem;
}
.console-input {
display: flex; display: flex;
margin: 0; margin: 0;
.console-input-prompt { .cli-stdin-prompt {
padding-right: 0.55em; padding-right: 0.55em;
text-wrap: nowrap; text-wrap: nowrap;
color: #0f0; color: #0f0;
} }
.console-input-field { .cli-stdin-input {
border: none; border: none;
outline: none; outline: none;
font-size: 1rem; font-size: 1rem;
@ -255,12 +82,5 @@ window.cli['whoami'] = {
</style> </style>
<template> <template>
<div class="console"> <div id="konsole"></div>
<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>
</template> </template>

View File

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

View File

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

View File

@ -4,15 +4,16 @@ import Contact from '@/components/contact.vue';
import Konsole from '@/components/konsole.vue'; import Konsole from '@/components/konsole.vue';
import Projects from '@/components/projects.vue'; import Projects from '@/components/projects.vue';
import Refrences from '@/components/refrences.vue'; import Refrences from '@/components/refrences.vue';
import type {Project as P} from '@/models/project';
import {ref} from 'vue'; 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: '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: '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'}, {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: '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: '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 '}, {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> <template>
<div class="p-3"> <div class="p-3">
<!-- Terminal --> <!-- Terminal -->
<konsole class="mb-5" style="max-height: 300px" /> <konsole class="mb-5" />
<!-- Steps --> <!-- Steps -->
<div class="mb-5 pt-5"> <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> <h3 class="mb-0">About</h3>
<hr class="mb-4"> <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%;"> <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> <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>
Zak Timson is a software engineer with over 10 years of professional experience. Zak has had a love for <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>
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"> <div class="mt-4">
<h4 class="mb-3 text-muted">CSV & References</h4> <h4 class="mb-3 text-muted">CSV & References</h4>

View File

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