Skip to content

Commit a70d688

Browse files
committed
Add broken / experimental floating console frontend
1 parent 1230880 commit a70d688

File tree

5 files changed

+495
-90
lines changed

5 files changed

+495
-90
lines changed
Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
const currentFloatingTerminalObjs = {};
2+
3+
function setupFloatingTerminal(){
4+
$("#floatingWindowConsole").resizable({
5+
handles: "n",
6+
containment: "#mainContainerDiv",
7+
maxHeight: $("#mainContainerDiv").height() - 10,
8+
minHeight: $("#floatingConsoleTabsDiv").height() + 5
9+
})
10+
11+
$("#floatingWindowConsole").show();
12+
$("#sidebarFooter").find("#openFloatingTerminal").removeClass("btn-outline-secondary").addClass("btn-secondary")
13+
14+
15+
$("#floatingConsoleActionTabs").on("click", "#openTabFloatingTerminal", function(){
16+
// TODO Plus button
17+
})
18+
19+
$("#floatingConsoleActionTabs").on("click", "#expandFloatingTerminal", function(){
20+
let currentHeight = $("#floatingWindowConsole").height()
21+
let fullHeight = $("#mainContainerDiv").height() - 10
22+
if(currentHeight == fullHeight){
23+
$("#floatingWindowConsole").height($("#floatingConsoleActionTabs").height())
24+
}else{
25+
$("#floatingWindowConsole").height(fullHeight)
26+
}
27+
28+
$("#floatingWindowConsole").trigger("resize")
29+
})
30+
31+
$("#floatingConsoleActionTabs").on("click", "#hideFloatingTerminal", function(){
32+
// TODO Hide button
33+
$("#floatingWindowConsole").hide();
34+
$("#sidebarFooter").find("#openFloatingTerminal").removeClass("btn-secondary").addClass("btn-outline-secondary")
35+
$(document).focus()
36+
})
37+
38+
let sessions = JSON.parse(localStorage.getItem("floatingTerminalSessions"));
39+
let firstKey = Object.keys(sessions)[0]
40+
let toRemove = [];
41+
let width = 0;
42+
let maxWidth = $("#floatingConsoleInstanceTabs").width();
43+
let currentWidth = 0;
44+
$.each(sessions, (terminalId, terminal)=>{
45+
checkTerminalStatus(terminalId, (data)=>{
46+
if(data.exists == false){
47+
toRemove.push(terminalId)
48+
// openFloatingTerminal(terminal.hostId, terminal.project, terminal.instance, terminal.shell)
49+
}else{
50+
let active = terminalId == firstKey ? "active" : ""
51+
let x = $(`<li class="nav-item d-inline viewFloatingTerminal" data-terminal-id="${terminalId}">
52+
<div style="line-height: 2" class="nav-link ${active} d-flex" aria-current="page">
53+
<div class="d-inline my-auto me-3">
54+
<i class="fas fa-terminal text-primary"></i>
55+
</div>
56+
<div class="d-inline">
57+
<div>
58+
<i class="fas fa-box me-2"></i>${terminal.instance}
59+
</div>
60+
<div class="me-2 d-inline"><i class="fas fa-server me-2"></i>${hostsAliasesLookupTable[terminal.hostId]}</div>
61+
<div class="d-inline"><i class="fas fa-project-diagram me-2"></i>${terminal.project}</div>
62+
</div>
63+
</div>
64+
</li>`)
65+
$("#floatingConsoleInstanceTabs").append(x)
66+
currentWidth += x.width()
67+
if(currentWidth > maxWidth){
68+
if($("#floatingTerminalInstanceMoreBtn").length == 0){
69+
$("#floatingConsoleInstanceTabs").append(`<li class="nav-item dropdown text-center" id="floatingTerminalInstanceMoreBtn" style="width: ${maxWidth - (currentWidth - x.width())}">
70+
<div id="openTabFloatingTerminal" style="line-height: 2" class="nav-link dropdown-toggle" data-bs-toggle="dropdown" href="#" role="button" aria-expanded="false">
71+
<i style="min-width: 24px; line-height: 4;" class="fas fa-plus"></i>
72+
</div>
73+
<ul class="dropdown-menu w-100 bg-dark">
74+
<li><a class="dropdown-item" href="#">New terminal</a></li>
75+
<li><hr class="dropdown-divider"></li>
76+
<div id="moreFloatingConsoleEntries">
77+
</div>
78+
</ul>
79+
</li>`)
80+
}else{
81+
$("#floatingTerminalInstanceMoreBtn").find("#moreFloatingConsoleEntries").append(`<li><a class="dropdown-item">${terminal.instance}</a></li>`)
82+
}
83+
84+
x.remove()
85+
}
86+
87+
// _openFloatingTerm(firstKey, terminalId == firstKey)
88+
}
89+
90+
91+
});
92+
})
93+
console.log(toRemove);
94+
95+
}
96+
97+
98+
$(document).on("click", ".viewFloatingTerminal", function(){
99+
$("#floatingConsoleInstanceTabs").find(".active").removeClass("active")
100+
$(this).find(".nav-link").addClass("active");
101+
// TODO On change session
102+
$(".terminalScreen").hide()
103+
$("#floating-" + $(this).data("terminalId")).show();
104+
$("#floating-" + $(this).data("terminalId")).height($("#termia"));
105+
106+
})
107+
108+
function openFloatingTerminal(hostId, project, instance, shell){
109+
$.ajax({
110+
type: "POST",
111+
dataType: 'json',
112+
contentType: 'application/json',
113+
url: '/terminals',
114+
data: JSON.stringify({
115+
hostId: hostId,
116+
instance: instance,
117+
project: project,
118+
shell: shell
119+
}),
120+
success: function(data) {
121+
let sessions = JSON.parse(localStorage.getItem("floatingTerminalSessions"))
122+
123+
if(sessions == null){
124+
sessions = {};
125+
}
126+
sessions[data.terminalId] = {
127+
hostId: hostId,
128+
project: project,
129+
instance: instance,
130+
shell: shell
131+
}
132+
133+
localStorage.setItem("floatingTerminalSessions", JSON.stringify(sessions))
134+
135+
$("#floatingConsoleInstanceTabs").find(".active").removeClass("active");
136+
$("#floatingConsoleInstanceTabs").append(`<li class="nav-item d-inline viewFloatingTerminal" data-terminal-id="${data.terminalId}">
137+
<div style="line-height: 2" class="nav-link active d-flex" aria-current="page">
138+
<div class="d-inline my-auto me-3">
139+
<i class="fas fa-terminal text-primary"></i>
140+
</div>
141+
<div class="d-inline">
142+
<div><i class="fas fa-box me-2"></i>${instance}</div>
143+
<div class="me-2 d-inline"><i class="fas fa-server me-2"></i>${hostsAliasesLookupTable[hostId]}</div>
144+
<div class="d-inline"><i class="fas fa-project-diagram me-2"></i>${project}</div>
145+
</div>
146+
</div>
147+
</li>`)
148+
//TODO VMS
149+
// <li class="nav-item">
150+
// <a style="line-height: 2" class="nav-link d-flex" aria-current="page" href="#">
151+
// <div class="d-inline my-auto me-3">
152+
// <i class="fas fa-desktop text-primary"></i>
153+
// </div>
154+
// <div class="d-inline">
155+
// <div><i class="fas fa-vr-cardboard me-2"></i>Active</div>
156+
// <div class="me-2 d-inline"><i class="fas fa-server me-2"></i>Alias</div>
157+
// <div class="d-inline"><i class="fas fa-project-diagram me-2"></i>default</div>
158+
// </div>
159+
// </a>
160+
// </li>
161+
_openFloatingTerm(data.terminalId)
162+
},
163+
error: function(){
164+
// localTerm.writeln("LXDMosaic: Node server cant be reached.")
165+
// localTerm.writeln("Please report this to your admin.")
166+
// $("#terminalControls").find(".btn-toolbar").fadeOut(2000)
167+
// return false;
168+
},
169+
dataType: "json"
170+
});
171+
return false;
172+
}
173+
174+
function _openFloatingTerm(terminalId, show = true){
175+
let terminalHtmlId = "floating-" + terminalId
176+
$(".terminalScreen").hide()
177+
178+
let localfitAddon = new window.FitAddon.FitAddon()
179+
let localTerm = new Terminal({});
180+
181+
if($("#" + terminalHtmlId).length === 0){
182+
$("#floatConsoleTermsDiv").append(`<div id="${terminalHtmlId}" class="terminalScreen"></div>`)
183+
if(show){
184+
$("#" + terminalHtmlId).show()
185+
}
186+
}
187+
188+
// $("#floatConsoleTermsDiv").height($("#floatConsoleTermsDiv").height())
189+
const terminalContainer = $("#floatConsoleTermsDiv").find("#" + terminalHtmlId )[0]
190+
191+
// Clean terminal
192+
while (terminalContainer.children.length) {
193+
terminalContainer.removeChild(terminalContainer.children[0]);
194+
}
195+
196+
localTerm.loadAddon(localfitAddon)
197+
localTerm.open(terminalContainer);
198+
localfitAddon.fit()
199+
200+
if(show){
201+
$("#floatConsoleTermsDiv").find("#" + terminalHtmlId ).show()
202+
}
203+
204+
// Theoretically no need to inject credentials
205+
// here as auth is only called when a socket
206+
// is first connected (in this case when the
207+
// operations socket is setup - which will
208+
// always come before this) but to be safe ...
209+
let localConsoleSocket = new WebSocket(`wss://${getQueryVar("host", window.location.hostname)}:${getQueryVar("port", 443)}/node/console?${$.param({
210+
ws_token: userDetails.apiToken,
211+
user_id: userDetails.userId,
212+
terminalId: terminalId
213+
})}`);
214+
215+
localConsoleSocket.onclose = function(){
216+
localTerm.writeln("")
217+
localTerm.writeln("LXDMosaic: Shell closed, if this is un-expected it could be:")
218+
localTerm.writeln("")
219+
localTerm.writeln(" - A network error")
220+
localTerm.writeln(" - The instance was turned off")
221+
localTerm.writeln(" - LXDMosaic is missbehaving")
222+
localTerm.writeln(" - You're trying to use a shell not installed (I.E bash instead of ash)")
223+
};
224+
225+
$("#" + terminalId).height($("#floatConsoleTermsDiv").height())
226+
localTerm.loadAddon(new window.AttachAddon.AttachAddon(localConsoleSocket));
227+
$("#terminalControls").find(".btn-toolbar").fadeOut(2000)
228+
229+
currentFloatingTerminalObjs[terminalId] = {
230+
clientSocket: localConsoleSocket,
231+
termObj: localTerm,
232+
fitObj: localfitAddon
233+
}
234+
235+
setTimeout(() => {
236+
let x = localfitAddon.proposeDimensions();
237+
//NOTE send two requests, this seems to force the server to respond
238+
// with something, preventing the user seeing a blank screen
239+
// if something producing output isn't running
240+
localConsoleSocket.send(`resize-window:cols=${x.cols - 1}&rows=${x.rows - 1}`)
241+
localConsoleSocket.send(`resize-window:cols=${x.cols}&rows=${x.rows}`)
242+
}, 500) // 500 magic number, waiting for socket ready wasn't long enough :shrug:
243+
}
244+
245+
$(document).on("resize", '#floatingWindowConsole', function(){
246+
let newHeight = $("#floatingWindowConsole").height() - $("#floatingConsoleTabsDiv").height()
247+
$("#floatingConsoleMainDisplay").height(newHeight)
248+
$("#floatConsoleTermsDiv").height(newHeight)
249+
let visible = $("#floatConsoleTermsDiv").find(".terminalScreen:visible")
250+
251+
$("#floatConsoleTermsDiv").find(".terminalScreen").each(function(){
252+
let terminalId = $(this).attr("id").replace("floating-", "")
253+
$(this).height(newHeight)
254+
let newDimensions = currentFloatingTerminalObjs[visible.attr("id").replace("floating-", "")].fitObj.proposeDimensions()
255+
256+
currentFloatingTerminalObjs[terminalId].clientSocket.send(`resize-window:cols=${newDimensions.cols}&rows=${newDimensions.rows}`)
257+
// localfitAddon.fit() just didn't work
258+
currentFloatingTerminalObjs[terminalId].termObj._core._renderService.clear();
259+
currentFloatingTerminalObjs[terminalId].termObj.resize(newDimensions.cols, newDimensions.rows)
260+
});
261+
});
262+
263+
function checkTerminalStatus(terminalId, callback){
264+
$.ajax({
265+
type: "POST",
266+
dataType: 'json',
267+
contentType: 'application/json',
268+
url: '/terminals/checkStatus',
269+
data: JSON.stringify({
270+
terminalId: terminalId
271+
}),
272+
success: callback,
273+
error: callback,
274+
dataType: "json"
275+
});
276+
}

