Skip to content

Commit

Permalink
static: Rewrite in VanJS
Browse files Browse the repository at this point in the history
  • Loading branch information
etu committed Mar 30, 2024
1 parent d1743fb commit 25100fb
Show file tree
Hide file tree
Showing 3 changed files with 263 additions and 68 deletions.
6 changes: 3 additions & 3 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
<html lang="en">
<head>
<title>goprocmgr</title>
<meta charset="utf-8" />
<link rel="stylesheet" type="text/css" href="/web/style.css" />
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="/web/style.css">
</head>
<body>
<div id="root"></div>
<div id="app"></div>
<script src="/web/van-1.5.0.nomodule.min.js"></script>
<script src="/web/script.js"></script>
</body>
Expand Down
195 changes: 140 additions & 55 deletions static/script.js
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());
130 changes: 120 additions & 10 deletions static/style.css
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);
}

0 comments on commit 25100fb

Please sign in to comment.