Compare commits
6 Commits
6657151f81
...
feat/termi
| Author | SHA1 | Date | |
|---|---|---|---|
|
d46f2ed40d
|
|||
|
687e7af91b
|
|||
|
f97e362192
|
|||
|
6c7242b167
|
|||
|
0fdcf435e1
|
|||
|
27517ad857
|
@@ -24,11 +24,24 @@ COMMANDS = {
|
|||||||
"rm [file]": "Remove a file",
|
"rm [file]": "Remove a file",
|
||||||
"tree [path]": "Display directory tree",
|
"tree [path]": "Display directory tree",
|
||||||
"touch [file]": "Create a new empty file",
|
"touch [file]": "Create a new empty file",
|
||||||
"nano [file]": "Edit a file (write content)",
|
"nano [file]": "Edit a file",
|
||||||
"reset": "Reset the terminal session",
|
"reset": "Reset the terminal session",
|
||||||
"exit": "Exit the terminal session",
|
"exit": "Exit the terminal session",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOT_FILES = [
|
||||||
|
"amd-ucode.img",
|
||||||
|
"EFI",
|
||||||
|
"initramfs-linux-fallback.img",
|
||||||
|
"initramfs-linux.img",
|
||||||
|
"initramfs-linux-lts-fallback.img",
|
||||||
|
"initramfs-linux-lts.img",
|
||||||
|
"intel-ucode.img",
|
||||||
|
"loader",
|
||||||
|
"vmlinuz-linux",
|
||||||
|
"vmlinuz-linux-lts"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
def get_nodes_in_directory(path: str) -> list[str]:
|
def get_nodes_in_directory(path: str) -> list[str]:
|
||||||
"""Simulate getting files in a directory for the terminal."""
|
"""Simulate getting files in a directory for the terminal."""
|
||||||
@@ -63,6 +76,9 @@ def setup_path_session():
|
|||||||
binaries = []
|
binaries = []
|
||||||
for cmd in COMMANDS.keys():
|
for cmd in COMMANDS.keys():
|
||||||
binaries.append({"name": cmd.split()[0], "type": 2, "content": "", "permissions": 1})
|
binaries.append({"name": cmd.split()[0], "type": 2, "content": "", "permissions": 1})
|
||||||
|
boot_files = []
|
||||||
|
for bin_file in BOOT_FILES:
|
||||||
|
boot_files.append({"name": bin_file, "type": 1, "content": "", "permissions": 1})
|
||||||
|
|
||||||
session["paths"] = [
|
session["paths"] = [
|
||||||
{"name": "home", "type": 0, "children": [
|
{"name": "home", "type": 0, "children": [
|
||||||
@@ -71,13 +87,11 @@ def setup_path_session():
|
|||||||
], "permissions": 2}
|
], "permissions": 2}
|
||||||
], "permissions": 1},
|
], "permissions": 1},
|
||||||
{"name": "bin", "type": 0, "children": binaries, "permissions": 1},
|
{"name": "bin", "type": 0, "children": binaries, "permissions": 1},
|
||||||
{"name": "boot", "type": 0, "children": [], "permissions": 1},
|
{"name": "boot", "type": 0, "children": boot_files, "permissions": 1},
|
||||||
{"name": "dev", "type": 0, "children": [], "permissions": 1},
|
{"name": "dev", "type": 0, "children": [], "permissions": 1},
|
||||||
{"name": "etc", "type": 0, "children": [], "permissions": 1},
|
{"name": "etc", "type": 0, "children": [], "permissions": 1},
|
||||||
{"name": "lib", "type": 0, "children": [], "permissions": 1},
|
{"name": "lib", "type": 0, "children": [], "permissions": 1},
|
||||||
{"name": "lib64", "type": 0, "children": [], "permissions": 1},
|
{"name": "lib64", "type": 0, "children": [], "permissions": 1},
|
||||||
{"name": "mnt", "type": 0, "children": [], "permissions": 1},
|
|
||||||
{"name": "nix", "type": 0, "children": [], "permissions": 1},
|
|
||||||
{"name": "opt", "type": 0, "children": [], "permissions": 1},
|
{"name": "opt", "type": 0, "children": [], "permissions": 1},
|
||||||
{"name": "proc", "type": 0, "children": [], "permissions": 1},
|
{"name": "proc", "type": 0, "children": [], "permissions": 1},
|
||||||
{"name": "root", "type": 0, "children": [], "permissions": 1},
|
{"name": "root", "type": 0, "children": [], "permissions": 1},
|
||||||
@@ -225,22 +239,68 @@ def ls():
|
|||||||
all_files = True
|
all_files = True
|
||||||
args.remove("--all")
|
args.remove("--all")
|
||||||
|
|
||||||
path = session.get("pwd", f"/home/{getClientIP(request)}")
|
long_format = False
|
||||||
|
if "-l" in args:
|
||||||
|
long_format = True
|
||||||
|
args.remove("-l")
|
||||||
|
elif "--long" in args:
|
||||||
|
long_format = True
|
||||||
|
args.remove("--long")
|
||||||
|
|
||||||
|
for arg in args:
|
||||||
|
if arg.startswith("-") and not arg.startswith("--"):
|
||||||
|
if "l" in arg:
|
||||||
|
long_format = True
|
||||||
|
if "a" in arg:
|
||||||
|
all_files = True
|
||||||
|
args.remove(arg)
|
||||||
|
|
||||||
|
|
||||||
|
ip = getClientIP(request)
|
||||||
|
path = session.get("pwd", f"/home/{ip}")
|
||||||
if args:
|
if args:
|
||||||
path = args[0]
|
path = args[0]
|
||||||
if not path.startswith("/"):
|
if not path.startswith("/"):
|
||||||
# Relative path
|
# Relative path
|
||||||
path = sanitize_path(session.get("pwd", f"/home/{getClientIP(request)}") + "/" + path)
|
path = sanitize_path(session.get("pwd", f"/home/{ip}") + "/" + path)
|
||||||
if not is_valid_path(path):
|
if not is_valid_path(path):
|
||||||
|
# If it is a file or binary, return it
|
||||||
|
if is_valid_file(path) or is_valid_binary(path):
|
||||||
|
if long_format:
|
||||||
|
node = get_node(path)
|
||||||
|
permissions = node.get("permissions", 0)
|
||||||
|
perm_str = ""
|
||||||
|
perm_str += "r" if permissions > 0 else "-"
|
||||||
|
perm_str += "w" if permissions > 1 else "-"
|
||||||
|
perm_str += "x" if (node.get("type", 0) == 2 and permissions > 1) else "-"
|
||||||
|
user = f"{ip}" if path.startswith(f"/home/{ip}") else "root"
|
||||||
|
output = f"{perm_str} {user} {user} {path.split('/')[-1]}"
|
||||||
|
return json_response(request, {"output": output}, 200)
|
||||||
|
return json_response(request, {"output": path.split("/")[-1]}, 200)
|
||||||
|
|
||||||
return json_response(request, {"output": f"ls: cannot access '{path}': No such file or directory"}, 200)
|
return json_response(request, {"output": f"ls: cannot access '{path}': No such file or directory"}, 200)
|
||||||
|
|
||||||
files = get_nodes_in_directory(path)
|
files = get_nodes_in_directory(path)
|
||||||
output = [file["name"] for file in files]
|
output = []
|
||||||
|
if long_format:
|
||||||
|
for f in files:
|
||||||
|
permissions = f.get("permissions", 0)
|
||||||
|
perm_str = ""
|
||||||
|
perm_str += "r" if permissions > 0 else "-"
|
||||||
|
perm_str += "w" if permissions > 1 else "-"
|
||||||
|
perm_str += "x" if (f.get("type", 0) == 2 and permissions >= 1) else "-"
|
||||||
|
user = f"{ip}" if path.startswith(f"/home/{ip}") else "root"
|
||||||
|
output.append(f"{perm_str} {user} {user} {f['name']}")
|
||||||
|
else:
|
||||||
|
output = [f["name"] for f in files]
|
||||||
if all_files:
|
if all_files:
|
||||||
output.insert(0, ".")
|
output.insert(0, ".")
|
||||||
output.insert(1, "..")
|
output.insert(1, "..")
|
||||||
else:
|
else:
|
||||||
output = [file for file in output if not file.startswith(".")]
|
output = [file for file in output if not file.startswith(".")]
|
||||||
|
if long_format:
|
||||||
|
output = "\n".join(output)
|
||||||
|
else:
|
||||||
output = " ".join(output)
|
output = " ".join(output)
|
||||||
|
|
||||||
return json_response(request, {"output": output}, 200)
|
return json_response(request, {"output": output}, 200)
|
||||||
@@ -567,9 +627,6 @@ def tree():
|
|||||||
@terminal_bp.route("/terminal/execute/<command>", methods=["POST"])
|
@terminal_bp.route("/terminal/execute/<command>", methods=["POST"])
|
||||||
def execute_catch(command):
|
def execute_catch(command):
|
||||||
try:
|
try:
|
||||||
# data = request.get_json() or {}
|
|
||||||
# args = data.get("args", "")
|
|
||||||
|
|
||||||
# Basic command processing
|
# Basic command processing
|
||||||
if command == "help":
|
if command == "help":
|
||||||
output = "Available commands:\n" + \
|
output = "Available commands:\n" + \
|
||||||
|
|||||||
@@ -94,10 +94,10 @@
|
|||||||
<div class="line output">Type 'help' for available commands</div>
|
<div class="line output">Type 'help' for available commands</div>
|
||||||
<div class="line output"></div>
|
<div class="line output"></div>
|
||||||
<div id="input-line">
|
<div id="input-line">
|
||||||
<div class="prompt-line">┌──({{user}}@NathanWoodburn)-[~]</div>
|
<div class="prompt-line">┌──({{user}}@NW)-[~]</div>
|
||||||
<div class="input-row">
|
<div class="input-row">
|
||||||
<span class="prompt-line">└─$ </span>
|
<span class="prompt-line">└─$ </span>
|
||||||
<input type="text" id="command-input" autofocus autocomplete="off" spellcheck="false">
|
<input type="text" id="command-input" autofocus autocomplete="off" spellcheck="false" autocapitalize="none">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -123,6 +123,9 @@
|
|||||||
let nanoCurrentFile = '';
|
let nanoCurrentFile = '';
|
||||||
let nanoOriginalContent = '';
|
let nanoOriginalContent = '';
|
||||||
let nanoSavePromptActive = false;
|
let nanoSavePromptActive = false;
|
||||||
|
let tabCompletionIndex = 0;
|
||||||
|
let tabCompletionMatches = [];
|
||||||
|
let lastTabInput = '';
|
||||||
|
|
||||||
// Function to update the prompt with current pwd
|
// Function to update the prompt with current pwd
|
||||||
async function updatePrompt() {
|
async function updatePrompt() {
|
||||||
@@ -131,7 +134,7 @@
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
if (data.output) {
|
if (data.output) {
|
||||||
currentPwd = data.output;
|
currentPwd = data.output;
|
||||||
inputLine.querySelector('.prompt-line:first-child').textContent = `┌──({{user}}@NathanWoodburn)-[${currentPwd}]`;
|
inputLine.querySelector('.prompt-line:first-child').textContent = `┌──({{user}}@NW)-[${currentPwd}]`;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to fetch pwd:', err);
|
console.error('Failed to fetch pwd:', err);
|
||||||
@@ -256,10 +259,98 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Function to get available commands
|
||||||
|
function getAvailableCommands() {
|
||||||
|
return ['help', 'about', 'clear', 'echo', 'whoami', 'ls', 'pwd', 'date', 'cd', 'cat', 'rm', 'tree', 'touch', 'nano', 'reset', 'exit'];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to get files/directories for tab completion
|
||||||
|
async function getFilesForCompletion(path = '') {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/terminal/execute/ls', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ args: path ? path : '' })
|
||||||
|
});
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.output && !data.output.startsWith('ls:')) {
|
||||||
|
return data.output.split(' ').filter(f => f.length > 0);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to get files:', err);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to handle tab completion
|
||||||
|
async function handleTabCompletion(currentInput) {
|
||||||
|
const parts = currentInput.split(' ');
|
||||||
|
const isFirstWord = parts.length === 1;
|
||||||
|
|
||||||
|
if (isFirstWord) {
|
||||||
|
// Complete command names
|
||||||
|
const commands = getAvailableCommands();
|
||||||
|
const matches = commands.filter(cmd => cmd.startsWith(parts[0]));
|
||||||
|
|
||||||
|
if (matches.length === 1) {
|
||||||
|
return matches[0] + ' ';
|
||||||
|
} else if (matches.length > 1) {
|
||||||
|
// Cycle through matches
|
||||||
|
if (lastTabInput === currentInput) {
|
||||||
|
tabCompletionIndex = (tabCompletionIndex + 1) % matches.length;
|
||||||
|
} else {
|
||||||
|
tabCompletionIndex = 0;
|
||||||
|
tabCompletionMatches = matches;
|
||||||
|
}
|
||||||
|
lastTabInput = currentInput;
|
||||||
|
return matches[tabCompletionIndex] + ' ';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Complete file/directory names
|
||||||
|
const lastPart = parts[parts.length - 1];
|
||||||
|
const files = await getFilesForCompletion();
|
||||||
|
const matches = files.filter(f => f.startsWith(lastPart));
|
||||||
|
|
||||||
|
if (matches.length === 1) {
|
||||||
|
parts[parts.length - 1] = matches[0];
|
||||||
|
return parts.join(' ') + ' ';
|
||||||
|
} else if (matches.length > 1) {
|
||||||
|
// Cycle through matches
|
||||||
|
if (lastTabInput === currentInput) {
|
||||||
|
tabCompletionIndex = (tabCompletionIndex + 1) % matches.length;
|
||||||
|
} else {
|
||||||
|
tabCompletionIndex = 0;
|
||||||
|
tabCompletionMatches = matches;
|
||||||
|
}
|
||||||
|
lastTabInput = currentInput;
|
||||||
|
parts[parts.length - 1] = matches[tabCompletionIndex];
|
||||||
|
return parts.join(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentInput;
|
||||||
|
}
|
||||||
|
|
||||||
// Update prompt on load
|
// Update prompt on load
|
||||||
updatePrompt();
|
updatePrompt();
|
||||||
|
|
||||||
input.addEventListener('keydown', async (e) => {
|
input.addEventListener('keydown', async (e) => {
|
||||||
|
if (e.key === 'Tab') {
|
||||||
|
e.preventDefault();
|
||||||
|
const currentInput = input.value;
|
||||||
|
const completed = await handleTabCompletion(currentInput);
|
||||||
|
input.value = completed;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset tab completion on any other key
|
||||||
|
if (e.key !== 'Tab') {
|
||||||
|
tabCompletionIndex = 0;
|
||||||
|
tabCompletionMatches = [];
|
||||||
|
lastTabInput = '';
|
||||||
|
}
|
||||||
|
|
||||||
if (e.key === 'Enter') {
|
if (e.key === 'Enter') {
|
||||||
const command = input.value.trim();
|
const command = input.value.trim();
|
||||||
|
|
||||||
@@ -270,7 +361,7 @@
|
|||||||
// Display command
|
// Display command
|
||||||
const cmdLine = document.createElement('div');
|
const cmdLine = document.createElement('div');
|
||||||
cmdLine.className = 'line';
|
cmdLine.className = 'line';
|
||||||
cmdLine.innerHTML = `<span class="prompt">┌──({{user}}@NathanWoodburn)-[${currentPwd}]\n└─$ </span>${command}`;
|
cmdLine.innerHTML = `<span class="prompt">┌──({{user}}@NW)-[${currentPwd}]\n└─$ </span>${command}`;
|
||||||
terminal.appendChild(cmdLine);
|
terminal.appendChild(cmdLine);
|
||||||
|
|
||||||
// Add to history
|
// Add to history
|
||||||
|
|||||||
Reference in New Issue
Block a user