src/assets/lxdMosaic/globalFunctions.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ var toolTipsBytesCallbacks = {
208208

209209
var monthsNameArray = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"];
210210

211-
https://stackoverflow.com/questions/1484506/random-color-generator/32124533
211+
//https://stackoverflow.com/questions/1484506/random-color-generator/32124533
212212
function randomColor(format = 'hex') {
213213
return '#2ecc71'
214214
const rnd = Math.random().toString(16).slice(-6);
@@ -220,3 +220,15 @@ function randomColor(format = 'hex') {
220220
return `rgb(${r}, ${g}, ${b})`;
221221
}
222222
}
223+
224+
//https://stackoverflow.com/a/1349426/4008082
225+
function makeRandomString(length = 10) {
226+
var result = '';
227+
var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
228+
var charactersLength = characters.length;
229+
for ( var i = 0; i < length; i++ ) {
230+
result += characters.charAt(Math.floor(Math.random() *
231+
charactersLength));
232+
}
233+
return result;
234+
}

src/assets/lxdMosaic/styles.css

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,59 @@
11
body {
2-
background-color: #a8a8a8;
2+
background-color: #bfbfbf;
33
font-size: .85rem !important;
44
}
55

66
#sidebar-ul .nav-link:hover {
77
color: #0dcaf0 !important;
88
}
99

