-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
263 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,79 +1,164 @@ | ||
// JS File | ||
'use strict'; | ||
|
||
(function () { | ||
const root = document.getElementById('root') | ||
const nav = root.appendChild(document.createElement(`nav`)) | ||
const navH1 = nav.appendChild(document.createElement(`h1`)) | ||
const navUl = nav.appendChild(document.createElement(`ul`)) | ||
const main = root.appendChild(document.createElement(`div`)) | ||
|
||
// | ||
// This function will query the config and runner API's to get the | ||
// configured servers and then to determine which ones are running | ||
// or not. It should be used to render a list of processes and | ||
// display if it's running or not. | ||
// | ||
const fetchServers = async() => { | ||
const response = {}; | ||
const App = () => { | ||
// Store state for the server list | ||
const serverListState = van.state([]) | ||
|
||
// TODO: Store this in local session or URL or something. | ||
const selectedServerState = van.state(null) | ||
|
||
// This loads the current configured servers and their running state, then it updates | ||
// the serverListState to update the rendered list. | ||
const loadServers = (async () => { | ||
const configs = await (await fetch('/api/config')).json() | ||
const runners = await (await fetch('/api/runner')).json() | ||
|
||
// Go through configs to build response containing some info | ||
// about the service and it's current status. | ||
const tmpServerListState = [] | ||
|
||
for (const serverName in configs.servers) { | ||
response[serverName] = { | ||
tmpServerListState.push({ | ||
name: serverName, | ||
cmd: configs.servers[serverName].cmd, | ||
cwd: configs.servers[serverName].cwd, | ||
isRunning: serverName in runners, | ||
running: (serverName in runners), | ||
stdout: runners[serverName] ? runners[serverName].stdout : [], | ||
stderr: runners[serverName] ? runners[serverName].stderr : [], | ||
}) | ||
} | ||
|
||
serverListState.val = tmpServerListState | ||
}) | ||
|
||
// Actually load the state | ||
loadServers() | ||
|
||
// And refresh the state every second | ||
setInterval(loadServers, 1000) | ||
|
||
// Update serverListState for the object with the name of `name` to running state of `state` | ||
const setServerListStateFor = (name, state) => { | ||
const tmpServerListState = [] | ||
|
||
for (const item of serverListState.val) { | ||
if (item.name === name) { | ||
item.running = state | ||
} | ||
|
||
tmpServerListState.push(item) | ||
} | ||
|
||
return response | ||
serverListState.val = tmpServerListState | ||
} | ||
|
||
// | ||
// This function actually clears the UL tag from items and then | ||
// creates new list items to update the menu. | ||
// | ||
const renderMenu = async() => { | ||
const servers = await fetchServers() | ||
// Get the running state of the server with the name of `name` | ||
const getServerListStateFor = (name) => { | ||
for (const item of serverListState.val) { | ||
if (item.name === name) { | ||
return item.running | ||
} | ||
} | ||
|
||
return false | ||
} | ||
|
||
if (navUl.textContent === "🔃") { | ||
// Empty the navigation list before we re-render it. | ||
navUl.textContent = "" | ||
// Object to render the actual items in the server list | ||
const ServerItem = (name) => { | ||
const toggleServer = async () => { | ||
if (getServerListStateFor(name)) { | ||
await fetch(`/api/runner/${name}`, { method: 'DELETE' }) | ||
setServerListStateFor(name, false) | ||
return | ||
} | ||
|
||
await fetch(`/api/runner/${name}`, { method: 'POST' }) | ||
setServerListStateFor(name, true) | ||
} | ||
|
||
// Go through all servers and add them to the navigation list. | ||
for (const serverName in servers) { | ||
// Try to select the previously selecting element | ||
let li = document.getElementById("li-server-" + serverName) | ||
return van.tags.li( | ||
{ | ||
class: () => selectedServerState.val === name ? 'server-item selected' : 'server-item', | ||
onclick: () => { selectedServerState.val = name } | ||
}, | ||
name, | ||
van.tags.label( | ||
{ class: 'switch', for: 'toggle-' + name }, | ||
van.tags.input({ | ||
type: 'checkbox', | ||
id: 'toggle-' + name, | ||
checked: getServerListStateFor(name), | ||
onclick: () => toggleServer(), | ||
}), | ||
van.tags.div({ class: 'slider' }) | ||
) | ||
) | ||
} | ||
|
||
// Derive the server list state into a list of items to render | ||
const serverList = van.derive(() => van.tags.ul( | ||
{ class: 'server-list' }, | ||
serverListState.val.map((item) => ServerItem(item.name)) | ||
)) | ||
|
||
// Derive the selection state to render the main viewer | ||
const mainViewer = van.derive(() => { | ||
const stderrViewer = (name) => { | ||
let stderrLogLines = [] | ||
|
||
// Loop through serverListState until you find the server with the name of `name` | ||
for (const item of serverListState.val) { | ||
if (item.name === name && item.stderr !== null) { | ||
stderrLogLines = item.stderr | ||
|
||
// If it doesn't exist, create it. | ||
if (!li) { | ||
li = navUl.appendChild(document.createElement(`li`)) | ||
li.id = "li-server-" + serverName | ||
break | ||
} | ||
} | ||
|
||
// Prepare checked="" attribute for input type checkbox. | ||
const checkStatus = servers[serverName].isRunning ? 'checked=""' : '' | ||
return van.tags.div( | ||
van.tags.div( | ||
{ id: 'stderr' }, | ||
stderrLogLines.map((line) => van.tags.div(line)) | ||
) | ||
) | ||
} | ||
const stdoutViewer = (name) => { | ||
let stdoutLogLines = [] | ||
|
||
// Loop through serverListState until you find the server with the name of `name` | ||
for (const item of serverListState.val) { | ||
if (item.name === name && item.stdout !== null) { | ||
stdoutLogLines = item.stdout | ||
|
||
break | ||
} | ||
} | ||
|
||
// Update the content | ||
li.innerHTML = ` | ||
<span>${serverName}</span> - <input type="checkbox" ${checkStatus} /> | ||
` | ||
return van.tags.div( | ||
van.tags.div( | ||
{ id: 'stdout' }, | ||
stdoutLogLines.map((line) => van.tags.div(line)) | ||
) | ||
) | ||
} | ||
} | ||
|
||
nav.id = "nav" | ||
navH1.textContent = "navigation" | ||
navUl.textContent = "🔃" | ||
main.textContent = "main viewer" | ||
main.id = "content" | ||
return van.tags.div( | ||
{ id: 'content' }, | ||
(!selectedServerState.val) ? van.tags.div({ id: 'frontpage' }, 'Select a server to view its logs :)') : null, | ||
(!!selectedServerState.val) ? stderrViewer(selectedServerState.val) : null, | ||
(!!selectedServerState.val) ? stdoutViewer(selectedServerState.val) : null, | ||
) | ||
}) | ||
|
||
return van.tags.div( | ||
{ id: 'wrapper' }, | ||
van.tags.nav( | ||
{ id: 'nav' }, | ||
van.tags.h1( | ||
{ onclick: () => { selectedServerState.val = null } }, | ||
'goprocmgr' | ||
), | ||
serverList | ||
), | ||
mainViewer, | ||
) | ||
} | ||
|
||
// Render the menu and keep updating it every now and then. | ||
renderMenu() | ||
setInterval(renderMenu, 5000) | ||
})() | ||
van.add(document.getElementById('app'), App()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,15 +1,125 @@ | ||
/* CSS file */ | ||
/* Skeleton layout */ | ||
html { height: 100%; } | ||
body { height: 100%; margin: 0; } | ||
#root { height: 100%; } | ||
#root nav#nav { | ||
float: left; | ||
html { | ||
height: 100%; | ||
width: 20%; | ||
} | ||
#root div#content { | ||
float: left; | ||
|
||
body { | ||
background-color: #ffffff; | ||
color: #000000; | ||
font-family: monospace; | ||
height: 100%; | ||
width: 80%; | ||
margin: 0; | ||
padding: 0; | ||
} | ||
|
||
@media (prefers-color-scheme: dark) { | ||
body { | ||
background-color: #333333; | ||
color: #ffffff; | ||
} | ||
} | ||
|
||
#app { | ||
display: flex; | ||
height: 100%; | ||
} | ||
|
||
#wrapper { | ||
display: flex; | ||
width: 100%; | ||
} | ||
|
||
#app nav#nav { | ||
background-color: #222222; | ||
border-right: 1px solid black; | ||
width: 300px; | ||
} | ||
|
||
#app nav#nav h1 { | ||
border-bottom: 1px solid black; | ||
color: #ffffff; | ||
font-size: 26px; | ||
font-weight: normal; | ||
margin: 0; | ||
padding: 18px 8px; | ||
text-align: center; | ||
user-select: none; | ||
} | ||
|
||
#app nav#nav ul { | ||
list-style-type: none; | ||
margin: 0; | ||
padding: 0; | ||
} | ||
|
||
#app nav#nav ul li { | ||
line-height: 34px; | ||
font-size: 18px; | ||
padding: 8px; | ||
user-select: none; | ||
} | ||
|
||
#app nav#nav ul li.selected { | ||
background-color: #aaaaaa; | ||
} | ||
|
||
@media (prefers-color-scheme: dark) { | ||
#app nav#nav ul li.selected { | ||
background-color: #3b3b3b; | ||
} | ||
} | ||
|
||
#app div#content { | ||
flex: 1; | ||
padding: 8px; | ||
} | ||
|
||
#app div#content div#frontpage { | ||
align-items: center; | ||
display: flex; | ||
font-size: 40px; | ||
height: 100%; | ||
justify-content: center; | ||
padding: 0 20px; | ||
text-align: center; | ||
} | ||
|
||
/* Nice toggle switch for input boxes */ | ||
#app nav#nav ul li.server-item label.switch { | ||
float: right; | ||
height: 34px; | ||
position: relative; | ||
width: 60px; | ||
} | ||
|
||
#app nav#nav ul li.server-item label.switch input { | ||
display: none; | ||
} | ||
|
||
#app nav#nav ul li.server-item label.switch div.slider { | ||
background-color: #cccccc; | ||
bottom: 0; | ||
cursor: pointer; | ||
left: 0; | ||
position: absolute; | ||
right: 0; | ||
top: 0; | ||
} | ||
|
||
#app nav#nav ul li.server-item label.switch div.slider:before { | ||
background-color: #ffffff; | ||
bottom: 4px; | ||
content: ""; | ||
height: 26px; | ||
left: 4px; | ||
position: absolute; | ||
width: 26px; | ||
} | ||
|
||
#app nav#nav ul li.server-item label.switch input:checked+div.slider { | ||
background-color: #66bb6a; | ||
} | ||
|
||
#app nav#nav ul li.server-item label.switch input:checked+div.slider:before { | ||
transform: translateX(26px); | ||
} |