Skip to content

Commit 530be11

Browse files
committed
Improved the log display logic, and added a keyboard shortcut
1 parent 1fa919e commit 530be11

File tree

4 files changed

+93
-38
lines changed

4 files changed

+93
-38
lines changed

client/apiclient.js

Lines changed: 40 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
function apiclient (baseurl) {
44
baseurl = baseurl || ''
55

6-
function request (url, method, opts, successHandler, errorHandler) {
6+
function createfetcher (url, method, opts) {
77
let requesturl = baseurl + url
88

99
const requestopts = {
@@ -21,41 +21,60 @@ function apiclient (baseurl) {
2121
}
2222
}
2323

24-
fetch(requesturl, requestopts)
24+
return fetch(requesturl, requestopts)
2525
.then(function (response) {
2626
// Replicate axios behaviour of a non-ok response being an error
2727
if (!response.ok) {
2828
const errorvalue = new Error('Error status returned:' + response.status)
2929
errorvalue.response = response
3030
throw errorvalue
3131
} else {
32-
// Only .text() is okay with an empty response. Later, we will parse it
33-
// into valid json.
34-
// This is a promise, so gets taken care of in the next .then
35-
return response.text()
32+
return response
3633
}
3734
})
38-
.then(function (responseData) {
39-
// Replicate axios behaviour of response body being sent back in
40-
// data property
41-
const responseBody = responseData ? { data: JSON.parse(responseData) } : {}
42-
successHandler(responseBody)
43-
})
44-
.catch(function (error) {
45-
errorHandler(error)
46-
})
4735
}
4836

49-
function get (url, opts, successHandler, errorHandler, progressHandler) {
50-
request(url, 'GET', opts, successHandler, errorHandler)
37+
function requestJSON (url, method, opts, successHandler, errorHandler) {
38+
const fetcher = createfetcher(url, method, opts)
39+
fetcher.then(function (response) {
40+
// Only .text() is okay with an empty response. Later, we will parse it
41+
// into valid json.
42+
// This is a promise, so gets taken care of in the next .then
43+
return response.text()
44+
}).then(function (responseData) {
45+
// Replicate axios behaviour of response body being sent back in
46+
// data property
47+
const responseBody = responseData ? { data: JSON.parse(responseData) } : {}
48+
successHandler(responseBody)
49+
}).catch(function (error) {
50+
errorHandler(error)
51+
})
52+
}
53+
54+
function requestBuffer (url, method, opts, successHandler, errorHandler) {
55+
const fetcher = createfetcher(url, method, opts)
56+
fetcher.then(function (response) {
57+
return response.arrayBuffer()
58+
}).then(function (responseData) {
59+
// Replicate axios behaviour of response body being sent back in
60+
// data property
61+
const responseBody = responseData ? { data: responseData } : {}
62+
successHandler(responseBody)
63+
}).catch(function (error) {
64+
errorHandler(error)
65+
})
66+
}
67+
68+
function get (url, opts, successHandler, errorHandler) {
69+
requestJSON(url, 'GET', opts, successHandler, errorHandler)
5170
}
5271

5372
function post (url, opts, successHandler, errorHandler) {
54-
request(url, 'POST', opts, successHandler, errorHandler)
73+
requestJSON(url, 'POST', opts, successHandler, errorHandler)
5574
}
5675

5776
function deleteverb (url, opts, successHandler, errorHandler) {
58-
request(url, 'DELETE', opts, successHandler, errorHandler)
77+
requestJSON(url, 'DELETE', opts, successHandler, errorHandler)
5978
}
6079

6180
this.listcontainers = function (opts, successHandler, errorHandler) {
@@ -83,12 +102,12 @@ function apiclient (baseurl) {
83102
get('/containers/' + name + '/top', opts, successHandler, errorHandler)
84103
}
85104

86-
this.logscontainer = function (name, opts, successHandler, errorHandler, progressHandler) {
105+
this.logscontainer = function (name, opts, successHandler, errorHandler) {
87106
opts = opts || {}
88107
opts.stdout = true
89108
opts.stderr = true
90109

91-
get('/containers/' + name + '/logs', opts, successHandler, errorHandler, progressHandler)
110+
requestBuffer('/containers/' + name + '/logs', 'GET', opts, successHandler, errorHandler)
92111
}
93112

94113
this.startcontainer = function (name, opts, successHandler, errorHandler) {

client/commands.js

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,23 +108,56 @@ function commands (world) {
108108
world.apiClient.logscontainer(
109109
container.name(),
110110
{},
111-
function () { },
112-
function () { },
113-
function onLogsProgress (event) { dialog.html(formatResponse(event.currentTarget.responseText)) })
114-
115-
function formatResponse (respText) {
116-
const respArray = respText.split('\n')
117-
const formattedArray = respArray.map(function formatter (index) {
118-
const firstchar = index.charCodeAt(0)
119-
let result = index.substring(2)
120-
if (firstchar === 1) {
121-
result = '<div class="stdout">STDOUT:' + result + '</div>'
122-
} else if (firstchar === 2) {
123-
result = '<div class="stdout">STDERR:' + result + '</div>'
111+
function onLogsSuccess (success) {
112+
if (success && (success.data instanceof ArrayBuffer)) {
113+
const logdata = processLogdata(success.data)
114+
dialog.html(logdata)
115+
} else {
116+
dialog.html('Could not fetch logs.')
124117
}
125-
return result
126-
})
127-
return (formattedArray.join(''))
118+
},
119+
onRequestError
120+
)
121+
122+
function processLogdata (databuffer) {
123+
// This function processes a Docker container log retrieved
124+
// via the API, as described in the following article:
125+
// https://ahmet.im/blog/docker-logs-api-binary-format-explained/
126+
// Many thanks to the author.
127+
const byteArray = new Uint8Array(databuffer)
128+
const records = []
129+
const decoder = new TextDecoder()
130+
131+
let currentIndex = 0;
132+
while (currentIndex < byteArray.length) {
133+
// Read the header
134+
// First byte contains record type. 1 is STDOUT, 2 is STDERR
135+
const type = byteArray[currentIndex];
136+
// Fifth through eightth bytes contain big-ending integer
137+
// which is the length of the message
138+
const recordLength = (byteArray[currentIndex + 4] << 24) |
139+
(byteArray[currentIndex + 5] << 16) |
140+
(byteArray[currentIndex + 6] << 8) |
141+
byteArray[currentIndex + 7];
142+
143+
if (recordLength > 0) {
144+
// Extract the message
145+
const messageBytes = byteArray.slice(currentIndex + 8, currentIndex + 8 + recordLength);
146+
147+
const message = decoder.decode(messageBytes)
148+
const newelement = document.createElement('div')
149+
newelement.setAttribute('data-type', type)
150+
newelement.classList.add('logmessage')
151+
newelement.innerText = message
152+
const record = newelement.outerHTML
153+
records.push(record);
154+
}
155+
156+
// Move to the next record
157+
currentIndex += 8 + recordLength;
158+
}
159+
160+
return records.join('')
128161
}
129162
},
130163
'containercommand')
@@ -301,6 +334,7 @@ function commands (world) {
301334
],
302335
['R key', 'toggle between first-person and third-person views.'],
303336
['I key', 'to inspect the container in front of you. You have to stand right in front of a container.'],
337+
['L key', 'to see the logs of the container in front of you. You have to stand right in front of a container.'],
304338
[
305339
'Esc key',
306340
'remove focus from the window. You will have to click the window again for things to work. Try not to press Esc :)'

client/gameconsole.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const dockergameconsole = function (world) {
99

1010
voxelconsole.keys.down.on('openconsole', openConsole)
1111
voxelconsole.keys.down.on('inspect', function () { processInput('inspect', true) })
12+
voxelconsole.keys.down.on('logs', function () { processInput('logs', true) })
1213

1314
widget.on('input', processInput)
1415

client/world.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ const world = function (opts) {
3939
'<control>': 'alt',
4040
'`': 'openconsole',
4141
I: 'inspect',
42-
R: 'pov'
42+
R: 'pov',
43+
L: 'logs'
4344
}
4445
opts.parentElement = opts.parentElement || document.body
4546
opts.container = opts.parentElement

0 commit comments

Comments
 (0)