10+
#floatingWindowConsole {
11+
min-height: 200;
12+
position: fixed;
13+
bottom:0%;
14+
width:80%;
15+
background-color: #000;
16+
opacity: 1;
17+
z-index: 999999999999999999999999999;
18+
19+
}
20+
21+
#floatingConsoleInstanceTabs .nav-link, #floatingConsoleActionTabs .nav-link {
22+
color: white !important;
23+
}
24+
#sidebarFooter {
25+
height:auto;
26+
position: fixed;
27+
bottom:0%;
28+
width: inherit;
29+
background-color: #293b4d;
30+
opacity: 1;
31+
padding: .5rem;
32+
max-height: 10vh;
33+
}
34+
35+
#sidebar-ul {
36+
height: 90vh;
37+
max-height: 90vh;
38+
overflow-y: auto;
39+
}
40+
#sidebar-ul li {
41+
padding-left: .5rem;
42+
}
43+
44+
.ui-resizable { position: relative;}
45+
.ui-resizable-handle { position: absolute;font-size: 0.1px;z-index: 99999; display: block;}
46+
.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
47+
.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0px; }
48+
.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0px; }
49+
.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0px; height: 100%; }
50+
.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0px; height: 100%; }
51+
.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
52+
.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
53+
.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
54+
.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}
55+
.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
56+
1057
.provider {
1158
cursor: pointer;
1259
}

0 commit comments

Comments
 (0)