Compare commits
7 Commits
f678c0fa54
...
unstable
| Author | SHA1 | Date | |
|---|---|---|---|
| 9ac27f5fbd | |||
| 1faab39849 | |||
| 0ad0e362d6 | |||
| 7fbf3593fb | |||
| 8830b130ad | |||
| aaecba5642 | |||
| 05c3dcd5b5 |
32
package-lock.json
generated
32
package-lock.json
generated
@@ -1195,9 +1195,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@sveltejs/kit": {
|
"node_modules/@sveltejs/kit": {
|
||||||
"version": "2.48.4",
|
"version": "2.50.2",
|
||||||
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.48.4.tgz",
|
"resolved": "https://registry.npmjs.org/@sveltejs/kit/-/kit-2.50.2.tgz",
|
||||||
"integrity": "sha512-TGFX1pZUt9qqY20Cv5NyYvy0iLWHf2jXi8s+eCGsig7jQMdwZWKUFMR6TbvFNhfDSUpc1sH/Y5EHv20g3HHA3g==",
|
"integrity": "sha512-875hTUkEbz+MyJIxWbQjfMaekqdmEKUUfR7JyKcpfMRZqcGyrO9Gd+iS1D/Dx8LpE5FEtutWGOtlAh4ReSAiOA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
"peer": true,
|
||||||
@@ -1207,13 +1207,13 @@
|
|||||||
"@types/cookie": "^0.6.0",
|
"@types/cookie": "^0.6.0",
|
||||||
"acorn": "^8.14.1",
|
"acorn": "^8.14.1",
|
||||||
"cookie": "^0.6.0",
|
"cookie": "^0.6.0",
|
||||||
"devalue": "^5.3.2",
|
"devalue": "^5.6.2",
|
||||||
"esm-env": "^1.2.2",
|
"esm-env": "^1.2.2",
|
||||||
"kleur": "^4.1.5",
|
"kleur": "^4.1.5",
|
||||||
"magic-string": "^0.30.5",
|
"magic-string": "^0.30.5",
|
||||||
"mrmime": "^2.0.0",
|
"mrmime": "^2.0.0",
|
||||||
"sade": "^1.8.1",
|
"sade": "^1.8.1",
|
||||||
"set-cookie-parser": "^2.6.0",
|
"set-cookie-parser": "^3.0.0",
|
||||||
"sirv": "^3.0.0"
|
"sirv": "^3.0.0"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@@ -1226,11 +1226,15 @@
|
|||||||
"@opentelemetry/api": "^1.0.0",
|
"@opentelemetry/api": "^1.0.0",
|
||||||
"@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0",
|
"@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0",
|
||||||
"svelte": "^4.0.0 || ^5.0.0-next.0",
|
"svelte": "^4.0.0 || ^5.0.0-next.0",
|
||||||
|
"typescript": "^5.3.3",
|
||||||
"vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0"
|
"vite": "^5.0.3 || ^6.0.0 || ^7.0.0-beta.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@opentelemetry/api": {
|
"@opentelemetry/api": {
|
||||||
"optional": true
|
"optional": true
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"optional": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -2401,9 +2405,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/devalue": {
|
"node_modules/devalue": {
|
||||||
"version": "5.4.2",
|
"version": "5.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/devalue/-/devalue-5.6.2.tgz",
|
||||||
"integrity": "sha512-MwPZTKEPK2k8Qgfmqrd48ZKVvzSQjgW0lXLxiIBA8dQjtf/6mw6pggHNLcyDKyf+fI6eXxlQwPsfaCMTU5U+Bw==",
|
"integrity": "sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
@@ -3042,9 +3046,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -4156,9 +4160,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/set-cookie-parser": {
|
"node_modules/set-cookie-parser": {
|
||||||
"version": "2.7.2",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-3.0.1.tgz",
|
||||||
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
"integrity": "sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
},
|
},
|
||||||
"Interactible": false,
|
"Interactible": false,
|
||||||
"Func": null,
|
"Func": null,
|
||||||
"Parent": null
|
"Parent": 1
|
||||||
},
|
},
|
||||||
"2": {
|
"2": {
|
||||||
"Inode": 2,
|
"Inode": 2,
|
||||||
|
|||||||
0
src/lib/assets/locales/terminal/en_US.json
Normal file
0
src/lib/assets/locales/terminal/en_US.json
Normal file
@@ -1,68 +1,60 @@
|
|||||||
import { COMMANDS, GROUP, PASSWD, type CommandArgs, type ICommand, type Result } from './static';
|
import { VirtualFS, type TreeNode } from './fs';
|
||||||
import { VirtualFS } from './fs';
|
|
||||||
import { Terminal, type PrintData } from '../terminal/terminal';
|
import { Terminal, type PrintData } from '../terminal/terminal';
|
||||||
import { Stack } from '../stack';
|
import { Stack } from '../stack';
|
||||||
|
import { PASSWD, type User } from './etc/userData';
|
||||||
export type Permission = {
|
import { ExitCode } from './metadata';
|
||||||
r: boolean;
|
import { COMMANDS, type CommandArgs, type CommandResultData, type ICommand } from './commandRegistry';
|
||||||
w: boolean;
|
import { GROUP, type Group } from './etc/groupData';
|
||||||
x: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type BashInitArgs = {
|
export type BashInitArgs = {
|
||||||
stdio?: Terminal;
|
io?: Terminal;
|
||||||
user: User;
|
instanceId: number;
|
||||||
fs: any;
|
user: {username: string, password: string};
|
||||||
|
fs?: VirtualFS;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TimeStamps = {
|
export type Result = {
|
||||||
modified: Date;
|
exitCode: ExitCode;
|
||||||
changed: Date;
|
path: number; //the inode of the place that the command was executed in
|
||||||
accessed: Date;
|
resultData?: CommandResultData;
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: Finish this
|
|
||||||
// TODO: Change into a type instead of an enum for performance (low priority)
|
|
||||||
export enum ExitCode {
|
|
||||||
SUCCESS = 0,
|
|
||||||
ERROR = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
export type User = {
|
|
||||||
username: string;
|
|
||||||
passwd: string; //HASHED PASSWORD //TODO: Make a formated type
|
|
||||||
readonly uid: number; // Normal user 1000+ System user 1-999 root - 0 //TODO: Make a formated type
|
|
||||||
readonly gid: number; // Primary group | 'Users' 1000 - Others - 1000+ root - 0 //TODO: Make a formated type
|
|
||||||
home: string; //TODO: Make a formated type
|
|
||||||
history: string[];
|
|
||||||
cwd?: number; //TODO: Make a formated type
|
|
||||||
pwd?: number; //TODO: Make a formated type
|
|
||||||
};
|
|
||||||
|
|
||||||
export type Group = {
|
|
||||||
groupname: string;
|
|
||||||
gid: number; // Primary group 'Users' 1000 - Others - 1000+ root - 0
|
|
||||||
members: number[]; //TODO: Make a formated type UID
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Bash {
|
export class Bash {
|
||||||
|
private readonly _instanceId: number;
|
||||||
private vfs: VirtualFS;
|
private vfs: VirtualFS;
|
||||||
private _passwd: User[];
|
private _passwd: User[];
|
||||||
private _instances: Stack<User>;
|
private _userInstances: Stack<User>;
|
||||||
private _group: Group[];
|
private _group: Group[];
|
||||||
private _terminal!: Terminal;
|
private _terminal!: Terminal;
|
||||||
private user: User;
|
private user: User;
|
||||||
private readonly _commands: Record<string, ICommand>;
|
private readonly _commands: Record<string, ICommand>;
|
||||||
|
|
||||||
constructor(args: BashInitArgs) {
|
constructor(args: BashInitArgs) {
|
||||||
this.user = args.user;
|
this._instanceId = args.instanceId
|
||||||
this._commands = COMMANDS;
|
this._commands = COMMANDS;
|
||||||
this._passwd = PASSWD;
|
this._passwd = PASSWD;
|
||||||
this._group = GROUP;
|
this._group = GROUP;
|
||||||
this._terminal = args.stdio!;
|
this._userInstances = new Stack<User>();
|
||||||
this._instances = new Stack<User>();
|
this._terminal = args.io!;
|
||||||
|
|
||||||
this.vfs = new VirtualFS({ fs: args.fs, user: args.user });
|
const loginResult = this.userLogin(args.user.username, args.user.password);
|
||||||
|
if(loginResult == ExitCode.ERROR)
|
||||||
|
this._terminal.throwExeption(
|
||||||
|
`Failed to initialize bash instance - access denied for user ${args.user.username}`,
|
||||||
|
ExitCode.ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
this.user = this._userInstances.peek()!
|
||||||
|
this.vfs = this._terminal.fileSystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _initNewUserSession(user: User): User {
|
||||||
|
if(!this._passwd.includes(user))
|
||||||
|
this._terminal.throwExeption(`user not found under the name ${user.username}`, ExitCode.ERROR)
|
||||||
|
|
||||||
|
this._userInstances.push(user);
|
||||||
|
return user
|
||||||
}
|
}
|
||||||
|
|
||||||
private _appendNewResult(inode: number, output: any, cmd: string) {
|
private _appendNewResult(inode: number, output: any, cmd: string) {
|
||||||
@@ -85,6 +77,10 @@ export class Bash {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clearTerminal(): void {
|
||||||
|
this._terminal.clearTerminal();
|
||||||
|
}
|
||||||
|
|
||||||
getCwd(): number {
|
getCwd(): number {
|
||||||
return this.vfs.cwd;
|
return this.vfs.cwd;
|
||||||
}
|
}
|
||||||
@@ -105,11 +101,15 @@ export class Bash {
|
|||||||
return this._terminal.getTerminalWidth();
|
return this._terminal.getTerminalWidth();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getTerminalFontSize(): number {
|
||||||
|
return this._terminal.getFontSize();
|
||||||
|
}
|
||||||
|
|
||||||
hasSudoPerms(uid: number): boolean {
|
hasSudoPerms(uid: number): boolean {
|
||||||
return this._group[1].members.includes(uid);
|
return this._group[1].members.includes(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
executeCommand(commandName: string, args: CommandArgs): void {
|
async executeCommand(commandName: string, args: CommandArgs) {
|
||||||
let result: Result = { exitCode: ExitCode.ERROR, path: this.getCwd() };
|
let result: Result = { exitCode: ExitCode.ERROR, path: this.getCwd() };
|
||||||
const command = this._commands[commandName];
|
const command = this._commands[commandName];
|
||||||
if (!command) this.throwError(result);
|
if (!command) this.throwError(result);
|
||||||
@@ -124,7 +124,7 @@ export class Bash {
|
|||||||
|
|
||||||
let out: Result = command.method.call(this, args);
|
let out: Result = command.method.call(this, args);
|
||||||
console.log(out);
|
console.log(out);
|
||||||
this._appendNewResult(out.path, out.data?.data, this.user.history[0]);
|
this._appendNewResult(out.path, out.resultData?.data, this.user.history[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
throwError(result: Result): void {
|
throwError(result: Result): void {
|
||||||
@@ -136,14 +136,25 @@ export class Bash {
|
|||||||
}
|
}
|
||||||
|
|
||||||
userLogout() {
|
userLogout() {
|
||||||
this._instances.pop();
|
this._userInstances.pop();
|
||||||
if (this._instances.size() === 0) {
|
if (this._userInstances.size() === 0) {
|
||||||
//TODO: Implement system logout
|
//TODO: Implement system logout
|
||||||
} else {
|
} else {
|
||||||
//this.changeUser(this._instances.peek()!);
|
//this.changeUser(this._instances.peek()!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userLogin(username: string, passwd: string): ExitCode {
|
||||||
|
const user: User | undefined =
|
||||||
|
this._passwd.find((user) => user.username === username);
|
||||||
|
|
||||||
|
if(user && user.passwd === passwd) {
|
||||||
|
this._initNewUserSession(user);
|
||||||
|
return ExitCode.SUCCESS;
|
||||||
|
}
|
||||||
|
return ExitCode.ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
formatBytes(bytes: number, dPoint?: number, pow: 1024 | 1000 = 1024): string {
|
formatBytes(bytes: number, dPoint?: number, pow: 1024 | 1000 = 1024): string {
|
||||||
if (!+bytes) return '0';
|
if (!+bytes) return '0';
|
||||||
|
|
||||||
|
|||||||
86
src/lib/stores/bash/static.ts → src/lib/stores/bash/commandRegistry.ts
Executable file → Normal file
86
src/lib/stores/bash/static.ts → src/lib/stores/bash/commandRegistry.ts
Executable file → Normal file
@@ -1,82 +1,30 @@
|
|||||||
import { Bash, ExitCode, type Group, type User } from './bash';
|
import type { Bash, Result } from "./bash";
|
||||||
import { ls } from './commands/ls';
|
import { cd } from "./commands/cd";
|
||||||
import { cd } from './commands/cd';
|
import { clear } from "./commands/clear";
|
||||||
|
import { ls } from "./commands/ls";
|
||||||
|
|
||||||
export type ICommand = {
|
export type ICommand = {
|
||||||
method: (this: Bash, args: CommandArgs) => Result;
|
method: (this: Bash, args: CommandArgs) => Result;
|
||||||
flags: string[];
|
flags: string[];
|
||||||
help: string;
|
help: string;
|
||||||
root: boolean;
|
root: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CommandArgs = {
|
export type CommandArgs = {
|
||||||
flags: string[];
|
flags: string[];
|
||||||
args: string[];
|
args: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type resultData = {
|
export type CommandResultData = {
|
||||||
cmd: string; //the string that contains the shorthand for the command that was executed - used in a switch statement in parseResult
|
cmd: string; //the string that contains the shorthand for the command that was executed - used in a switch statement in parseResult
|
||||||
data: any; //the data that the commmand may have returned like TreeNodes[] from ls
|
data: any; //the data that the commmand may have returned like TreeNodes[] from ls
|
||||||
args?: CommandArgs;
|
args?: CommandArgs;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Result = {
|
|
||||||
exitCode: ExitCode;
|
|
||||||
path: number; //the inode of the place that the command was executed in
|
|
||||||
data?: resultData;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const GROUP: Group[] = [
|
|
||||||
{
|
|
||||||
groupname: 'sudo',
|
|
||||||
gid: 69,
|
|
||||||
members: [0, 1001]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
groupname: 'users',
|
|
||||||
gid: 984,
|
|
||||||
members: [1001, 1002]
|
|
||||||
}
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
export const PASSWD: User[] = [
|
|
||||||
{
|
|
||||||
username: 'root',
|
|
||||||
passwd: '123',
|
|
||||||
uid: 0,
|
|
||||||
gid: 0,
|
|
||||||
home: '/',
|
|
||||||
history: [] //TODO: Delete this and declare a new history array when logging the user in.
|
|
||||||
},
|
|
||||||
{
|
|
||||||
username: 'admin',
|
|
||||||
passwd: '456',
|
|
||||||
uid: 1000,
|
|
||||||
gid: 1000,
|
|
||||||
home: '/home/admin',
|
|
||||||
history: [] //TODO: Delete this and declare a new history array when logging the user in.
|
|
||||||
},
|
|
||||||
{
|
|
||||||
username: 'user',
|
|
||||||
passwd: '789',
|
|
||||||
uid: 1001,
|
|
||||||
gid: 1000,
|
|
||||||
home: '/home/user',
|
|
||||||
history: [] //TODO: Delete this and declare a new history array when logging the user in.
|
|
||||||
},
|
|
||||||
{
|
|
||||||
username: 'kamil',
|
|
||||||
passwd: '000',
|
|
||||||
uid: 1002,
|
|
||||||
gid: 1000,
|
|
||||||
home: '/home/kamil',
|
|
||||||
history: [] //TODO: Delete this and declare a new history array when logging the user in.
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
export const COMMANDS = {
|
export const COMMANDS = {
|
||||||
cd,
|
cd,
|
||||||
ls
|
ls,
|
||||||
|
clear
|
||||||
} as const satisfies Record<string, ICommand>;
|
} as const satisfies Record<string, ICommand>;
|
||||||
|
|
||||||
/* //export const commands {
|
/* //export const commands {
|
||||||
@@ -195,4 +143,4 @@ export const COMMANDS = {
|
|||||||
flags: [],
|
flags: [],
|
||||||
help: "",
|
help: "",
|
||||||
}
|
}
|
||||||
} */
|
} */
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { ExitCode, type Bash } from '../bash';
|
import type { Bash, Result } from '../bash';
|
||||||
|
import type { CommandArgs, ICommand } from '../commandRegistry';
|
||||||
import { Type, type TreeNode } from '../fs';
|
import { Type, type TreeNode } from '../fs';
|
||||||
import type { CommandArgs, ICommand, Result } from '../static';
|
import { ExitCode } from '../metadata';
|
||||||
|
|
||||||
export const cmd_cd = function (this: Bash, args: CommandArgs): Result {
|
export const cmd_cd = function (this: Bash, args: CommandArgs): Result {
|
||||||
let result: Result = { exitCode: ExitCode.ERROR, path: this.getCwd() };
|
let result: Result = { exitCode: ExitCode.ERROR, path: this.getCwd() };
|
||||||
|
|||||||
16
src/lib/stores/bash/commands/clear.ts
Normal file
16
src/lib/stores/bash/commands/clear.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { ExitCode, type Bash } from "../bash";
|
||||||
|
import type { CommandArgs, ICommand, Result } from "../static";
|
||||||
|
|
||||||
|
export const cmd_clear = function(this: Bash, args: CommandArgs): Result {
|
||||||
|
let result: Result = { exitCode: ExitCode.ERROR, path: this.getCwd() };
|
||||||
|
this.clearTerminal();
|
||||||
|
result.exitCode = ExitCode.SUCCESS;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clear: ICommand = {
|
||||||
|
method: cmd_clear,
|
||||||
|
flags: [] as string[],
|
||||||
|
help: 'PATH TO HELP.md',
|
||||||
|
root: false
|
||||||
|
};
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
import { Bash, ExitCode, type Permission, type TimeStamps } from '../bash';
|
import type { Bash, Result } from '../bash';
|
||||||
import { Type, type NodePerms, type TreeNode } from '../fs';
|
import type { CommandArgs, CommandResultData, ICommand } from '../commandRegistry';
|
||||||
|
import { Type, VirtualFS, type NodePerms, type TreeNode } from '../fs';
|
||||||
|
import { ExitCode, type Permission } from '../metadata';
|
||||||
import { Sort, SortNodeBy } from '../sort';
|
import { Sort, SortNodeBy } from '../sort';
|
||||||
import type { CommandArgs, ICommand, Result, resultData } from '../static';
|
|
||||||
|
|
||||||
type LsEntry = {
|
type LsEntry = {
|
||||||
inode: number | null;
|
inode: number | null;
|
||||||
@@ -18,7 +19,7 @@ const LsEntryUtils = {
|
|||||||
return Object.entries(entry)
|
return Object.entries(entry)
|
||||||
.filter(([_, value]) => value !== '')
|
.filter(([_, value]) => value !== '')
|
||||||
.map(([_, value]) => value)
|
.map(([_, value]) => value)
|
||||||
.join(' ');
|
.join(' ').trimStart();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -38,8 +39,9 @@ const months: readonly string[] = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
export const cmd_ls = function (this: Bash, args: CommandArgs): Result {
|
export const cmd_ls = function (this: Bash, args: CommandArgs): Result {
|
||||||
const resultData: resultData = { cmd: 'ls', data: null, args: args };
|
const Fs = this.getFs();
|
||||||
const result: Result = { exitCode: ExitCode.ERROR, path: this.getCwd(), data: resultData };
|
const resultData: CommandResultData = { cmd: 'ls', data: null, args: args };
|
||||||
|
const result: Result = { exitCode: ExitCode.ERROR, path: this.getCwd(), resultData: resultData };
|
||||||
const nodes: TreeNode[] = [];
|
const nodes: TreeNode[] = [];
|
||||||
|
|
||||||
//Check if any args contain the long flags with value and are valid flags inside the ls const
|
//Check if any args contain the long flags with value and are valid flags inside the ls const
|
||||||
@@ -55,10 +57,10 @@ export const cmd_ls = function (this: Bash, args: CommandArgs): Result {
|
|||||||
this.throwError(result); //No such flag/s
|
this.throwError(result); //No such flag/s
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.args.length === 0) nodes.push(this.getFs().getNodeByINode(this.getFs().cwd));
|
if (args.args.length === 0) nodes.push(Fs.getNodeByINode(Fs.cwd));
|
||||||
|
|
||||||
for (let i = 0; i < args.args.length; i++) {
|
for (let i = 0; i < args.args.length; i++) {
|
||||||
const node = this.getFs().resolvePath(args.args[i]);
|
const node = Fs.resolvePath(args.args[i]);
|
||||||
if (node === null) this.throwError(result); //no such path (i think this will never occur as backed methods have error cases implemented - which is wrong)
|
if (node === null) this.throwError(result); //no such path (i think this will never occur as backed methods have error cases implemented - which is wrong)
|
||||||
|
|
||||||
nodes.push(node);
|
nodes.push(node);
|
||||||
@@ -71,15 +73,19 @@ export const cmd_ls = function (this: Bash, args: CommandArgs): Result {
|
|||||||
|
|
||||||
function result_ls(this: Bash, data: any, args: CommandArgs): HTMLElement {
|
function result_ls(this: Bash, data: any, args: CommandArgs): HTMLElement {
|
||||||
const dummysonoerror: HTMLElement = document.createElement('div');
|
const dummysonoerror: HTMLElement = document.createElement('div');
|
||||||
|
const Fs: VirtualFS = this.getFs();
|
||||||
|
|
||||||
|
|
||||||
const flagInfo = checkFlags(args.flags);
|
const flagInfo = checkFlags(args.flags);
|
||||||
const nodes: TreeNode[] = data;
|
const nodes: TreeNode[] = data;
|
||||||
|
let nodeIndex: number = 0;
|
||||||
|
|
||||||
const f_a: boolean = flagInfo.has('a') || flagInfo.has('all');
|
const f_a: boolean = flagInfo.has('a') || flagInfo.has('all');
|
||||||
const f_A: boolean = flagInfo.has('A') || flagInfo.has('almost-all');
|
const f_A: boolean = flagInfo.has('A') || flagInfo.has('almost-all');
|
||||||
const f_G: boolean = flagInfo.has('G') || flagInfo.has('no-group');
|
const f_G: boolean = flagInfo.has('G') || flagInfo.has('no-group');
|
||||||
const f_h: boolean = flagInfo.has('h') || flagInfo.has('human-readable');
|
const f_h: boolean = flagInfo.has('h') || flagInfo.has('human-readable');
|
||||||
const f_r: boolean = flagInfo.has('r') || flagInfo.has('reverse');
|
const f_r: boolean = flagInfo.has('r') || flagInfo.has('reverse');
|
||||||
|
const f_R: boolean = flagInfo.has('R') || flagInfo.has('recursive');
|
||||||
const f_Q: boolean = flagInfo.has('Q') || flagInfo.has('quote-name');
|
const f_Q: boolean = flagInfo.has('Q') || flagInfo.has('quote-name');
|
||||||
const f_n: boolean = flagInfo.has('n') || flagInfo.has('numeric-uid-gid');
|
const f_n: boolean = flagInfo.has('n') || flagInfo.has('numeric-uid-gid');
|
||||||
const f_N: boolean = flagInfo.has('N') || flagInfo.has('literal');
|
const f_N: boolean = flagInfo.has('N') || flagInfo.has('literal');
|
||||||
@@ -96,138 +102,171 @@ function result_ls(this: Bash, data: any, args: CommandArgs): HTMLElement {
|
|||||||
const f_g: boolean = flagInfo.has('g');
|
const f_g: boolean = flagInfo.has('g');
|
||||||
const f_o: boolean = flagInfo.has('o');
|
const f_o: boolean = flagInfo.has('o');
|
||||||
|
|
||||||
let shouldShift: boolean = false
|
|
||||||
|
|
||||||
const valuedArgs = args.flags.filter((flag: string) =>
|
const valuedArgs = args.flags.filter((flag: string) =>
|
||||||
flag.includes('=') && ls.flags.includes(flag.split('=')[0]));
|
flag.includes('=') && ls.flags.includes(flag.split('=')[0]));
|
||||||
|
|
||||||
if (f_l || f_g || f_o) {
|
const w: HTMLElement = document.createElement('div');
|
||||||
const w: HTMLElement = document.createElement('div');
|
|
||||||
|
|
||||||
for (const node of nodes) {
|
if(f_R) {
|
||||||
const elem: HTMLElement = document.createElement('div');
|
const treeWalkResult: TreeNode[] = [];
|
||||||
const children: TreeNode[] = node.children.map((child) => this.getFs().getNodeByINode(child));
|
const treeWalkCallback = (node: TreeNode) => { treeWalkResult.push(node); }
|
||||||
|
|
||||||
|
for(const node of nodes) {
|
||||||
|
treeWalkResult.push(node);
|
||||||
|
Fs.recursiveTraversalPre(node, treeWalkCallback);
|
||||||
|
}
|
||||||
|
nodes.length = 0;
|
||||||
|
nodes.push(...treeWalkResult.filter((node) => node.type != Type.File));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const node of nodes) {
|
||||||
|
const elem: HTMLElement = document.createElement('div');
|
||||||
|
const childrenMap: TreeNode[] = node.children.map((child) => Fs.getNodeByINode(child));
|
||||||
|
|
||||||
|
const children: TreeNode[] = (f_a || f_A)
|
||||||
|
? childrenMap
|
||||||
|
: childrenMap.filter((child) => !child.name.startsWith('.'));
|
||||||
|
|
||||||
|
const timeArg = valuedArgs.find((flag) => flag.startsWith('time'));
|
||||||
|
const shouldNamesShift: boolean = children.some((child) => child.name.match(/\s/) !== null);
|
||||||
|
let timestamp: SortNodeBy.ATIME | SortNodeBy.CTIME | SortNodeBy.MTIME = SortNodeBy.MTIME;
|
||||||
|
|
||||||
|
if(f_a && !f_A) {
|
||||||
|
const current: TreeNode = node;
|
||||||
|
current.name = '.';
|
||||||
|
|
||||||
|
const parent: TreeNode = Fs.getNodeByINode(node.parent);
|
||||||
|
parent.name = '..';
|
||||||
|
|
||||||
|
children.unshift(current, parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(timeArg) {
|
||||||
|
let value: string = timeArg.split('=')[1];
|
||||||
|
if (value && isValidNodeTimestamp(value)) timestamp = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!f_U && !f_f) {
|
||||||
|
const sortArg = valuedArgs.find((flag) => flag.startsWith('sort'));
|
||||||
|
let sortBy: SortNodeBy = SortNodeBy.NAME;
|
||||||
|
|
||||||
|
if(f_t) sortBy = timestamp;
|
||||||
|
if(f_S) sortBy = SortNodeBy.SIZE;
|
||||||
|
if(f_X) sortBy = SortNodeBy.EXTENSION;
|
||||||
|
|
||||||
|
if(sortArg) {
|
||||||
|
let value = sortArg.split('=')[1];
|
||||||
|
if(value && isValidNodeSortMethod(value)) sortBy = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
Sort.nodeArraySort.call(this, children, f_r, sortBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (f_l || f_g || f_o) {
|
||||||
const rows: string[] = [];
|
const rows: string[] = [];
|
||||||
|
const maxSizeWidth = Math.max(
|
||||||
const timeArg = valuedArgs.find((flag) => flag.startsWith('time'));
|
...children.map((child) => child.size.toString().length));
|
||||||
let timestamp: SortNodeBy.ATIME | SortNodeBy.CTIME | SortNodeBy.MTIME = SortNodeBy.MTIME;
|
|
||||||
if(timeArg) {
|
|
||||||
let value: string = timeArg.split('=')[1];
|
|
||||||
if (value && isValidNodeTimestamp(value)) {
|
|
||||||
timestamp = value;
|
|
||||||
console.log(timestamp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!f_U && !f_f) {
|
|
||||||
const sortArg = valuedArgs.find((flag) => flag.startsWith('sort'));
|
|
||||||
let sortBy: SortNodeBy = SortNodeBy.NAME;
|
|
||||||
if(f_t) sortBy = timestamp;
|
|
||||||
if(f_S) sortBy = SortNodeBy.SIZE;
|
|
||||||
if(f_X) sortBy = SortNodeBy.EXTENSION;
|
|
||||||
if(sortArg) {
|
|
||||||
let value = sortArg.split('=')[1];
|
|
||||||
if(value && isValidNodeSortMethod(value)) {
|
|
||||||
sortBy = value;
|
|
||||||
console.log(sortBy, 'sortBy');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Sort.nodeArraySort.call(this, children, f_r, sortBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
const sizes = children.map((child) => child.size);
|
|
||||||
const maxSizeWidth = Math.max(...sizes.map((size) => size));
|
|
||||||
|
|
||||||
for (const child of children) {
|
for (const child of children) {
|
||||||
if (child.name.startsWith('.') && !(f_a || f_A)) continue;
|
const entry: LsEntry = {
|
||||||
|
|
||||||
const cols: LsEntry = {
|
|
||||||
inode: null,
|
inode: null,
|
||||||
perms: formatPermission(child),
|
perms: formatPermission(child),
|
||||||
children: formatChildren(child),
|
children: formatChildren(child),
|
||||||
owners: formatOwners.call(this, child, flagInfo),
|
owners: formatOwners.call(this, child, flagInfo),
|
||||||
size: formatSize.call(this, f_h, child, maxSizeWidth, f_si),
|
size: formatSize.call(this, f_h, child, maxSizeWidth, f_si),
|
||||||
modt: formatModtime(child, timestamp),
|
modt: formatModtime(child, timestamp),
|
||||||
name: formatName(child, flagInfo, shouldShift)
|
name: formatName(child, flagInfo, shouldNamesShift)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (f_i) cols.inode = child.inode;
|
if (f_i) entry.inode = child.inode;
|
||||||
|
|
||||||
rows.push(LsEntryUtils.toString(cols));
|
rows.push(LsEntryUtils.toString(entry));
|
||||||
}
|
|
||||||
|
|
||||||
if (f_a && !f_A) {
|
|
||||||
const current: LsEntry = {
|
|
||||||
inode: null,
|
|
||||||
perms: formatPermission(node),
|
|
||||||
children: formatChildren(node),
|
|
||||||
owners: formatOwners.call(this, node, flagInfo),
|
|
||||||
size: formatSize.call(this, f_h, node, maxSizeWidth, f_si),
|
|
||||||
modt: formatModtime(node, timestamp),
|
|
||||||
name: '.'
|
|
||||||
};
|
|
||||||
let parent: LsEntry = {
|
|
||||||
...current,
|
|
||||||
name: '..'
|
|
||||||
};
|
|
||||||
if (node.parent) {
|
|
||||||
const parentNode: TreeNode = this.getFs().getNodeByINode(node.parent);
|
|
||||||
parent = {
|
|
||||||
inode: null,
|
|
||||||
perms: formatPermission(parentNode),
|
|
||||||
children: formatChildren(parentNode),
|
|
||||||
owners: formatOwners.call(this, parentNode, flagInfo),
|
|
||||||
size: formatSize.call(this, f_h, parentNode, maxSizeWidth, f_si),
|
|
||||||
modt: formatModtime(parentNode, timestamp),
|
|
||||||
name: '..'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (f_i) {
|
|
||||||
current.inode = node.inode;
|
|
||||||
parent.inode = node.parent ? node.parent : node.inode;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (f_r) {
|
|
||||||
rows.push(LsEntryUtils.toString(parent), LsEntryUtils.toString(current));
|
|
||||||
} else {
|
|
||||||
rows.unshift(LsEntryUtils.toString(current), LsEntryUtils.toString(parent));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Calculate the total size of contents in the node
|
//TODO: Calculate the total size of contents in the node
|
||||||
rows.unshift('total ' + node.children.length.toString());
|
rows.unshift('total ' + node.children.length.toString());
|
||||||
|
|
||||||
if (nodes.length > 1) {
|
if (nodes.length > 1) {
|
||||||
const nodePath: string =
|
const nodePath: HTMLElement = document.createElement('p');
|
||||||
node.name === '/' ? '/:' : `${this.getFs().getPathByInode(node.inode).slice(1)}:`;
|
nodePath.innerText = `${Fs.getPathByInode(node.inode)}:`;
|
||||||
rows.unshift(nodePath);
|
w.appendChild(nodePath);
|
||||||
rows.push('\n');
|
if(nodeIndex +1 < nodes.length) elem.style.marginBottom = `${this.getTerminalFontSize() * 2}px`;
|
||||||
}
|
|
||||||
|
|
||||||
for(const row of rows) {
|
|
||||||
const name: string = row[row.length - 1];
|
|
||||||
if(!name.startsWith('"') || !name.startsWith("'"))
|
|
||||||
name.padStart(1, ' ');
|
|
||||||
else continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < rows.length; i++) {
|
for (let i = 0; i < rows.length; i++) {
|
||||||
const p: HTMLElement = document.createElement('p');
|
const p: HTMLElement = document.createElement('p');
|
||||||
|
|
||||||
|
|
||||||
p.innerText = rows[i];
|
p.innerText = rows[i];
|
||||||
|
|
||||||
elem.appendChild(p);
|
elem.appendChild(p);
|
||||||
}
|
}
|
||||||
|
|
||||||
w.appendChild(elem);
|
w.appendChild(elem);
|
||||||
}
|
}
|
||||||
return w;
|
else {
|
||||||
}
|
const maxWidth: number = Math.ceil((this.getTerminalWidth() / this.getTerminalFontSize()) * 0.8);
|
||||||
|
|
||||||
|
let columns: string[][] = [];
|
||||||
|
let colWidths: number[];
|
||||||
|
let lowBound: number = 1;
|
||||||
|
let highBound: number = children.length;
|
||||||
|
|
||||||
|
while(lowBound + 1 < highBound) {
|
||||||
|
const c = lowBound + (highBound - lowBound) / 2;
|
||||||
|
|
||||||
return dummysonoerror; //TEMP SO NO ERROR CUZ RETURNS HTMLElement EVERY TIME, DELETE LATER
|
columns = [];
|
||||||
|
colWidths = [];
|
||||||
|
let fileIndex = 0;
|
||||||
|
|
||||||
|
for(let i = 0; i < c; i++) {
|
||||||
|
if(fileIndex >= children.length) break;
|
||||||
|
columns[i] = [];
|
||||||
|
for(let j = 0; j < Math.ceil(children.length / c); j++) {
|
||||||
|
if(fileIndex >= children.length) break;
|
||||||
|
columns[i].push(children[fileIndex].name);
|
||||||
|
fileIndex++;
|
||||||
|
}
|
||||||
|
colWidths.push(Math.max(...columns[i].map((name) => name.length)));
|
||||||
|
}
|
||||||
|
|
||||||
|
const calcWidth: number = colWidths.reduce((prev, curr) => prev + curr) + ((c-1) * 2);
|
||||||
|
|
||||||
|
if(calcWidth < maxWidth) lowBound = c + 1;
|
||||||
|
else if(calcWidth > maxWidth) highBound = c -1
|
||||||
|
}
|
||||||
|
|
||||||
|
const wrapper: HTMLElement = document.createElement('div');
|
||||||
|
|
||||||
|
wrapper.style.display = 'flex';
|
||||||
|
wrapper.style.columnGap = `${this.getTerminalFontSize() * 2}px`;
|
||||||
|
wrapper.style.marginBottom = `${this.getTerminalFontSize() * 2}px`;
|
||||||
|
|
||||||
|
let fileIndex = 0;
|
||||||
|
|
||||||
|
for(let i = 0; i < lowBound; i++) {
|
||||||
|
if(fileIndex >= children.length) break;
|
||||||
|
const col: HTMLElement = document.createElement('div');
|
||||||
|
|
||||||
|
for(let j = 0; j < Math.ceil(children.length / lowBound); j++) {
|
||||||
|
if(fileIndex >= children.length) break;
|
||||||
|
|
||||||
|
const entry: HTMLElement = document.createElement('p');
|
||||||
|
|
||||||
|
entry.innerText = formatName(children[fileIndex], flagInfo, shouldNamesShift);
|
||||||
|
col.appendChild(entry);
|
||||||
|
fileIndex++;
|
||||||
|
}
|
||||||
|
wrapper.appendChild(col);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nodes.length > 1) {
|
||||||
|
const nodePath: HTMLElement = document.createElement('p');
|
||||||
|
nodePath.innerText = `${Fs.getPathByInode(node.inode)}`;
|
||||||
|
w.appendChild(nodePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
w.appendChild(wrapper);
|
||||||
|
}
|
||||||
|
nodeIndex++;
|
||||||
|
}
|
||||||
|
return w;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidNodeSortMethod(value: string): value is SortNodeBy {
|
function isValidNodeSortMethod(value: string): value is SortNodeBy {
|
||||||
@@ -321,19 +360,38 @@ function formatModtime(node: TreeNode, sortBy: SortNodeBy.ATIME | SortNodeBy.CTI
|
|||||||
}
|
}
|
||||||
|
|
||||||
function formatName(node: TreeNode, flag: any, shouldShift: boolean) {
|
function formatName(node: TreeNode, flag: any, shouldShift: boolean) {
|
||||||
let name: string;
|
let name: string = node.name;
|
||||||
const char: string = flag.has('Q') ? '"' : "'";
|
const char: string = flag.has('Q') ? '"' : "'";
|
||||||
|
|
||||||
if (/\s/.test(node.name)) {
|
if (/\s/.test(node.name)) {
|
||||||
name = `${char}${node.name}${char}`
|
name = `${char}${name}${char}`
|
||||||
shouldShift = true;
|
|
||||||
} else {
|
} else {
|
||||||
name = `${node.name}`;
|
//Shift non quoted names 1 char right to align if any names in group have a quote
|
||||||
|
name = `${shouldShift ? ' ' : ''}${node.name}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return flag.has('p') && node.type === Type.Directory ? `${name}/` : name;
|
return flag.has('p') && node.type === Type.Directory ? `${name}/` : name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* function formatOutputShort(node: TreeNode, flag: any) {
|
||||||
|
let output: string = node.name;
|
||||||
|
const char: string = flag.has('Q') ? '"' : "'";
|
||||||
|
|
||||||
|
if (/\s/.test(node.name)) {
|
||||||
|
if(flag.has('i')) {
|
||||||
|
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
output = `${char}${node.name}${char}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(flag.has('i')) {
|
||||||
|
output = `${node.inode} ${output}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return flag.has('p') && node.type === Type.Directory ? ` ${output}/ ` : output;
|
||||||
|
} */
|
||||||
|
|
||||||
const checkFlags = (pFlags: string[]) => {
|
const checkFlags = (pFlags: string[]) => {
|
||||||
const flagSet = new Set(pFlags);
|
const flagSet = new Set(pFlags);
|
||||||
|
|
||||||
@@ -358,6 +416,7 @@ export const ls: ICommand = {
|
|||||||
't',
|
't',
|
||||||
'S',
|
'S',
|
||||||
'r',
|
'r',
|
||||||
|
'R',
|
||||||
'Q',
|
'Q',
|
||||||
'p',
|
'p',
|
||||||
'o',
|
'o',
|
||||||
@@ -374,6 +433,7 @@ export const ls: ICommand = {
|
|||||||
'no-group',
|
'no-group',
|
||||||
'human-readable',
|
'human-readable',
|
||||||
'reverse',
|
'reverse',
|
||||||
|
'recursive',
|
||||||
'quote-name',
|
'quote-name',
|
||||||
'indicator-style',
|
'indicator-style',
|
||||||
'literal',
|
'literal',
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import type { ICommand } from "../static"
|
|
||||||
import { cmd_ls } from "./ls"
|
|
||||||
|
|
||||||
type LsEntry
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class ls {
|
|
||||||
public const ls: ICommand = {
|
|
||||||
method: ls.cmd_ls
|
|
||||||
}
|
|
||||||
}
|
|
||||||
18
src/lib/stores/bash/etc/groupData.ts
Normal file
18
src/lib/stores/bash/etc/groupData.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
export type Group = {
|
||||||
|
groupname: string;
|
||||||
|
gid: number; // Primary group 'Users' 1000 - Others - 1000+ root - 0
|
||||||
|
members: number[]; //TODO: Make a formated type UID
|
||||||
|
};
|
||||||
|
|
||||||
|
export const GROUP: Group[] = [
|
||||||
|
{
|
||||||
|
groupname: 'sudo',
|
||||||
|
gid: 69,
|
||||||
|
members: [0, 1001]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
groupname: 'users',
|
||||||
|
gid: 984,
|
||||||
|
members: [1001, 1002]
|
||||||
|
}
|
||||||
|
] as const;
|
||||||
45
src/lib/stores/bash/etc/userData.ts
Normal file
45
src/lib/stores/bash/etc/userData.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
export type User = {
|
||||||
|
username: string;
|
||||||
|
passwd: string; //HASHED PASSWORD //TODO: Make a formated type
|
||||||
|
readonly uid: number; // Normal user 1000+ System user 1-999 root - 0 //TODO: Make a formated type
|
||||||
|
readonly gid: number; // Primary group | 'Users' 1000 - Others - 1000+ root - 0 //TODO: Make a formated type
|
||||||
|
home: string; //TODO: Make a formated type
|
||||||
|
history: string[];
|
||||||
|
cwd?: number; //TODO: Make a formated type
|
||||||
|
pwd?: number; //TODO: Make a formated type
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PASSWD: User[] = [
|
||||||
|
{
|
||||||
|
username: 'root',
|
||||||
|
passwd: '123',
|
||||||
|
uid: 0,
|
||||||
|
gid: 0,
|
||||||
|
home: '/',
|
||||||
|
history: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
username: 'admin',
|
||||||
|
passwd: '456',
|
||||||
|
uid: 1000,
|
||||||
|
gid: 1000,
|
||||||
|
home: '/home/admin',
|
||||||
|
history: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
username: 'user',
|
||||||
|
passwd: '789',
|
||||||
|
uid: 1001,
|
||||||
|
gid: 1000,
|
||||||
|
home: '/home/user',
|
||||||
|
history: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
username: 'kamil',
|
||||||
|
passwd: '000',
|
||||||
|
uid: 1002,
|
||||||
|
gid: 1000,
|
||||||
|
home: '/home/kamil',
|
||||||
|
history: []
|
||||||
|
}
|
||||||
|
];
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import type { Permission, TimeStamps, User } from './bash';
|
import type { Bash } from "./bash";
|
||||||
|
import type { Permission, TimeStamps } from "./metadata";
|
||||||
|
|
||||||
export enum Type {
|
export enum Type {
|
||||||
Directory = 16384,
|
Directory = 16384,
|
||||||
@@ -14,12 +15,12 @@ export type NodePerms = {
|
|||||||
|
|
||||||
export type FsInitArgs = {
|
export type FsInitArgs = {
|
||||||
fs: any;
|
fs: any;
|
||||||
user: User;
|
bash: Bash;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TreeNode = {
|
export type TreeNode = {
|
||||||
inode: number;
|
inode: number;
|
||||||
parent?: number;
|
parent: number;
|
||||||
name: string;
|
name: string;
|
||||||
type: Type;
|
type: Type;
|
||||||
size: number; //Size in Bytes
|
size: number; //Size in Bytes
|
||||||
@@ -37,6 +38,7 @@ export type TreeNode = {
|
|||||||
export class VirtualFS {
|
export class VirtualFS {
|
||||||
private FsTable: Map<number, TreeNode>;
|
private FsTable: Map<number, TreeNode>;
|
||||||
private rootINode: number;
|
private rootINode: number;
|
||||||
|
private _Bash: Bash;
|
||||||
|
|
||||||
home: number;
|
home: number;
|
||||||
cwd: number;
|
cwd: number;
|
||||||
@@ -45,9 +47,10 @@ export class VirtualFS {
|
|||||||
constructor(args: FsInitArgs) {
|
constructor(args: FsInitArgs) {
|
||||||
this.FsTable = args.fs;
|
this.FsTable = args.fs;
|
||||||
this.rootINode = 1;
|
this.rootINode = 1;
|
||||||
this.home = this._pathStringToINode(args.user.home);
|
this._Bash = args.bash;
|
||||||
this.cwd = args.user.cwd ? args.user.cwd : this.home;
|
this.home = this._pathStringToINode(args.bash.getUser().home);
|
||||||
this.pwd = args.user.pwd ? args.user.pwd : this.cwd;
|
this.cwd = args.bash.getUser().cwd ? args.bash.getUser().cwd! : this.home;
|
||||||
|
this.pwd = args.bash.getUser().pwd ? args.bash.getUser().pwd! : this.cwd;
|
||||||
|
|
||||||
console.log(this.home);
|
console.log(this.home);
|
||||||
console.log(this.cwd);
|
console.log(this.cwd);
|
||||||
@@ -110,11 +113,11 @@ export class VirtualFS {
|
|||||||
return typeof path === 'string' && path.startsWith('/');
|
return typeof path === 'string' && path.startsWith('/');
|
||||||
};
|
};
|
||||||
|
|
||||||
getPathByInode(inode: number): string {
|
public getPathByInode(inode: number): string {
|
||||||
return this._iNodeToPathString(inode);
|
return this._iNodeToPathString(inode);
|
||||||
}
|
}
|
||||||
|
|
||||||
formatPath(path: string): string {
|
public formatPath(path: string): string {
|
||||||
console.log(path, 'formatPath');
|
console.log(path, 'formatPath');
|
||||||
const prefix = this._iNodeToPathString(this.home);
|
const prefix = this._iNodeToPathString(this.home);
|
||||||
|
|
||||||
@@ -123,7 +126,7 @@ export class VirtualFS {
|
|||||||
} else return path;
|
} else return path;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolvePath(path: string): TreeNode {
|
public resolvePath(path: string): TreeNode {
|
||||||
if (path === '/') return this.getNodeByINode(this.rootINode);
|
if (path === '/') return this.getNodeByINode(this.rootINode);
|
||||||
let parsedPath: string = path;
|
let parsedPath: string = path;
|
||||||
|
|
||||||
@@ -144,22 +147,32 @@ export class VirtualFS {
|
|||||||
return Node;
|
return Node;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNodeByINode(inode: number): TreeNode {
|
public getNodeByINode(inode: number): TreeNode {
|
||||||
const node: TreeNode | undefined = this.FsTable.get(inode);
|
const node: TreeNode | undefined = this.FsTable.get(inode);
|
||||||
if (!node) throw new Error('Could not get the node, no such i node exists');
|
if (!node) throw new Error(`Could not get the node, no such inode exists - ${inode}`);
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* private _getPathToNode(node: TreeNode): string[] {
|
public recursiveTraversalPre(node: TreeNode, callback: (param: TreeNode) => void, childIndex?: number[], depthIndex?: number): void {
|
||||||
const path: string[] = [];
|
if(!depthIndex) depthIndex = 0;
|
||||||
let current = node;
|
if(!childIndex) childIndex = [0];
|
||||||
path.push(node.name);
|
|
||||||
|
|
||||||
while (current.parent) {
|
if(node.type != Type.File && node.children[childIndex[depthIndex]]) {
|
||||||
current = current.parent;
|
node = this.getNodeByINode(node.children[childIndex[depthIndex]]);
|
||||||
path.unshift(current.name);
|
depthIndex++;
|
||||||
|
if(!childIndex[depthIndex]) childIndex[depthIndex] = 0;
|
||||||
|
|
||||||
|
callback(node);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
node = this.getNodeByINode(node.parent);
|
||||||
|
childIndex[depthIndex] = 0;
|
||||||
|
depthIndex--;
|
||||||
|
childIndex[depthIndex]++;
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
if(depthIndex < 0) return;
|
||||||
} */
|
|
||||||
|
this.recursiveTraversalPre(node, callback, childIndex, depthIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
17
src/lib/stores/bash/metadata.ts
Normal file
17
src/lib/stores/bash/metadata.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
export type TimeStamps = {
|
||||||
|
modified: Date;
|
||||||
|
changed: Date;
|
||||||
|
accessed: Date;
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Finish this
|
||||||
|
export enum ExitCode {
|
||||||
|
SUCCESS = 0,
|
||||||
|
ERROR = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Permission = {
|
||||||
|
r: boolean;
|
||||||
|
w: boolean;
|
||||||
|
x: boolean;
|
||||||
|
};
|
||||||
78
src/lib/stores/bash/posix.ts
Normal file
78
src/lib/stores/bash/posix.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
export class Posix {
|
||||||
|
private static readonly CharTable = {
|
||||||
|
// Space and punctuation (32-47)
|
||||||
|
' ': 32,
|
||||||
|
'!': 33,
|
||||||
|
'"': 34,
|
||||||
|
'#': 35,
|
||||||
|
'$': 36,
|
||||||
|
'%': 37,
|
||||||
|
'&': 38,
|
||||||
|
"'": 39,
|
||||||
|
'(': 40,
|
||||||
|
')': 41,
|
||||||
|
'*': 42,
|
||||||
|
'+': 43,
|
||||||
|
',': 44,
|
||||||
|
'-': 45,
|
||||||
|
'.': 46,
|
||||||
|
'/': 47,
|
||||||
|
|
||||||
|
// Numbers 0-9 (48-57)
|
||||||
|
'0': 48, '1': 49, '2': 50, '3': 51, '4': 52,
|
||||||
|
'5': 53, '6': 54, '7': 55, '8': 56, '9': 57,
|
||||||
|
|
||||||
|
// More punctuation (58-64)
|
||||||
|
':': 58,
|
||||||
|
';': 59,
|
||||||
|
'<': 60,
|
||||||
|
'=': 61,
|
||||||
|
'>': 62,
|
||||||
|
'?': 63,
|
||||||
|
'@': 64,
|
||||||
|
|
||||||
|
// LETTERS - INTERLEAVED AaBbCc... (65-116)
|
||||||
|
'A': 65, 'a': 66,
|
||||||
|
'B': 67, 'b': 68,
|
||||||
|
'C': 69, 'c': 70,
|
||||||
|
'D': 71, 'd': 72,
|
||||||
|
'E': 73, 'e': 74,
|
||||||
|
'F': 75, 'f': 76,
|
||||||
|
'G': 77, 'g': 78,
|
||||||
|
'H': 79, 'h': 80,
|
||||||
|
'I': 81, 'i': 82,
|
||||||
|
'J': 83, 'j': 84,
|
||||||
|
'K': 85, 'k': 86,
|
||||||
|
'L': 87, 'l': 88,
|
||||||
|
'M': 89, 'm': 90,
|
||||||
|
'N': 91, 'n': 92,
|
||||||
|
'O': 93, 'o': 94,
|
||||||
|
'P': 95, 'p': 96,
|
||||||
|
'Q': 97, 'q': 98,
|
||||||
|
'R': 99, 'r': 100,
|
||||||
|
'S': 101, 's': 102,
|
||||||
|
'T': 103, 't': 104,
|
||||||
|
'U': 105, 'u': 106,
|
||||||
|
'V': 107, 'v': 108,
|
||||||
|
'W': 109, 'w': 110,
|
||||||
|
'X': 111, 'x': 112,
|
||||||
|
'Y': 113, 'y': 114,
|
||||||
|
'Z': 115, 'z': 116,
|
||||||
|
|
||||||
|
// Specials after letters (now 117-126)
|
||||||
|
'[': 117,
|
||||||
|
'\\': 118,
|
||||||
|
']': 119,
|
||||||
|
'^': 120,
|
||||||
|
'_': 121,
|
||||||
|
'`': 122,
|
||||||
|
'{': 123,
|
||||||
|
'|': 124,
|
||||||
|
'}': 125,
|
||||||
|
'~': 126
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
public static GetCharWeight(char: string): number {
|
||||||
|
return this.CharTable[char as keyof typeof this.CharTable];
|
||||||
|
}
|
||||||
|
}
|
||||||
7
src/lib/stores/bash/search.ts
Normal file
7
src/lib/stores/bash/search.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
import type { Bash } from "./bash";
|
||||||
|
import type { TreeNode } from "./fs";
|
||||||
|
|
||||||
|
export class Search {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import type { TreeNode } from './fs';
|
import type { TreeNode } from './fs';
|
||||||
import type { Bash } from './bash';
|
import type { Bash } from './bash';
|
||||||
|
import { Posix } from './posix';
|
||||||
|
|
||||||
export enum SortNodeBy {
|
export enum SortNodeBy {
|
||||||
NAME = 'name',
|
NAME = 'name',
|
||||||
@@ -19,7 +20,7 @@ export class Sort {
|
|||||||
reverse: boolean = false,
|
reverse: boolean = false,
|
||||||
sortBy: SortNodeBy = SortNodeBy.NAME
|
sortBy: SortNodeBy = SortNodeBy.NAME
|
||||||
): TreeNode[] {
|
): TreeNode[] {
|
||||||
if (nodes.length === 0) throw new Error('Tried to sort an empty node array!');
|
if (nodes.length === 0) {console.warn('Tried to sort an empty node array!'); return [];}
|
||||||
const parsedNodes: TreeNode[] = [];
|
const parsedNodes: TreeNode[] = [];
|
||||||
|
|
||||||
if (typeof nodes[0] === 'number') {
|
if (typeof nodes[0] === 'number') {
|
||||||
@@ -85,17 +86,19 @@ export class Sort {
|
|||||||
): number {
|
): number {
|
||||||
switch (sortBy) {
|
switch (sortBy) {
|
||||||
case SortNodeBy.NAME: {
|
case SortNodeBy.NAME: {
|
||||||
const minLength = Math.min(a.name.length, b.name.length);
|
const nameA: string = a.name.startsWith('.') ? a.name.slice(1) : a.name;
|
||||||
|
const nameB: string = b.name.startsWith('.') ? b.name.slice(1) : b.name;
|
||||||
|
const minLength = Math.min(nameA.length, nameB.length);
|
||||||
|
|
||||||
for (let i = 0; i < minLength; i++) {
|
for (let i = 0; i < minLength; i++) {
|
||||||
const charCodeA = a.name.charCodeAt(i);
|
const charCodeA = Posix.GetCharWeight(nameA[i]);
|
||||||
const charCodeB = b.name.charCodeAt(i);
|
const charCodeB = Posix.GetCharWeight(nameB[i]);
|
||||||
|
|
||||||
if (charCodeA !== charCodeB) {
|
if (charCodeA !== charCodeB) {
|
||||||
return reverse ? charCodeB - charCodeA : charCodeA - charCodeB;
|
return reverse ? charCodeB - charCodeA : charCodeA - charCodeB;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return reverse ? b.name.length - a.name.length : a.name.length - b.name.length;
|
return reverse ? nameB.length - nameA.length : nameA.length - nameB.length;
|
||||||
}
|
}
|
||||||
case SortNodeBy.MTIME:
|
case SortNodeBy.MTIME:
|
||||||
case SortNodeBy.ATIME:
|
case SortNodeBy.ATIME:
|
||||||
|
|||||||
@@ -4,10 +4,13 @@ import deFlag from '$lib/assets/deFlag.svg';
|
|||||||
import frFlag from '$lib/assets/frFlag.svg';
|
import frFlag from '$lib/assets/frFlag.svg';
|
||||||
import jaFlag from '$lib/assets/jaFlag.svg';
|
import jaFlag from '$lib/assets/jaFlag.svg';
|
||||||
|
|
||||||
export const langs = {
|
import { writable } from 'svelte/store';
|
||||||
pl: {},
|
|
||||||
en: {},
|
function getInitalLocale(): string {
|
||||||
de: {},
|
if (typeof navigator === 'undefined') return 'en-US';
|
||||||
ja: {},
|
|
||||||
fr: {}
|
const sysPrefLocale = navigator.language
|
||||||
};
|
return sysPrefLocale
|
||||||
|
}
|
||||||
|
|
||||||
|
export const locale = writable(getInitalLocale());
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import type { User } from '../bash/bash';
|
import type { User } from '../bash/etc/userData';
|
||||||
import type { TreeNode } from '../bash/fs';
|
import type { TreeNode } from '../bash/fs';
|
||||||
import { Terminal, type TermInitArgs } from './terminal';
|
import { type ILocale } from './localeRegistry';
|
||||||
|
import { Terminal, type PageCallbacks, type TermInitArgs } from './terminal';
|
||||||
|
|
||||||
let initializing = $state(true);
|
let initializing = $state(true);
|
||||||
|
|
||||||
@@ -8,7 +9,7 @@ export function isInitializing(): boolean {
|
|||||||
return initializing;
|
return initializing;
|
||||||
}
|
}
|
||||||
|
|
||||||
function jsonToNodeTable(data: any, parent?: number): Map<number, TreeNode> {
|
function jsonToNodeTable(data: any): Map<number, TreeNode> {
|
||||||
const FsTable: Map<number, TreeNode> = new Map<number, TreeNode>();
|
const FsTable: Map<number, TreeNode> = new Map<number, TreeNode>();
|
||||||
const entryList = Object.entries(data);
|
const entryList = Object.entries(data);
|
||||||
|
|
||||||
@@ -99,16 +100,34 @@ async function fetchFileSystem(path: string): Promise<any> {
|
|||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initTerminal(user: User, callbackInit: any): Promise<Terminal> {
|
async function fetchLocale(id: string): Promise<any> {
|
||||||
|
const response = await fetch(`/src/lib/assets/locales/terminal/${id}.json`);
|
||||||
|
if(!response.ok) throw new Error('Failed to fetch the chosen locale');
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initTerminal(
|
||||||
|
user: {username: string, password: string},
|
||||||
|
callbackInit: PageCallbacks,
|
||||||
|
localeId: string
|
||||||
|
): Promise<Terminal> {
|
||||||
try {
|
try {
|
||||||
const sig = await fetchFsSignature('/src/lib/assets/fs/signature');
|
const sig = await fetchFsSignature('/src/lib/assets/fs/signature');
|
||||||
const fsJson = await fetchFsJson(sig);
|
const fsJson = await fetchFsJson(sig);
|
||||||
const fs: Map<number, TreeNode> = jsonToNodeTable(fsJson);
|
const fs: Map<number, TreeNode> = jsonToNodeTable(fsJson);
|
||||||
|
const locale: ILocale = {
|
||||||
|
id: localeId,
|
||||||
|
keys: fetchLocale(localeId)
|
||||||
|
};
|
||||||
|
|
||||||
const args: TermInitArgs = {
|
const args: TermInitArgs = {
|
||||||
|
fs,
|
||||||
|
locale,
|
||||||
bash: {
|
bash: {
|
||||||
user,
|
user,
|
||||||
fs
|
instanceId: 0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
13
src/lib/stores/terminal/localeRegistry.ts
Normal file
13
src/lib/stores/terminal/localeRegistry.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export interface ILocale {
|
||||||
|
id: string;
|
||||||
|
keys?: {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const en: ILocale = {
|
||||||
|
id: 'en_US',
|
||||||
|
keys: {}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const LOCALES = {
|
||||||
|
en
|
||||||
|
} as const satisfies Record<string, ILocale>;
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { mount, unmount } from 'svelte';
|
import { mount, unmount } from 'svelte';
|
||||||
import Output from '../../../modules/terminal/Output.svelte';
|
import Output from '../../../../modules/terminal/Output.svelte';
|
||||||
import { isInitializing } from './init.svelte';
|
import { isInitializing } from '../init.svelte';
|
||||||
import type { PrintData } from './terminal';
|
import type { PrintData } from '../terminal';
|
||||||
|
|
||||||
interface OutputProps {
|
interface OutputProps {
|
||||||
path: string;
|
path: string;
|
||||||
@@ -23,6 +23,8 @@ function appendOutput(container: HTMLElement, props: OutputProps): Output | unde
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function print(e: HTMLElement, data: PrintData): void {
|
export function print(e: HTMLElement, data: PrintData): void {
|
||||||
|
if(data.cmd == 'clear') return;
|
||||||
|
|
||||||
if (isInitializing()) {
|
if (isInitializing()) {
|
||||||
console.error('Terminal is initializing! Skipping Print');
|
console.error('Terminal is initializing! Skipping Print');
|
||||||
return;
|
return;
|
||||||
@@ -35,7 +37,7 @@ export function print(e: HTMLElement, data: PrintData): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function clear(): void {
|
export function clear(): void {
|
||||||
for (const n of outputInstances) {
|
for(const instance of outputInstances) {
|
||||||
unmount(n);
|
unmount(instance, {});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
4
src/lib/stores/terminal/stdio/keystrokeRegistry.ts
Normal file
4
src/lib/stores/terminal/stdio/keystrokeRegistry.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export interface KeyStroke {
|
||||||
|
keys: KeyboardEvent[];
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,12 +1,16 @@
|
|||||||
import { Bash, ExitCode, type BashInitArgs, type User } from '../bash/bash';
|
import { Bash, type BashInitArgs } from '../bash/bash';
|
||||||
import type { VirtualFS } from '../bash/fs';
|
import type { CommandArgs } from '../bash/commandRegistry';
|
||||||
import type { CommandArgs } from '../bash/static';
|
import type { User } from '../bash/etc/userData';
|
||||||
import { Char } from '../char';
|
import { VirtualFS, type TreeNode } from '../bash/fs';
|
||||||
|
import type { ExitCode } from '../bash/metadata';
|
||||||
|
import type { ILocale } from './localeRegistry';
|
||||||
|
|
||||||
export type TerminalMode = {};
|
export type TerminalMode = {};
|
||||||
|
|
||||||
export type TermInitArgs = {
|
export type TermInitArgs = {
|
||||||
|
fs: Map<number, TreeNode>;
|
||||||
bash: BashInitArgs;
|
bash: BashInitArgs;
|
||||||
|
locale: ILocale
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ParsedInput = {
|
export type ParsedInput = {
|
||||||
@@ -22,16 +26,34 @@ export type PrintData = {
|
|||||||
|
|
||||||
export type PageCallbacks = {
|
export type PageCallbacks = {
|
||||||
print: (data: PrintData) => void;
|
print: (data: PrintData) => void;
|
||||||
|
clear: () => void;
|
||||||
getWidth: () => number;
|
getWidth: () => number;
|
||||||
|
getFontSize: () => number;
|
||||||
};
|
};
|
||||||
|
|
||||||
export class Terminal {
|
export class Terminal {
|
||||||
private bash: Bash;
|
private _bashInstanceId: number;
|
||||||
private callbacks: Partial<PageCallbacks> = {};
|
private _bashInstances: Bash[];
|
||||||
|
private _callbacks: Partial<PageCallbacks> = {};
|
||||||
|
public fileSystem: VirtualFS;
|
||||||
|
public locale: ILocale;
|
||||||
|
|
||||||
constructor(args: TermInitArgs) {
|
constructor(args: TermInitArgs) {
|
||||||
args.bash.stdio = this;
|
args.bash.io = this;
|
||||||
this.bash = new Bash(args.bash);
|
this._bashInstanceId = 0;
|
||||||
|
this._bashInstances = [];
|
||||||
|
|
||||||
|
this._initializeBashInstance(args.bash);
|
||||||
|
|
||||||
|
this.fileSystem = new VirtualFS({ fs: args.fs, bash: this._bashInstances[this._bashInstanceId] });
|
||||||
|
this.locale = args.locale;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private _initializeBashInstance(init: BashInitArgs) {
|
||||||
|
const instance: Bash = new Bash(init);
|
||||||
|
this._bashInstances.push(instance);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private _parseInput(input: string): ParsedInput {
|
private _parseInput(input: string): ParsedInput {
|
||||||
@@ -87,41 +109,50 @@ export class Terminal {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throwExeption(message: string, exitCode: ExitCode) {
|
||||||
|
console.error(message, exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
executeCommand(input: string): void {
|
executeCommand(input: string): void {
|
||||||
this.bash.updateHistory(input);
|
this._bashInstances[this._bashInstanceId].updateHistory(input);
|
||||||
const parsed: ParsedInput = this._parseInput(input);
|
const parsed: ParsedInput = this._parseInput(input);
|
||||||
console.log(parsed, 'executeCommand output');
|
console.log(parsed, 'executeCommand output');
|
||||||
this.bash.executeCommand(parsed.command, parsed.args);
|
this._bashInstances[this._bashInstanceId].executeCommand(parsed.command, parsed.args);
|
||||||
}
|
}
|
||||||
|
|
||||||
registerCallbacks(callbacks: PageCallbacks): void {
|
registerCallbacks(callbacks: PageCallbacks): void {
|
||||||
this.callbacks = callbacks;
|
this._callbacks = callbacks;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearTerminal() {
|
||||||
|
this._callbacks.clear?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
getUser(): User {
|
getUser(): User {
|
||||||
return this.bash.getUser();
|
return this._bashInstances[this._bashInstanceId].getUser();
|
||||||
}
|
}
|
||||||
|
|
||||||
getTerminalWidth(): number {
|
getTerminalWidth(): number {
|
||||||
const width = this.callbacks.getWidth?.();
|
const width = this._callbacks.getWidth?.();
|
||||||
if(!width) { throw new Error('somehow width is undefined still after all the checks'); }
|
if(!width) { throw new Error('somehow width is undefined still after all the checks'); }
|
||||||
|
|
||||||
console.log(width);
|
|
||||||
return width;
|
return width;
|
||||||
}
|
}
|
||||||
|
|
||||||
getCwd(): string {
|
getFontSize(): number {
|
||||||
const fs: VirtualFS = this.bash.getFs();
|
const size = this._callbacks.getFontSize?.();
|
||||||
console.log(fs.getPathByInode(this.bash.getCwd()));
|
if(!size) { throw new Error('somehow font size is undefined still after all the checks'); }
|
||||||
return fs.formatPath(fs.getPathByInode(this.bash.getCwd()));
|
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: Later reimplement the backend helper methods
|
getCwd(): string {
|
||||||
/* userLogin(username: string, passwd: string): ExitCode {
|
const fs: VirtualFS = this._bashInstances[this._bashInstanceId].getFs();
|
||||||
return this.bash.userLogin(username, passwd);
|
console.log(fs.getPathByInode(this._bashInstances[this._bashInstanceId].getCwd()));
|
||||||
} */
|
return fs.formatPath(fs.getPathByInode(this._bashInstances[this._bashInstanceId].getCwd()));
|
||||||
|
}
|
||||||
|
|
||||||
PrintOutput(data: PrintData) {
|
PrintOutput(data: PrintData) {
|
||||||
this.callbacks.print?.(data);
|
this._callbacks.print?.(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Languages, Settings2, SunMoon } from '@lucide/svelte';
|
import { Languages, Settings2, SunMoon } from '@lucide/svelte';
|
||||||
import { theme } from '$lib/stores/theme';
|
import { theme } from '$lib/stores/theme';
|
||||||
|
import { locale } from '$lib/stores/lang';
|
||||||
import plFlag from '$lib/assets/plFlag.svg';
|
import plFlag from '$lib/assets/plFlag.svg';
|
||||||
import enFlag from '$lib/assets/enFlag.svg';
|
import enFlag from '$lib/assets/enFlag.svg';
|
||||||
import deFlag from '$lib/assets/deFlag.svg';
|
import deFlag from '$lib/assets/deFlag.svg';
|
||||||
@@ -18,6 +19,10 @@
|
|||||||
const langsMenu = window.document.getElementById('langs-menu');
|
const langsMenu = window.document.getElementById('langs-menu');
|
||||||
langsMenu?.classList.toggle('hide');
|
langsMenu?.classList.toggle('hide');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setLang(id: string) {
|
||||||
|
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -50,19 +55,21 @@
|
|||||||
<label
|
<label
|
||||||
class="border-primary-dark duration-100 hover:ml-1 hover:pl-1 has-checked:border-l-3 has-checked:hover:m-0 has-checked:hover:p-0 light:border-primary-light"
|
class="border-primary-dark duration-100 hover:ml-1 hover:pl-1 has-checked:border-l-3 has-checked:hover:m-0 has-checked:hover:p-0 light:border-primary-light"
|
||||||
>
|
>
|
||||||
<input name="langs" type="radio" class="lang hidden" id="pl" autocomplete="off" />
|
<input name="langs" type="radio" class="lang hidden" id="pl-PL" autocomplete="off" onclick={() => {console.log($locale)}}/>
|
||||||
<img class="flags mx-2" src={plFlag} alt="PL" height="26" width="26" />
|
<img class="flags mx-2" src={plFlag} alt="PL" height="26" width="26" />
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label
|
||||||
class="border-primary-dark duration-100 hover:ml-1 hover:pl-1 has-checked:border-l-3 has-checked:hover:m-0 has-checked:hover:p-0 light:border-primary-light"
|
class="border-primary-dark duration-100 hover:ml-1 hover:pl-1 has-checked:border-l-3 has-checked:hover:m-0 has-checked:hover:p-0 light:border-primary-light"
|
||||||
>
|
>
|
||||||
<input name="langs" type="radio" class="lang hidden" id="en" autocomplete="off" />
|
<input name="langs" type="radio" class="lang hidden" id="en-US" autocomplete="off" onclick={(id) => {
|
||||||
|
console.log(id);
|
||||||
|
}}/>
|
||||||
<img class="flags mx-2" src={enFlag} alt="EN" height="26" width="26" />
|
<img class="flags mx-2" src={enFlag} alt="EN" height="26" width="26" />
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label
|
||||||
class="border-primary-dark duration-100 hover:ml-1 hover:pl-1 has-checked:border-l-3 has-checked:hover:m-0 has-checked:hover:p-0 light:border-primary-light"
|
class="border-primary-dark duration-100 hover:ml-1 hover:pl-1 has-checked:border-l-3 has-checked:hover:m-0 has-checked:hover:p-0 light:border-primary-light"
|
||||||
>
|
>
|
||||||
<input name="langs" type="radio" class="lang hidden" id="en" autocomplete="off" />
|
<input name="langs" type="radio" class="lang hidden" id="fr-FR" autocomplete="off" />
|
||||||
<img class="flags mx-2" src={deFlag} alt="DE" height="26" width="26" />
|
<img class="flags mx-2" src={deFlag} alt="DE" height="26" width="26" />
|
||||||
</label>
|
</label>
|
||||||
<label
|
<label
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { User } from '$lib/stores/bash/bash';
|
|
||||||
import { Terminal, type PrintData } from '$lib/stores/terminal/terminal';
|
import { Terminal, type PrintData } from '$lib/stores/terminal/terminal';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import Input from './terminal/Input.svelte';
|
import Input from './terminal/Input.svelte';
|
||||||
import { initTerminal, isInitializing } from '$lib/stores/terminal/init.svelte';
|
import { initTerminal, isInitializing } from '$lib/stores/terminal/init.svelte';
|
||||||
import { clear, print } from '$lib/stores/terminal/stdio';
|
import { clear, print } from '$lib/stores/terminal/stdio/io';
|
||||||
|
import type { User } from '$lib/stores/bash/etc/userData';
|
||||||
|
|
||||||
const clearTerminal = (): void => clear();
|
const clearTerminal = (): void => clear();
|
||||||
|
|
||||||
@@ -20,13 +20,25 @@
|
|||||||
if(!e){
|
if(!e){
|
||||||
throw new Error('cant get width of the teminal element. Its null');
|
throw new Error('cant get width of the teminal element. Its null');
|
||||||
}
|
}
|
||||||
|
//gets an int from padding property value (which is a string) by cutting last 2 letters "px" and parsing to int
|
||||||
const padding: number = parseInt(window.getComputedStyle(e, null).getPropertyValue('padding').slice(0, -2));
|
const padding: number = parseInt(window.getComputedStyle(e, null).getPropertyValue('padding').slice(0, -2));
|
||||||
console.log(padding);
|
|
||||||
return e.clientWidth - (padding * 2);
|
return e.clientWidth - (padding * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleInput(e: KeyboardEvent) {
|
function getFontSize() {
|
||||||
switch (e.key) {
|
const e = document.getElementById('cout');
|
||||||
|
if(!e) {
|
||||||
|
throw new Error('cant get font size of the terminal element. its null');
|
||||||
|
}
|
||||||
|
|
||||||
|
const canvas = document.createElement('canvas');
|
||||||
|
const ctx = canvas.getContext('2d')!;
|
||||||
|
ctx.font = `${window.getComputedStyle(e, null).getPropertyValue('font-size')} 'JetBrains Mono', monospace;`;
|
||||||
|
return ctx.measureText('M').width;
|
||||||
|
}
|
||||||
|
|
||||||
|
function inputHandler(event: KeyboardEvent) {
|
||||||
|
switch (event.key) {
|
||||||
case 'Enter': {
|
case 'Enter': {
|
||||||
terminal.executeCommand(inputValue);
|
terminal.executeCommand(inputValue);
|
||||||
updateTerminal();
|
updateTerminal();
|
||||||
@@ -45,6 +57,13 @@
|
|||||||
//TODO: Make a traverse history function with up/down args
|
//TODO: Make a traverse history function with up/down args
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const elem = document.getElementById('cout');
|
||||||
|
if(!elem){
|
||||||
|
throw new Error('cant scroll to bottom, element is null');
|
||||||
|
}
|
||||||
|
|
||||||
|
elem.scrollTop = elem.scrollHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Callback initializer
|
//Callback initializer
|
||||||
@@ -54,7 +73,9 @@
|
|||||||
if (!e) return;
|
if (!e) return;
|
||||||
printOutput(e, data);
|
printOutput(e, data);
|
||||||
},
|
},
|
||||||
getWidth: getWidth
|
clear: clearTerminal,
|
||||||
|
getWidth: getWidth,
|
||||||
|
getFontSize: getFontSize
|
||||||
};
|
};
|
||||||
|
|
||||||
//Test user with basic data so the bash can run
|
//Test user with basic data so the bash can run
|
||||||
@@ -76,7 +97,7 @@
|
|||||||
|
|
||||||
onMount(async () => {
|
onMount(async () => {
|
||||||
try {
|
try {
|
||||||
terminal = await initTerminal(testUser, callbackInit);
|
terminal = await initTerminal({ username: 'root', password: '123'}, callbackInit, 'en_US');
|
||||||
updateTerminal();
|
updateTerminal();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('onMount trycatch failed', error);
|
console.error('onMount trycatch failed', error);
|
||||||
@@ -84,8 +105,8 @@
|
|||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<label for="input" onkeydowncapture={(e) => handleInput(e)}>
|
<label for="input" onkeydowncapture={(event) => inputHandler(event)} class="w-11/12">
|
||||||
<div id="terminal" class="terminal-window shadow-() size-full rounded-md shadow-bg">
|
<div id="terminal" class="terminal-window shadow-() h-full w-full rounded-md shadow-bg">
|
||||||
<div
|
<div
|
||||||
class="terminal-bar flex h-9 w-full flex-row items-center rounded-t-md bg-bg-dark text-center font-terminal text-sm font-bold text-primary-dark light:bg-bg-dark-light light:text-primary-light"
|
class="terminal-bar flex h-9 w-full flex-row items-center rounded-t-md bg-bg-dark text-center font-terminal text-sm font-bold text-primary-dark light:bg-bg-dark-light light:text-primary-light"
|
||||||
>
|
>
|
||||||
@@ -94,7 +115,7 @@
|
|||||||
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
|
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
|
||||||
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
|
<button class="size-2.5 cursor-pointer rounded-full p-0" title=""></button>
|
||||||
</div>
|
</div>
|
||||||
<div class=" flex">
|
<div class=" flex mr-2 grow">
|
||||||
<h5>{username}</h5>
|
<h5>{username}</h5>
|
||||||
<!-- prettier-ignore -->
|
<!-- prettier-ignore -->
|
||||||
<h5 class=" mr-2">@terminal: </h5>
|
<h5 class=" mr-2">@terminal: </h5>
|
||||||
@@ -102,7 +123,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="inner-content scroll-hidden h-[860px] origin-top overflow-y-auto rounded-b-md bg-bg-light-dark p-4 text-text-dark shadow-subtle light:bg-bg-lighter-light light:text-text-light"
|
class="inner-content scroll-hidden h-7/8 origin-top overflow-y-auto rounded-b-md bg-bg-light-dark p-4 text-text-dark shadow-subtle light:bg-bg-lighter-light light:text-text-light"
|
||||||
id="cout"
|
id="cout"
|
||||||
>
|
>
|
||||||
<div id="outputWrapper"></div>
|
<div id="outputWrapper"></div>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<span class=" pointer self-start pr-2">$</span>
|
<span class=" pointer self-start pr-2">$</span>
|
||||||
<p>{cmd}</p>
|
<p>{cmd}</p>
|
||||||
</div>
|
</div>
|
||||||
<div style="white-space: nowrap;" class=" relative">
|
<div style="white-space: pre;" class=" relative">
|
||||||
{#if typeof output === 'string'}
|
{#if typeof output === 'string'}
|
||||||
{output}
|
{output}
|
||||||
{:else if output instanceof Element}
|
{:else if output instanceof Element}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Settings></Settings>
|
<Settings></Settings>
|
||||||
<div class="h-dvh w-full p-24">
|
<div class="h-dvh w-full p-24 flex justify-center">
|
||||||
<TerminalModule />
|
<TerminalModule />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import adapter from '@sveltejs/adapter-auto';
|
|
||||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||||
|
|
||||||
/** @type {import('@sveltejs/kit').Config} */
|
/** @type {import('@sveltejs/kit').Config} */
|
||||||
@@ -11,7 +11,6 @@ const config = {
|
|||||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||||
adapter: adapter()
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user