├── screen1.jpg
├── screen2.jpg
├── screen3.jpg
├── source
├── modules
│ ├── manifest.js
│ ├── io-fs.js
│ ├── context.js
│ ├── fp-api.js
│ └── app.js
├── styles
│ ├── themes
│ │ ├── light.css
│ │ ├── dark.css
│ │ ├── hacker.css
│ │ └── cherry.css
│ └── global.css
└── index.htm
├── README.md
├── .github
└── FUNDING.yml
└── LICENSE
/screen1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DosX-dev/braux/HEAD/screen1.jpg
--------------------------------------------------------------------------------
/screen2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DosX-dev/braux/HEAD/screen2.jpg
--------------------------------------------------------------------------------
/screen3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/DosX-dev/braux/HEAD/screen3.jpg
--------------------------------------------------------------------------------
/source/modules/manifest.js:
--------------------------------------------------------------------------------
1 | // Github: https://github.com/DosX-dev/braux
2 |
3 | const config = {
4 | version: '3.20.25'
5 | },
6 | version = {
7 | os: 'braux (client)',
8 | kernel: 'VM-' + config.version,
9 | shell: 'bash-js'
10 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # What is Braux?
2 | Braux is a unique console system built on browser-based client tools.
3 | It combines ease of use with powerful features, giving you a Unix-like
4 | command line with a choice of different themes and color schemes.
5 |
6 | # Installed?
7 | ### https://dosx.su/terminal
8 |
9 | # To do
10 | * make command control the current user
11 |
12 | 
13 | 
14 | 
15 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
13 | custom: ['https://github.com/DosX-dev/DosX-dev/blob/main/donate.md'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
14 |
--------------------------------------------------------------------------------
/source/styles/themes/light.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: white;
3 | color: black;
4 | }
5 |
6 | hr {
7 | background-color: rgb(151, 151, 151);
8 | }
9 |
10 | ::-webkit-scrollbar-track {
11 | background-color: white;
12 | }
13 |
14 | ::-webkit-scrollbar-thumb {
15 | background-color: black;
16 | }
17 |
18 | ::selection {
19 | background-color: black;
20 | color: white;
21 | }
22 |
23 | input[type="text"] {
24 | color: black;
25 | caret-color: black;
26 | }
27 |
28 | .error {
29 | background-color: rgb(116, 0, 0);
30 | color: white;
31 | }
32 |
33 | a {
34 | background-color: rgb(0, 63, 238);
35 | color: white;
36 | }
37 |
38 | a:hover {
39 | background-color: rgb(0, 0, 0);
40 | }
41 |
42 | .pointer {
43 | color: black;
44 | }
45 |
46 | #context-menu {
47 | background: #eee;
48 | border-color: rgb(151, 151, 151);
49 | }
50 |
51 | #context-menu .item {
52 | color: #000000;
53 | }
54 |
55 | #context-menu .item:hover {
56 | background: #dbdbdb;
57 | }
--------------------------------------------------------------------------------
/source/styles/themes/dark.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: black;
3 | color: white;
4 | }
5 |
6 | hr {
7 | background-color: rgb(66, 66, 66);
8 | }
9 |
10 | ::-webkit-scrollbar-track {
11 | background-color: black;
12 | }
13 |
14 | ::-webkit-scrollbar-thumb {
15 | background-color: white;
16 | }
17 |
18 | ::selection {
19 | background-color: white;
20 | color: black;
21 | }
22 |
23 | input[type="text"] {
24 | color: white;
25 | caret-color: white;
26 | }
27 |
28 | .error {
29 | background-color: rgb(116, 0, 0);
30 | color: white;
31 | }
32 |
33 | a {
34 | background-color: rgb(43, 100, 255);
35 | color: white;
36 | }
37 |
38 | a:hover {
39 | background-color: white;
40 | color: rgb(57, 43, 255);
41 | }
42 |
43 | .pointer {
44 | color: white;
45 | }
46 |
47 | #context-menu {
48 | background: #1b1a1a;
49 | border-color: rgb(66, 66, 66);
50 | }
51 |
52 | #context-menu .item {
53 | color: #eee;
54 | }
55 |
56 | #context-menu .item:hover {
57 | background: #343434;
58 | }
--------------------------------------------------------------------------------
/source/styles/themes/hacker.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: rgb(0, 18, 19);
3 | color: white;
4 | }
5 |
6 | hr {
7 | background-color: rgb(0, 138, 0);
8 | }
9 |
10 | ::-webkit-scrollbar-track {
11 | background-color: black;
12 | }
13 |
14 | ::-webkit-scrollbar-thumb {
15 | background-color: lime;
16 | }
17 |
18 | ::selection {
19 | background-color: rgb(0, 138, 0);
20 | color: white;
21 | }
22 |
23 | input[type="text"] {
24 | color: white;
25 | caret-color: lime;
26 | }
27 |
28 | .error {
29 | background-color: rgb(255, 155, 155);
30 | color: black;
31 | }
32 |
33 | a {
34 | background-color: rgb(23, 80, 0);
35 | color: white;
36 | }
37 |
38 | a:hover {
39 | background-color: white;
40 | color: rgb(57, 43, 255);
41 | }
42 |
43 | .pointer {
44 | color: lime;
45 | }
46 |
47 | #context-menu {
48 | background: #1b1a1a;
49 | border-color: rgb(23, 80, 0);
50 | }
51 |
52 | #context-menu .item {
53 | color: #eee;
54 | }
55 |
56 | #context-menu .item:hover {
57 | background: #003612;
58 | }
--------------------------------------------------------------------------------
/source/styles/themes/cherry.css:
--------------------------------------------------------------------------------
1 | body {
2 | background-color: rgb(41, 0, 35);
3 | color: white;
4 | }
5 |
6 | hr {
7 | background-color: rgb(108, 0, 158);
8 | }
9 |
10 | ::-webkit-scrollbar-track {
11 | background-color: black;
12 | }
13 |
14 | ::-webkit-scrollbar-thumb {
15 | background-color: white;
16 | }
17 |
18 | ::selection {
19 | background-color: rgb(255, 0, 43);
20 | color: white;
21 | }
22 |
23 | input[type="text"] {
24 | color: white;
25 | caret-color: rgb(255, 210, 247);
26 | }
27 |
28 | .error {
29 | background-color: rgb(190, 44, 44);
30 | color: white;
31 | }
32 |
33 | a {
34 | background-color: rgb(173, 0, 130);
35 | color: white;
36 | }
37 |
38 | a:hover {
39 | background-color: white;
40 | color: rgb(255, 0, 191);
41 | }
42 |
43 | .pointer {
44 | color: white;
45 | }
46 |
47 | #context-menu {
48 | background: #1b1a1a;
49 | border-color: rgb(108, 0, 158);
50 | }
51 |
52 | #context-menu .item {
53 | color: #eee;
54 | }
55 |
56 | #context-menu .item:hover {
57 | background: #4e004e;
58 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 DosX
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/source/index.htm:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 | Braux: Terminal
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
23 |
24 |
25 |
26 | >
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/source/styles/global.css:
--------------------------------------------------------------------------------
1 | html,
2 | body {
3 | transition: background-color 0.8s, color 0.8s;
4 | cursor: default;
5 | font-family: monospace;
6 | margin: 0;
7 | padding: 0;
8 | zoom: 100%;
9 | width: 100%;
10 | height: 100%;
11 | }
12 |
13 | input .container {
14 | margin-left: 10px;
15 | }
16 |
17 | input,
18 | .prompt {
19 | user-select: none;
20 | }
21 |
22 | .command {
23 | white-space: pre-wrap;
24 | }
25 |
26 | .first {
27 | font-style: italic;
28 | }
29 |
30 | .log {
31 | margin: 10px;
32 | margin-bottom: 10px;
33 | white-space: pre-wrap;
34 | }
35 |
36 | hr {
37 | height: 1px;
38 | border: none;
39 | }
40 |
41 | #console {
42 | overflow-y: auto;
43 | word-wrap: break-word;
44 | }
45 |
46 | ::-webkit-scrollbar {
47 | width: 10px;
48 | }
49 |
50 | input[type="text"] {
51 | cursor: default;
52 | background-color: transparent;
53 | border: none;
54 | font-family: monospace;
55 | font-size: inherit;
56 | width: 94vw;
57 | outline: none;
58 | }
59 |
60 | .error {
61 | transition: background-color 0.9s, color 0.9s;
62 | border-radius: 4px;
63 | height: 100%;
64 | }
65 |
66 | a {
67 | transition: background-color 0.3s, color 0.3s;
68 | text-decoration-line: none;
69 | border-bottom: 1px dotted;
70 | }
71 |
72 | .insert-cmd {
73 | cursor: help;
74 | }
75 |
76 | #context-menu {
77 | border: 1px solid;
78 | position: fixed;
79 | z-index: 10000;
80 | width: 150px;
81 | border-radius: 5px;
82 | transform: scale(0);
83 | transform-origin: top left;
84 | user-select: none;
85 | }
86 |
87 | #context-menu.visible {
88 | transform: scale(1);
89 | transition: transform 200ms ease-in-out;
90 | }
91 |
92 | #context-menu .item {
93 | transition: background-color 0.3s, color 0.3s;
94 | padding: 8px 10px;
95 | font-size: 15px;
96 | cursor: pointer;
97 | border-radius: inherit;
98 | }
--------------------------------------------------------------------------------
/source/modules/io-fs.js:
--------------------------------------------------------------------------------
1 | // Github: https://github.com/DosX-dev/braux
2 |
3 | class FileSystem {
4 | constructor() {
5 | this.storage = window.localStorage;
6 | this.files = JSON.parse(this.storage.getItem('files')) || {};
7 | this.serializedFiles = JSON.stringify(this.files);
8 | this.fileList = Object.keys(this.files).join('\n');
9 | }
10 |
11 | createFile(name, content = '') {
12 | if (!this.isValidFileName(name)) {
13 | throw new Error('Invalid file name');
14 | }
15 |
16 | if (this.fileExists(name)) {
17 | throw new Error('File already exists');
18 | }
19 |
20 | this.files[name] = content;
21 | this.serializedFiles = JSON.stringify(this.files);
22 | this.saveFiles();
23 | }
24 |
25 | deleteFile(name) {
26 | if (!this.isValidFileName(name)) {
27 | throw new Error('Invalid file name');
28 | }
29 |
30 | if (!this.fileExists(name)) {
31 | throw new Error('File not found');
32 | }
33 |
34 | delete this.files[name];
35 | this.serializedFiles = JSON.stringify(this.files);
36 | this.saveFiles();
37 | }
38 |
39 | readFile(name) {
40 | if (!this.isValidFileName(name)) {
41 | throw new Error('Invalid file name');
42 | }
43 |
44 | if (!this.fileExists(name)) {
45 | throw new Error('File not found');
46 | }
47 |
48 | return this.files[name];
49 | }
50 |
51 | writeFile(name, content) {
52 | if (!this.isValidFileName(name)) {
53 | throw new Error('Invalid file name');
54 | }
55 |
56 | if (!this.fileExists(name)) {
57 | throw new Error('File not found');
58 | }
59 |
60 | this.files[name] = content;
61 | this.serializedFiles = JSON.stringify(this.files);
62 | this.saveFiles();
63 | }
64 |
65 | saveFiles() {
66 | this.storage.setItem('files', this.serializedFiles);
67 | }
68 |
69 | getFileList() {
70 | return this.fileList;
71 | }
72 |
73 | isValidFileName(name) {
74 | const forbiddenChars = /[^\w\d-_]/;
75 | return !forbiddenChars.test(name);
76 | }
77 |
78 | fileExists(name) {
79 | return name in this.files;
80 | }
81 | }
82 |
83 | const IO = new FileSystem();
--------------------------------------------------------------------------------
/source/modules/context.js:
--------------------------------------------------------------------------------
1 | // Github: https://github.com/DosX-dev/braux
2 |
3 | const contextMenu = document.getElementById("context-menu");
4 | const scope = document.querySelector("body");
5 |
6 | const normalizePozition = (mouseX, mouseY) => {
7 | // ? compute what is the mouse position relative to the container element (scope)
8 | let {
9 | left: scopeOffsetX,
10 | top: scopeOffsetY,
11 | } = scope.getBoundingClientRect();
12 |
13 | scopeOffsetX = scopeOffsetX < 0 ? 0 : scopeOffsetX;
14 | scopeOffsetY = scopeOffsetY < 0 ? 0 : scopeOffsetY;
15 |
16 | const scopeX = mouseX - scopeOffsetX,
17 | scopeY = mouseY - scopeOffsetY;
18 |
19 | // ? check if the element will go out of bounds
20 | const outOfBoundsOnX =
21 | scopeX + contextMenu.clientWidth > scope.clientWidth;
22 |
23 | const outOfBoundsOnY =
24 | scopeY + contextMenu.clientHeight > scope.clientHeight;
25 |
26 | let normalizedX = mouseX,
27 | normalizedY = mouseY;
28 |
29 | // ? normalize on X
30 | if (outOfBoundsOnX) {
31 | normalizedX =
32 | scopeOffsetX + scope.clientWidth - contextMenu.clientWidth;
33 | }
34 |
35 | // ? normalize on Y
36 | if (outOfBoundsOnY) {
37 | normalizedY =
38 | scopeOffsetY + scope.clientHeight - contextMenu.clientHeight;
39 | }
40 |
41 | return {
42 | normalizedX,
43 | normalizedY
44 | };
45 | };
46 |
47 | scope.addEventListener("contextmenu", (event) => {
48 | event.preventDefault();
49 |
50 | const {
51 | clientX: mouseX,
52 | clientY: mouseY
53 | } = event, {
54 | normalizedX,
55 | normalizedY
56 | } = normalizePozition(mouseX, mouseY);
57 |
58 | contextMenu.classList.remove("visible");
59 |
60 | contextMenu.style.top = `${normalizedY}px`;
61 | contextMenu.style.left = `${normalizedX}px`;
62 |
63 | setTimeout(() => {
64 | contextMenu.classList.add("visible");
65 | });
66 | });
67 |
68 | scope.addEventListener("click", (event) => {
69 | // ? close the menu if the user clicks outside of it
70 | if (event.target.offsetParent != contextMenu) {
71 | contextMenu.classList.remove("visible");
72 | }
73 | });
74 |
75 | const items = document.getElementsByClassName("item");
76 |
77 | Array.from(items).forEach(item => {
78 | item.addEventListener("click", (event) => {
79 | contextMenu.classList.remove('visible');
80 | });
81 | });
--------------------------------------------------------------------------------
/source/modules/fp-api.js:
--------------------------------------------------------------------------------
1 | // Github: https://github.com/DosX-dev/braux
2 |
3 | function fingerprint() {
4 | var fingerprintData = {
5 | 'User Agent': navigator.userAgent,
6 | 'Browser Language': navigator.language,
7 | 'Cookies Enabled': navigator.cookieEnabled,
8 | 'Screen Resolution': screen.width + 'x' + screen.height,
9 | 'Available Screen Resolution': screen.availWidth + 'x' + screen.availHeight,
10 | 'Color Depth': screen.colorDepth,
11 | 'Timezone': Intl.DateTimeFormat().resolvedOptions().timeZone,
12 | 'Local Storage Enabled': typeof Storage !== 'undefined',
13 | 'Session Storage Enabled': typeof sessionStorage !== 'undefined',
14 | 'Do Not Track': Boolean(navigator.doNotTrack),
15 | 'Plugins': Array.from(navigator.plugins).map(plugin => plugin.name).join(', '),
16 | 'WebGL Vendor': getWebGLVendor(),
17 | 'WebGL Renderer': getWebGLRenderer(),
18 | 'WebGL Version': getWebGLVersion(),
19 | 'Web Audio API': isWebAudioAPISupported(),
20 | 'MIDI API': isMIDIAPIAvailable(),
21 | 'WebSockets Supported': isWebSocketsSupported(),
22 | 'Battery API Supported': isBatteryAPISupported(),
23 | 'WebVR API Supported': isWebVRAPIAvailable(),
24 | 'AudioContext Max Channels': getMaxAudioContextChannels(),
25 | 'Is Chromium based': isChromium()
26 | };
27 |
28 | var fingerprintString = Object.entries(fingerprintData)
29 | .map(entry => `${entry[0]}` + ': ' + entry[1])
30 | .join('\n');
31 |
32 | return fingerprintString;
33 | }
34 |
35 | function getWebGLVendor() {
36 | var canvas = document.createElement('canvas'),
37 | gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
38 | if (gl && gl.getExtension('WEBGL_debug_renderer_info')) { return gl.getParameter(gl.getExtension('WEBGL_debug_renderer_info').UNMASKED_VENDOR_WEBGL); }
39 | return 'N/A';
40 | }
41 |
42 | function getWebGLRenderer() {
43 | var canvas = document.createElement('canvas'),
44 | gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
45 | if (gl && gl.getExtension('WEBGL_debug_renderer_info')) { return gl.getParameter(gl.getExtension('WEBGL_debug_renderer_info').UNMASKED_RENDERER_WEBGL); }
46 | return 'N/A';
47 | }
48 |
49 | function getWebGLVersion() {
50 | var canvas = document.createElement('canvas'),
51 | gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl');
52 | if (gl) { return gl.getParameter(gl.VERSION); }
53 | return 'N/A';
54 | }
55 |
56 | function isWebAudioAPISupported() { return typeof window.AudioContext !== 'undefined' || typeof window.webkitAudioContext !== 'undefined'; }
57 |
58 | function isMIDIAPIAvailable() { return typeof navigator.requestMIDIAccess !== 'undefined'; }
59 |
60 | function isWebSocketsSupported() { return 'WebSocket' in window ? 'Supported' : 'Not supported'; }
61 |
62 | function isBatteryAPISupported() { return navigator.getBattery ? 'Supported' : 'Not supported'; }
63 |
64 | function isWebVRAPIAvailable() { return 'getVRDisplays' in navigator ? 'Supported' : 'Not supported'; }
65 |
66 | function isWebXRAPIAvailable() { return 'xr' in navigator ? 'Supported' : 'Not supported'; }
67 |
68 | function getMaxAudioContextChannels() {
69 | try {
70 | var audioContext = new(window.AudioContext || window.webkitAudioContext)(),
71 | maxChannels = audioContext.destination.maxChannelCount;
72 | audioContext.close();
73 | return maxChannels;
74 | } catch (exc) {
75 | return 'N/A'
76 | }
77 | }
78 |
79 | function isChromium() { return typeof window.chrome == 'object'; }
80 |
81 | function isOpera() { return typeof window.opera == 'object'; }
--------------------------------------------------------------------------------
/source/modules/app.js:
--------------------------------------------------------------------------------
1 | // Github: https://github.com/DosX-dev/braux
2 |
3 | const visual = {
4 | themes: {
5 | dark: 'styles/themes/dark.css',
6 | light: 'styles/themes/light.css',
7 | cherry: 'styles/themes/cherry.css',
8 | hacker: 'styles/themes/hacker.css'
9 | },
10 | installTheme(theme) {
11 | visual.setTheme(theme);
12 | localStorage.setItem('console-theme', theme);
13 | },
14 | setTheme(theme) {
15 | const linkId = 'theme-link',
16 | link = document.getElementById(linkId);
17 |
18 | if (link) {
19 | link.href = theme;
20 | } else {
21 | const newLink = document.createElement('link');
22 | newLink.id = linkId;
23 | newLink.rel = 'stylesheet';
24 | newLink.href = theme;
25 | document.head.appendChild(newLink);
26 | }
27 | },
28 | loadTheme() {
29 | const theme = localStorage.getItem('console-theme');
30 | if (theme) {
31 | visual.setTheme(theme);
32 | } else {
33 | const preferredTheme = window.matchMedia &&
34 | window.matchMedia('(prefers-color-scheme: dark)').matches ? visual.themes.dark : visual.themes.light;
35 | visual.installTheme(preferredTheme);
36 | }
37 | }
38 | }
39 |
40 | visual.loadTheme();
41 | document.getElementById('version-info').innerText = `version ${config.version}`;
42 |
43 | Object.entries({
44 | "app-default-config": "0",
45 | "app-prompt": navigator.userAgent
46 | }).forEach(([key, value]) => {
47 | setDefaultPromptValue(key, value);
48 | });
49 |
50 | const commandInput = document.getElementById("commandInput");
51 | const isWelcomeHiddenKey = 'isWelcomeHidden';
52 |
53 | function isWelcomeHidden() {
54 | return localStorage.getItem(isWelcomeHiddenKey);
55 | }
56 |
57 | var commandHistory = [],
58 | currentCommandIndex = -1;
59 |
60 |
61 |
62 | const userProfile = {
63 | name: {
64 | key: 'user',
65 | defaultName: 'user',
66 | get() {
67 | const userNameValue = localStorage.getItem(userProfile.name.key);
68 |
69 | if (userNameValue) {
70 | return userNameValue;
71 | } else {
72 | this.set(this.defaultName);
73 | return this.defaultName;
74 | }
75 | },
76 | set(newUserName) {
77 |
78 | if (newUserName.length < 1) {
79 | console.error('Username cannot be empty.');
80 | return;
81 | } else if (!/^[a-zA-Z0-9]+$/.test(newUserName)) {
82 | console.error('Invalid characters.');
83 | return;
84 | }
85 |
86 | localStorage.setItem(userProfile.name.key, newUserName.toLowerCase());
87 | }
88 | }
89 | };
90 |
91 | function replaceTagsWithEntities(text) {
92 | return replacedText = text.replace(//g, '>');
93 | }
94 |
95 | function setFocus() {
96 | if (window.getSelection().toString() == '' && commandInput !== document.activeElement) {
97 | commandInput.focus();
98 | }
99 | }
100 |
101 | async function getIpInfo(ip = '') {
102 | const response = await fetch('https://freeipapi.com/api/json/' + ip, {
103 | method: 'GET'
104 | });
105 |
106 | const data = await response.json();
107 |
108 | const processedData = Object.entries(data)
109 | .map(([key, value]) => `${key}: ${value}`)
110 | .join('\n');
111 |
112 | return processedData + '\napiUsed: freeipapi.com';
113 | }
114 |
115 | function wrapFirstWord(sentence) {
116 | var words = sentence.split(' ');
117 |
118 | if (words.length === 0) {
119 | return '';
120 | }
121 |
122 | words[0] = `${words[0]}`;
123 |
124 | return words.join(' ');
125 | }
126 |
127 | window.onerror = function(message, source, lineno, colno, error) {
128 | console.error(`[APP]: ${message}`);
129 | };
130 |
131 | function autoScroll() {
132 | window.scrollTo(0, document.body.scrollHeight);
133 | }
134 |
135 | function pushCommand(command, displayCommand = true, sudo = false) {
136 | var consoleDiv = document.getElementById('console'),
137 | output = document.createElement('div');
138 | if (displayCommand) {
139 | commandInput.placeholder = '';
140 | output.classList.add('out');
141 | output.innerHTML = '> ' + wrapFirstWord(replaceTagsWithEntities(command)) + '';
142 | consoleDiv.appendChild(output);
143 | }
144 |
145 | function executeAsRoot(callback) {
146 | if (sudo) {
147 | callback();
148 | } else {
149 | error('You do not have permission to execute this command.');
150 | out(`Try 'sudo ${command}' (click to insert)`);
151 | }
152 | }
153 |
154 | function getAllCommandArguments(array) {
155 | let out = '';
156 | for (let i = 1; i < array.length; i++) {
157 | out += array[i] + (i == array.length - 1 ? '' : ' ');
158 | }
159 | return out;
160 | }
161 |
162 | function out(text) {
163 | let outStd = document.createElement('div');
164 | ['out', 'log'].forEach(outClass => {
165 | outStd.classList.add(outClass);
166 | });
167 | outStd.innerHTML = text;
168 | consoleDiv.appendChild(outStd);
169 | autoScroll();
170 | }
171 |
172 | function error(text) {
173 | out(`${text}`);
174 | let lastCommands = document.getElementsByClassName("command"),
175 | lastCommand = lastCommands[lastCommands.length - 1];
176 | lastCommand.style = 'color: rgba(255, 79, 79);';
177 | lastCommand.innerHTML += ' (!)';
178 | autoScroll();
179 | }
180 |
181 | console.error = error;
182 | console.log = console.info = console.warn = out;
183 |
184 | console.clear = () => pushCommand("clear");
185 |
186 | const commandArgs = command.trim().split(' ');
187 |
188 | switch (commandArgs[0]) {
189 | case 'help':
190 | out(`help - show this message
191 | clear - clear console
192 | theme [name] - change theme
193 | echo [html] - format and write text in console
194 | ipinfo [ip] - get info about IP (no domains support)
195 | history - get a log of commands entered
196 | fingerprint - get client information
197 | clear - clear console
198 |
199 | sudo [command] - execute command as root
200 | about - get info about application
201 | reboot - restart the application
202 | exit - exit from the application
203 |
204 | * js [code] - execute JavaScript (unsafe)
205 | * factory-reset - reset all application data settings
206 | `);
207 | break;
208 |
209 |
210 |
211 | case 'clear':
212 | consoleDiv.innerText = '';
213 | out("Console cleared.");
214 | break;
215 |
216 |
217 |
218 | case 'exit':
219 | out('Goodbye!');
220 | setTimeout(function() {
221 | window.location.href = 'about:blank';
222 | }, 300);
223 | break;
224 |
225 |
226 |
227 | case 'echo':
228 | out(getAllCommandArguments(commandArgs))
229 | break;
230 |
231 |
232 |
233 | case 'fingerprint':
234 | out(fingerprint());
235 | break;
236 |
237 |
238 |
239 | case 'factory-reset':
240 | executeAsRoot(() => {
241 | localStorage.clear();
242 | out(`All data of '${document.domain}' erased`);
243 | setTimeout(() => {
244 | pushCommand('reboot', false);
245 | }, 750);
246 | });
247 | break;
248 |
249 |
250 |
251 | case 'history':
252 | switch (commandArgs[1]) {
253 | case 'clear':
254 | clearCommandHistory();
255 | out('Command history cleared.');
256 | break;
257 | case 'list':
258 | out(getCommandHistory());
259 | break;
260 | default:
261 | out(`Arguments:
262 | * list (get history as numbered list)
263 | * clear (clear history)
264 |
265 | Usage: history list`)
266 | }
267 | break;
268 |
269 |
270 |
271 | case 'theme':
272 | let isSeccuss = true;
273 | switch (commandArgs[1]) {
274 | case 'dark':
275 | visual.installTheme(visual.themes.dark);
276 | break;
277 | case 'light':
278 | visual.installTheme(visual.themes.light);
279 | break;
280 | case 'cherry':
281 | visual.installTheme(visual.themes.cherry);
282 | break;
283 | case 'hacker':
284 | visual.installTheme(visual.themes.hacker);
285 | break;
286 | default:
287 | isSeccuss = false;
288 | out(`Themes:
289 | * dark
290 | * light
291 | * cherry
292 | * hacker
293 |
294 | Usage: theme dark`);
295 | }
296 | if (isSeccuss) {
297 | out(`Theme installed: ${commandArgs[1]}`);
298 | }
299 | break;
300 |
301 |
302 |
303 | case 'ipinfo':
304 | out('Requesting...');
305 | getIpInfo(commandArgs[1])
306 | .then(dataString => {
307 | out(dataString);
308 | })
309 | .catch(error => {
310 | error('No API access');
311 | });
312 | break;
313 |
314 |
315 |
316 | case 'js':
317 | executeAsRoot(() => {
318 | let codeToExec = getAllCommandArguments(commandArgs);
319 |
320 | if (codeToExec.trim() == '') {
321 | error('Empty source!');
322 | } else {
323 | try {
324 | out(`< ${eval(codeToExec)}`);
325 | } catch (exc) {
326 | error(`[VM]: ${exc}`);
327 | }
328 | }
329 | });
330 | break;
331 |
332 |
333 |
334 | case 'about':
335 | out(` OS: ${version.os}
336 | Kernel: ${version.kernel}
337 | Shell: ${version.shell}`);
338 | break;
339 |
340 |
341 |
342 | case 'reboot':
343 | out('Rebooting...');
344 | setTimeout(() => {
345 | location.reload();
346 | }, 300);
347 | break;
348 |
349 |
350 |
351 | case 'su':
352 | error(`You can only use 'sudo'`);
353 | break;
354 |
355 |
356 |
357 | case 'sudo':
358 | let commandToExecute = getAllCommandArguments(commandArgs);
359 | if (commandToExecute.trim() == '') {
360 | out(`Usage: sudo [command]`)
361 | }
362 | pushCommand(commandToExecute, false, true);
363 | break;
364 |
365 |
366 |
367 | case 'remove-intro':
368 | localStorage.setItem(isWelcomeHiddenKey, String(true))
369 | break;
370 |
371 |
372 |
373 | case 'python': // Yes, I hate python
374 | error('Do not embarrass yourself.');
375 | break;
376 |
377 |
378 |
379 | case '': // Empty prompt
380 | break;
381 | case '#': // Comment
382 | break;
383 | default:
384 | error(`Command or packet '${commandArgs[0]}' not found!`);
385 | }
386 | autoScroll();
387 | }
388 |
389 | function pushCommandScript(command, sudo = false) {
390 | command.split('\n').forEach(element => {
391 | pushCommand(element, false, sudo);
392 | });
393 | }
394 |
395 | pushCommand(`echo Welcome, ${userProfile.name.get()}!`, false);
396 |
397 | if (isWelcomeHidden() !== String(true)) {
398 | pushCommand('echo ' +
399 | `+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
400 | Braux is a unique console system built on browser-based client tools.
401 | It combines ease of use with powerful features, giving you a Unix-like
402 | command line with a choice of different themes and color schemes.
403 |
404 | GitHub -> https://github.com/DosX-dev/braux
405 | +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
406 | Use 'remove-intro' to remove this message`, false);
407 | }
408 |
409 | function setDefaultPromptValue(name, defaultValue) {
410 | const urlParams = new URLSearchParams(window.location.search);
411 | if (!urlParams.has(name)) {
412 | let locationBarSections = document.URL.split('/'),
413 | locationBarLastSection = locationBarSections[locationBarSections.length - 1],
414 | separator = locationBarLastSection.includes('?') ? '&' : '?',
415 | newUrl = `${window.location.href + separator + name}=${defaultValue}`;
416 | history.pushState({ path: newUrl }, '', newUrl);
417 | }
418 | }
419 |
420 | // load history form localStorage
421 | const storedHistory = localStorage.getItem("history");
422 | if (storedHistory) {
423 | commandHistory = JSON.parse(storedHistory);
424 | currentCommandIndex = commandHistory.length;
425 | }
426 |
427 | commandInput.addEventListener("keydown", (event) => {
428 | switch (event.keyCode) {
429 | case 13: // Enter
430 | let command = commandInput.value.trim();
431 | if (command !== '') {
432 | if (commandHistory.length === 0 || command !== commandHistory[commandHistory.length - 1]) {
433 | commandHistory.push(command);
434 | }
435 | currentCommandIndex = commandHistory.length;
436 |
437 | // save history in localStorage
438 | localStorage.setItem("history", JSON.stringify(commandHistory));
439 | }
440 | break;
441 | case 38: // Up
442 | event.preventDefault();
443 | if (currentCommandIndex > 0) {
444 | currentCommandIndex--;
445 | commandInput.value = commandHistory[currentCommandIndex];
446 | }
447 | break;
448 | case 40: // Down
449 | event.preventDefault();
450 | if (currentCommandIndex < commandHistory.length - 1) {
451 | currentCommandIndex++;
452 | commandInput.value = commandHistory[currentCommandIndex];
453 | } else {
454 | currentCommandIndex = commandHistory.length;
455 | commandInput.value = '';
456 | }
457 | break;
458 | default:
459 | break;
460 | }
461 | });
462 |
463 |
464 | function getCommandHistory() {
465 | return commandHistory.map((command, index) => `${index + 1}. ${command}`).join("\n");
466 | }
467 |
468 | function clearCommandHistory() {
469 | commandHistory = [];
470 | currentCommandIndex = 0;
471 | localStorage.removeItem("history");
472 | }
473 |
474 | document.addEventListener('DOMContentLoaded', () => {
475 | commandInput.addEventListener('keypress', function(event) {
476 | if (event.keyCode === 13) { // Enter
477 | event.preventDefault();
478 | pushCommand(commandInput.value.trim());
479 | commandInput.value = '';
480 | }
481 | });
482 | });
483 |
484 | setInterval(() => {
485 | setFocus()
486 | }, 500);
487 |
488 | document.addEventListener('click', (event) => {
489 | if (event.target.classList.contains('insert-cmd')) {
490 | commandInput.value = event.target.textContent;
491 | }
492 | });
--------------------------------------------------------------------------------