|
Packit |
90a5c9 |
--[[
|
|
Packit |
90a5c9 |
Licensed to the Apache Software Foundation (ASF) under one
|
|
Packit |
90a5c9 |
or more contributor license agreements. See the NOTICE file
|
|
Packit |
90a5c9 |
distributed with this work for additional information
|
|
Packit |
90a5c9 |
regarding copyright ownership. The ASF licenses this file
|
|
Packit |
90a5c9 |
to you under the Apache License, Version 2.0 (the
|
|
Packit |
90a5c9 |
"License"); you may not use this file except in compliance
|
|
Packit |
90a5c9 |
with the License. You may obtain a copy of the License at
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
http://www.apache.org/licenses/LICENSE-2.0
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
Unless required by applicable law or agreed to in writing,
|
|
Packit |
90a5c9 |
software distributed under the License is distributed on an
|
|
Packit |
90a5c9 |
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
Packit |
90a5c9 |
KIND, either express or implied. See the License for the
|
|
Packit |
90a5c9 |
specific language governing permissions and limitations
|
|
Packit |
90a5c9 |
under the License.
|
|
Packit |
90a5c9 |
]]
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
--[[ mod_lua implementation of the server-status page ]]
|
|
Packit |
90a5c9 |
local ssversion = "0.11" -- verion of this script
|
|
Packit |
90a5c9 |
local redact_ips = true -- whether to replace the last two bits of every IP with 'x.x'
|
|
Packit |
90a5c9 |
local warning_banner = [[
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
Don't be alarmed - this page is here for a reason!
|
|
Packit |
90a5c9 |
This is an example server status page for the Apache HTTP Server. Nothing on this server is secret, no URL tokens, no sensitive passwords. Everything served from here is static data.
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
]]
|
|
Packit |
90a5c9 |
local show_warning = true -- whether to display the above warning/notice on the page
|
|
Packit |
90a5c9 |
local show_modules = false -- Whether to list loaded modules or not
|
|
Packit |
90a5c9 |
local show_threads = true -- whether to list thread information or not
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
-- pre-declare some variables defined at the bottom of this script:
|
|
Packit |
90a5c9 |
local status_js, status_css, quokka_js
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
-- quick and dirty JSON conversion
|
|
Packit |
90a5c9 |
local function quickJSON(input)
|
|
Packit |
90a5c9 |
if type(input) == "table" then
|
|
Packit |
90a5c9 |
local t = 'array'
|
|
Packit |
90a5c9 |
for k, v in pairs(input) do
|
|
Packit |
90a5c9 |
if type(k) ~= "number" then
|
|
Packit |
90a5c9 |
t = 'hash'
|
|
Packit |
90a5c9 |
break
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
if t == 'hash' then
|
|
Packit |
90a5c9 |
local out = ""
|
|
Packit |
90a5c9 |
local tbl = {}
|
|
Packit |
90a5c9 |
for k, v in pairs(input) do
|
|
Packit |
90a5c9 |
local kv = ([["%s": %s]]):format(k, quickJSON(v))
|
|
Packit |
90a5c9 |
table.insert(tbl, kv)
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
return "{" .. table.concat(tbl, ", ") .. "}"
|
|
Packit |
90a5c9 |
else
|
|
Packit |
90a5c9 |
local tbl = {}
|
|
Packit |
90a5c9 |
for k, v in pairs(input) do
|
|
Packit |
90a5c9 |
table.insert(tbl, quickJSON(v))
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
return "[" .. table.concat(tbl, ", ") .. "]"
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
elseif type(input) == "string" then
|
|
Packit |
90a5c9 |
return ([["%s"]]):format(input:gsub('"', '\\"'):gsub("[\r\n\t]", " "))
|
|
Packit |
90a5c9 |
elseif type(input) == "number" then
|
|
Packit |
90a5c9 |
return tostring(input)
|
|
Packit |
90a5c9 |
elseif type(input) == "boolean" then
|
|
Packit |
90a5c9 |
return (input and "true" or "false")
|
|
Packit |
90a5c9 |
else
|
|
Packit |
90a5c9 |
return "null"
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
-- Module information callback
|
|
Packit |
90a5c9 |
local function modInfo(r, modname)
|
|
Packit |
90a5c9 |
if modname then
|
|
Packit |
90a5c9 |
r:puts [[
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
<html>
|
|
Packit |
90a5c9 |
<head>
|
|
Packit |
90a5c9 |
<meta charset="utf-8">
|
|
Packit |
90a5c9 |
<style>
|
|
Packit |
90a5c9 |
]]
|
|
Packit |
90a5c9 |
r:puts (status_css)
|
|
Packit |
90a5c9 |
r:puts [[
|
|
Packit |
90a5c9 |
</style>
|
|
Packit |
90a5c9 |
<title>Module information</title>
|
|
Packit |
90a5c9 |
</head>
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
<body>
|
|
Packit |
90a5c9 |
]]
|
|
Packit |
90a5c9 |
r:puts( ("Details for module %s\n"):format(r:escape_html(modname)) )
|
|
Packit |
90a5c9 |
-- Queries the server for information about a module
|
|
Packit |
90a5c9 |
local mod = r.module_info(modname)
|
|
Packit |
90a5c9 |
if mod then
|
|
Packit |
90a5c9 |
for k, v in pairs(mod.commands) do
|
|
Packit |
90a5c9 |
-- print out all directives accepted by this module
|
|
Packit |
90a5c9 |
r:puts( ("%s: %s \n"):format(r:escape_html(k), v))
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
-- HTML tail
|
|
Packit |
90a5c9 |
r:puts[[
|
|
Packit |
90a5c9 |
</body>
|
|
Packit |
90a5c9 |
</html>
|
|
Packit |
90a5c9 |
]]
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
-- Function for generating server stats
|
|
Packit |
90a5c9 |
function getServerState(r, verbose)
|
|
Packit |
90a5c9 |
local state = {}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
state.mpm = {
|
|
Packit |
90a5c9 |
type = "prefork", -- default to prefork until told otherwise
|
|
Packit |
90a5c9 |
threadsPerChild = 1,
|
|
Packit |
90a5c9 |
threaded = false,
|
|
Packit |
90a5c9 |
maxServers = r.mpm_query(12),
|
|
Packit |
90a5c9 |
activeServers = 0
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if r.mpm_query(14) == 1 then
|
|
Packit |
90a5c9 |
state.mpm.type = "event" -- this is event mpm
|
|
Packit |
90a5c9 |
elseif r.mpm_query(3) >= 1 then
|
|
Packit |
90a5c9 |
state.mpm.type = "worker" -- it's not event, but it's threaded, we'll assume worker mpm (could be motorz??)
|
|
Packit |
90a5c9 |
elseif r.mpm_query(2) == 1 then
|
|
Packit |
90a5c9 |
state.mpm.type = "winnt" -- it's threaded, but not worker nor event, so it's probably winnt
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
if state.mpm.type ~= "prefork" then
|
|
Packit |
90a5c9 |
state.mpm.threaded = true -- it's threaded
|
|
Packit |
90a5c9 |
state.mpm.threadsPerChild = r.mpm_query(6) -- get threads per child proc
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
state.processes = {} -- list of child procs
|
|
Packit |
90a5c9 |
state.connections = { -- overall connection info
|
|
Packit |
90a5c9 |
idle = 0,
|
|
Packit |
90a5c9 |
active = 0
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
-- overall server stats
|
|
Packit |
90a5c9 |
state.server = {
|
|
Packit |
90a5c9 |
connections = 0,
|
|
Packit |
90a5c9 |
bytes = 0,
|
|
Packit |
90a5c9 |
built = r.server_built,
|
|
Packit |
90a5c9 |
localtime = os.time(),
|
|
Packit |
90a5c9 |
uptime = os.time() - r.started,
|
|
Packit |
90a5c9 |
version = r.banner,
|
|
Packit |
90a5c9 |
host = r.server_name,
|
|
Packit |
90a5c9 |
modules = nil,
|
|
Packit |
90a5c9 |
extended = show_threads, -- whether extended status is available or not
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
-- if show_modules is true, add list of modules to the JSON
|
|
Packit |
90a5c9 |
if show_modules then
|
|
Packit |
90a5c9 |
state.server.modules = {}
|
|
Packit |
90a5c9 |
for k, module in pairs(r:loaded_modules()) do
|
|
Packit |
90a5c9 |
table.insert(state.server.modules, module)
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
-- Fetch process/thread data
|
|
Packit |
90a5c9 |
for i=0,state.mpm.maxServers-1,1 do
|
|
Packit |
90a5c9 |
local server = r.scoreboard_process(r, i);
|
|
Packit |
90a5c9 |
if server then
|
|
Packit |
90a5c9 |
local s = {
|
|
Packit |
90a5c9 |
active = false,
|
|
Packit |
90a5c9 |
pid = nil,
|
|
Packit |
90a5c9 |
bytes = 0,
|
|
Packit |
90a5c9 |
stime = 0,
|
|
Packit |
90a5c9 |
utime = 0,
|
|
Packit |
90a5c9 |
connections = 0,
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
local tstates = {}
|
|
Packit |
90a5c9 |
if server.pid then
|
|
Packit |
90a5c9 |
state.connections.idle = state.connections.idle + (server.keepalive or 0)
|
|
Packit |
90a5c9 |
s.connections = 0
|
|
Packit |
90a5c9 |
if server.pid > 0 then
|
|
Packit |
90a5c9 |
state.mpm.activeServers = state.mpm.activeServers + 1
|
|
Packit |
90a5c9 |
s.active = true
|
|
Packit |
90a5c9 |
s.pid = server.pid
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
for j = 0, state.mpm.threadsPerChild-1, 1 do
|
|
Packit |
90a5c9 |
local worker = r.scoreboard_worker(r, i, j)
|
|
Packit |
90a5c9 |
if worker then
|
|
Packit |
90a5c9 |
s.stime = s.stime + (worker.stimes or 0);
|
|
Packit |
90a5c9 |
s.utime = s.utime + (worker.utimes or 0);
|
|
Packit |
90a5c9 |
if verbose and show_threads then
|
|
Packit |
90a5c9 |
s.threads = s.threads or {}
|
|
Packit |
90a5c9 |
table.insert(s.threads, {
|
|
Packit |
90a5c9 |
bytes = worker.bytes_served,
|
|
Packit |
90a5c9 |
thread = ("0x%x"):format(worker.tid),
|
|
Packit |
90a5c9 |
client = redact_ips and (worker.client or "???"):gsub("[a-f0-9]+[.:]+[a-f0-9]+$", "x.x") or worker.client or "???",
|
|
Packit |
90a5c9 |
cost = ((worker.utimes or 0) + (worker.stimes or 0)),
|
|
Packit |
90a5c9 |
count = worker.access_count,
|
|
Packit |
90a5c9 |
vhost = worker.vhost:gsub(":%d+", ""),
|
|
Packit |
90a5c9 |
request = worker.request,
|
|
Packit |
90a5c9 |
last_used = math.floor(worker.last_used/1000000)
|
|
Packit |
90a5c9 |
})
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
state.server.connections = state.server.connections + worker.access_count
|
|
Packit |
90a5c9 |
s.bytes = s.bytes + worker.bytes_served
|
|
Packit |
90a5c9 |
s.connections = s.connections + worker.access_count
|
|
Packit |
90a5c9 |
if server.pid > 0 then
|
|
Packit |
90a5c9 |
tstates[worker.status] = (tstates[worker.status] or 0) + 1
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
s.workerStates = {
|
|
Packit |
90a5c9 |
keepalive = (server.keepalive > 0) and server.keepalive or tstates[5] or 0,
|
|
Packit |
90a5c9 |
closing = tstates[8] or 0,
|
|
Packit |
90a5c9 |
idle = tstates[2] or 0,
|
|
Packit |
90a5c9 |
writing = tstates[4] or 0,
|
|
Packit |
90a5c9 |
reading = tstates[3] or 0,
|
|
Packit |
90a5c9 |
graceful = tstates[9] or 0
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
table.insert(state.processes, s)
|
|
Packit |
90a5c9 |
state.server.bytes = state.server.bytes + s.bytes
|
|
Packit |
90a5c9 |
state.connections.active = state.connections.active + (tstates[8] or 0) + (tstates[4] or 0) + (tstates[3] or 0)
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
return state
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
-- Handler function
|
|
Packit |
90a5c9 |
function handle(r)
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
-- Parse GET data, if any, and set content type
|
|
Packit |
90a5c9 |
local GET = r:parseargs()
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
if GET['module'] then
|
|
Packit |
90a5c9 |
modInfo(r, GET['module'])
|
|
Packit |
90a5c9 |
return apache2.OK
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
-- If we only need the stats feed, compact it and hand it over
|
|
Packit |
90a5c9 |
if GET['view'] and GET['view'] == "json" then
|
|
Packit |
90a5c9 |
local state = getServerState(r, GET['extended'] == 'true')
|
|
Packit |
90a5c9 |
r.content_type = "application/json"
|
|
Packit |
90a5c9 |
r:puts(quickJSON(state))
|
|
Packit |
90a5c9 |
return apache2.OK
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
if not GET['resource'] then
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
local state = getServerState(r, show_threads)
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
-- Print out the HTML for the front page
|
|
Packit |
90a5c9 |
r.content_type = "text/html"
|
|
Packit |
90a5c9 |
r:puts ( ([=[
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
<html>
|
|
Packit |
90a5c9 |
<head>
|
|
Packit |
90a5c9 |
<meta charset="utf-8">
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
<link href="?resource=css" rel="stylesheet">
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
<script type="text/javascript" src="?resource=js"></script>
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
<title>Server status for %s</title>
|
|
Packit |
90a5c9 |
</head>
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
<body onload="refreshCharts(false);">
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
Apache HTTPd
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
Status for %s on %s
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
Dashboard
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
Server Info
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
Show thread information
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
Show loaded modules
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
%s
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
Quick Stats
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
Charts
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
<canvas id="actions_div" width="1400" height="400" class="canvas_wide"></canvas>
|
|
Packit |
90a5c9 |
<canvas id="status_div" width=580" height="400" class="canvas_narrow"></canvas>
|
|
Packit |
90a5c9 |
<canvas id="traffic_div" width="1400" height="400" class="canvas_wide"></canvas>
|
|
Packit |
90a5c9 |
<canvas id="idle_div" width="580" height="400" class="canvas_narrow"></canvas>
|
|
Packit |
90a5c9 |
<canvas id="connection_div" width="1400" height="400" class="canvas_wide"></canvas>
|
|
Packit |
90a5c9 |
<canvas id="cpu_div" width="580" height="400" class="canvas_narrow"></canvas>
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
General server information
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
Thread breakdown
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
Modules loaded
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
blabla
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
]=]):format(
|
|
Packit |
90a5c9 |
r.server_name,
|
|
Packit |
90a5c9 |
r.banner,
|
|
Packit |
90a5c9 |
r.server_name,
|
|
Packit |
90a5c9 |
show_warning and warning_banner or ""
|
|
Packit |
90a5c9 |
) );
|
|
Packit |
90a5c9 |
-- HTML tail
|
|
Packit |
90a5c9 |
r:puts[[
|
|
Packit |
90a5c9 |
</body>
|
|
Packit |
90a5c9 |
</html>
|
|
Packit |
90a5c9 |
]]
|
|
Packit |
90a5c9 |
else
|
|
Packit |
90a5c9 |
-- Resource documents (CSS, JS, PNG)
|
|
Packit |
90a5c9 |
if GET['resource'] == 'js' then
|
|
Packit |
90a5c9 |
r.content_type = "application/javascript"
|
|
Packit |
90a5c9 |
r:puts(quokka_js)
|
|
Packit |
90a5c9 |
r:puts(status_js)
|
|
Packit |
90a5c9 |
elseif GET['resource'] == 'css' then
|
|
Packit |
90a5c9 |
r.content_type = "text/css"
|
|
Packit |
90a5c9 |
r:puts(status_css)
|
|
Packit |
90a5c9 |
elseif GET['resource'] == 'feather' then
|
|
Packit |
90a5c9 |
r.content_type = "image/png"
|
|
Packit |
90a5c9 |
r:write(r:base64_decode('iVBORw0KGgoAAAANSUhEUgAAACUAAABACAYAAACdp77qAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4QEWECwoSXwjUAAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAlvSURBVGje7Zl7cFXVFcZ/a50bHhIRAQWpICSEgGKEUKAUgqKDWsBBHBFndKzYKdAWlWkDAlUEkfIogyAUxfqqdYqP1scg2mq1QLCiIC8LhEeCPDQwoWAgBHLvOXv1j3PvJQRQAjfgH90zmXvu3nv2/u73fWutvU/gHLX9C3IBOLCgc9MDz+S+dGB+l6B0Tu7re2d1bgawd0bn5Fw5F4D+uyCXJsNXs//pzi1U5SMg25zgYkYQY4s76ro3H7/2m8R8PRegmgxfTenTnS8R1SIgG0AERAQR2kma/gFgz7Rrah/UvwdfnpCucUR1KVAvLo4hFj4qiNDz6yk56c3Hrqt9UG3aXxbaw/gz0CHebcBhANE4RKW+RrwW50S+yyavtF0P5T7nH6IfxxCVAWlJCUOmVDXsqzVQW+/PAWDXmC53I9wXO0hgQRh8QClQN7G7KKAEiFTWKqiINuTL/Nzmzsk8c4qL4vkV5kRtjXhkiRKYTyyosCBWTix6gIP+odieWgG1eVi30EtzlhNEvfctkItcAC5QjpTI24d3cP2hbRYt24KW7yCtogQvup80d5SSFpO+KN817pray1NbR3Sbqx4jRUE8ANuunlWKWntRQOy4+Wb201bT17xUa8lz833d+4vKG+JRR9Qg/HvGi8gwEUPU4jkqPgZBy2mrI1XXSKl8G+/60UXOl6nmU8fFwPmCxeQFAumf+O58xQWCc4L5ijkmAKzLz0ktqPW39ghliOk0i+nVzhfMBxdjrQukmfn6gxCQ4Pxj4IJA9vlRferw9O5cM3N96kCt+Uk3ct76hPUDe1xvASNCMIKLaWAxPreAvs4H8wXzBRfTquCey5i96sDevdHj1kyJp1b3657uqbdBlFaSyD0ehepZiXj0EQE8IzEW5ibbD35O1oLPv6q+3lkxVdCqF2tv6om/L21YEJVWxxgAF7PnnS95LhaXLaYhg/HxwGd01oLPv9o6ousJ654xUx+37UXPbctZntHrAo3IoUhT57wGRMQDUXtTlXT16EtVdrzEs/tnh5dX9N10b3c6vPhp6kAlTwJZee8BN+Ph6jQzxOMI6h7ROjJL1FCpKhmIx0Y8rqtXP1qa+fyqk1eEswG0PCPvDkNuFgAf9cvwvQa2SOrog64SJBKyg4GYodjbR0t1YRC1uletWHXKdc+IqaVt8vA8GoAsBbokKz4c8RoFz4onw8SjLkrMnPkSUN8CVltMWksailjOl4e/2XXHhg2pAwVQkJE3SFTeqFYvloryDSIDxWGYCRruIl7SU38N6kaH9Fz5qTvV2jWOvmUZvcNfIzqr+pjDppjJQHPgMEElRGRhMrUo5qK8+G2Aagxqaca19C5exrKM3sMNWlcl2rDZgk6oKoIzw6qKYnz648KCxf/pdCMpA3Vt8VKWtO6djsgUA5yBmWAmBzEpFqFXdXeYJebZKudzM8CesrJvP4/V2EyeN8zgYjCEJBMfCfIzi98Fqh9NgM8Cx7O9txeUfZyZR8+igtSAej/jJpRYuqFDwFQAw8WBua0gvSV+KxAST2Bmu0TEU5VGwHcCqpF8Nxb/AyStY4B2C9A4HA+H7gY9YkjjkLtQLhfKiqAtMfaA/0RBZt7pHadPZ9Litv3pv20xvsk4EUHjsikOQ/IV7ylJWtoQXPIuhdm7ecXLBtTEIaedpxZn9WsuTkpUDMzF049txmyeCnMlDiZx0VPMGW6rwGHn3KDrthfsPN29vlO+11vdEuYg5z1sooTSeTgUH53hRGc4BJfsFwzFoQpetiH7agLotOQbvHMRsxoNVMNudxY3sRgBtlPMtTGR+s4szg4IHsdYE4BJNQ3w0zJ66ybaN8BrGIS3RgJTnGmhE69ngEcgHiaKk/g4SoBHgBRGrd6Kf2X2IaVMAQR4XRWrHxaNUCDMPlBkvAAqQhBPAxr3Vdz4T91U/K6r8WX2uya8mjG4rsENAWHUCYpguxH2gFwsOMyMMCrBiZdIDHtx+saZFPtvle/lNkMw1YhDe1jczAGK73Sow5tzzOBKYAlZBRfKO69f8Xu7P7xqQGpB3b39VQInVzu0rksmTN1pKi0c2jiIgwzwsOSzEhibBxS98/iizAHcsOEdUi6fE++2KrkHzP6kovnJs0GyBiaizspA+gPcUvQOKZcvfHfTsI9ZMveUG1IRoO2rMJewt8Wjc8RtxW8WvZlx6xkfs08ANbZF/nHfK6XeD4+SFljola8C0aaGprl46Cc+DXFm3D+46G+vvJZ5O4OK3zpjUCctM4+3ze+LBR+CXZqmXkk9dzRo6Mo9wc0RoYtAL5FE+TUEK4xY5d0rtXNhRummil+W/cXOFNCKNh31OKbym8VZcm4dXmQRGslxCBVaX3wU37n5zqSXQ3CJaHMy+q6ihR12asvmza30nrMBlLRx9Z7JV4zikR2zmdxu9DwxrhWhY/jWJpjfyB00xX4FVgq8fkDS58a0XoM0/IfF7Iox257InZn5gOQXPXlWwE55Snis3ZjOgiwDSxcMM3IFW4WgDm+XYFEPawQ0EXOFmN0wbtusr1PxbuKU0Tdhy4w1TmSTieKQzwLx+gQa0TD0aQlkOmhi8Nrho0c6Hah0JdMyR6XmnWn1jvyMhyJpaXVaTt08eXsgskyQrghLnOlQFTAxxAwxyh3MFyNWt/4FPR7fMnNJKgCNHPngpScwVX60IhCzluPbP7zYiTfQiUYdXomptkiWFVGcajqio0xs6SNbZi55ZciClLAkIrkngLrwokvEx9aZ6UZncplDyn3TSmfS0InGDKIOqXDIQt/k0ke3/P6DCW1/w52vDk8FS8ydO/vvxxl9VPajEQ86RoQ7wZaJ0UOgsQkHwDYolAD+7wonL6+t/1KMHPlg90i1UHRmbJy+edJYgNEdJo5R828DvcSht0wrnLQwMXdc1jimbp1aG7h2nHLk19mPXZ7f/rEXkgGQPTGPc9ROmRLM006B6PtxQMzcPLEgP3viOQF10uR5/1VTEBgL8taTG8YXco7bCUw90OMZ5m74LQFeVnj7/Z604VdOv/IXV86Yeb72P6mnTL0RvvA236d2Z8dJRQCjOs0+L/t71Tuubz9qUCXR3UWlnxSs2HMhsPGcgzqhIJdZ+R0Vh4/eE3+TcP49lZM9tFEMt2/TjpdjXdv+/LzZJ8nU1Vn3IkgGsBZg5bY/ct6j74utL2JYJtjOnHZDz2ugHZ8SjKYYK9ZveeH7kwpy2t2r/L+dvP0P/Tla8usTzhIAAAAASUVORK5CYII='))
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
return apache2.OK;
|
|
Packit |
90a5c9 |
end
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
------------------------------------
|
|
Packit |
90a5c9 |
-- JavaScript and CSS definitions --
|
|
Packit |
90a5c9 |
------------------------------------
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
-- Set up some JavaScripts:
|
|
Packit |
90a5c9 |
status_js = [==[
|
|
Packit |
90a5c9 |
Number.prototype.pad = function(size) {
|
|
Packit |
90a5c9 |
var str = String(this);
|
|
Packit |
90a5c9 |
while (str.length < size) {
|
|
Packit |
90a5c9 |
str = "0" + str;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
return str;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
function getAsync(theUrl, xstate, callback) {
|
|
Packit |
90a5c9 |
var xmlHttp = null;
|
|
Packit |
90a5c9 |
if (window.XMLHttpRequest) {
|
|
Packit |
90a5c9 |
xmlHttp = new XMLHttpRequest();
|
|
Packit |
90a5c9 |
} else {
|
|
Packit |
90a5c9 |
xmlhttp = new ActiveXObject("Microsoft.XMLHTTP");
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
xmlHttp.open("GET", theUrl, true);
|
|
Packit |
90a5c9 |
xmlHttp.send(null);
|
|
Packit |
90a5c9 |
xmlHttp.onreadystatechange = function(state) {
|
|
Packit |
90a5c9 |
if (xmlHttp.readyState == 4 && xmlHttp.status == 200) {
|
|
Packit |
90a5c9 |
if (callback) {
|
|
Packit |
90a5c9 |
callback(JSON.parse(xmlHttp.responseText));
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
var actionCache = [];
|
|
Packit |
90a5c9 |
var connectionCache = [];
|
|
Packit |
90a5c9 |
var trafficCache = [];
|
|
Packit |
90a5c9 |
var processes = {};
|
|
Packit |
90a5c9 |
var lastBytes = 0;
|
|
Packit |
90a5c9 |
var lastConnections = 0;
|
|
Packit |
90a5c9 |
var negativeBytes = 0; // cache for proc reloads, which skews traffic
|
|
Packit |
90a5c9 |
var updateSpeed = 5; // How fast do charts update?
|
|
Packit |
90a5c9 |
var maxRecords = 24; // How many records to show per chart
|
|
Packit |
90a5c9 |
var cpumax = 1000000; // random cpu max(?)
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
function refreshCharts(json, state) {
|
|
Packit |
90a5c9 |
if (json && json.processes) {
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// general server info box
|
|
Packit |
90a5c9 |
var gs = document.getElementById('server_breakdown');
|
|
Packit |
90a5c9 |
gs.innerHTML = "";
|
|
Packit |
90a5c9 |
gs.innerHTML += "Server version: " + json.server.version + " ";
|
|
Packit |
90a5c9 |
gs.innerHTML += "Server built: " + json.server.built + " ";
|
|
Packit |
90a5c9 |
gs.innerHTML += "Server MPM: " + json.mpm.type + " ";
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Get a timestamp
|
|
Packit |
90a5c9 |
var now = new Date();
|
|
Packit |
90a5c9 |
var ts = now.getHours().pad(2) + ":" + now.getMinutes().pad(2) + ":" + now.getSeconds().pad(2);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
var utime = 0;
|
|
Packit |
90a5c9 |
var stime = 0;
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Construct state based on proc details
|
|
Packit |
90a5c9 |
var state = {
|
|
Packit |
90a5c9 |
timestamp: ts,
|
|
Packit |
90a5c9 |
closing: 0,
|
|
Packit |
90a5c9 |
idle: 0,
|
|
Packit |
90a5c9 |
writing: 0,
|
|
Packit |
90a5c9 |
reading: 0,
|
|
Packit |
90a5c9 |
keepalive: 0,
|
|
Packit |
90a5c9 |
graceful: 0
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
for (var i in json.processes) {
|
|
Packit |
90a5c9 |
var proc = json.processes[i];
|
|
Packit |
90a5c9 |
if (proc.pid) {
|
|
Packit |
90a5c9 |
state.closing += proc.workerStates.closing||0;
|
|
Packit |
90a5c9 |
state.idle += proc.workerStates.idle||0;
|
|
Packit |
90a5c9 |
state.writing += proc.workerStates.writing||0;
|
|
Packit |
90a5c9 |
state.reading += proc.workerStates.reading||0;
|
|
Packit |
90a5c9 |
state.keepalive += proc.workerStates.keepalive||0;
|
|
Packit |
90a5c9 |
state.graceful += proc.workerStates.graceful||0;
|
|
Packit |
90a5c9 |
utime += proc.utime;
|
|
Packit |
90a5c9 |
stime += proc.stime;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Push action state entry into action cache with timestamp
|
|
Packit |
90a5c9 |
// Shift if more than 10 entries in cache
|
|
Packit |
90a5c9 |
actionCache.push(state);
|
|
Packit |
90a5c9 |
if (actionCache.length > maxRecords) {
|
|
Packit |
90a5c9 |
actionCache.shift();
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// construct array for QuokkaLines
|
|
Packit |
90a5c9 |
var arr = [];
|
|
Packit |
90a5c9 |
for (var i in actionCache) {
|
|
Packit |
90a5c9 |
var el = actionCache[i];
|
|
Packit |
90a5c9 |
if (json.mpm.type == 'event') {
|
|
Packit |
90a5c9 |
arr.push([el.timestamp, el.closing, el.idle, el.writing, el.reading, el.graceful]);
|
|
Packit |
90a5c9 |
} else {
|
|
Packit |
90a5c9 |
arr.push([el.timestamp, el.keepalive, el.closing, el.idle, el.writing, el.reading, el.graceful]);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var states = ['Keepalive', 'Closing', 'Idle', 'Writing', 'Reading', 'Graceful']
|
|
Packit |
90a5c9 |
if (json.mpm.type == 'event') {
|
|
Packit |
90a5c9 |
states.shift();
|
|
Packit |
90a5c9 |
if (document.getElementById('mpminfo')) {
|
|
Packit |
90a5c9 |
document.getElementById('mpminfo').innerHTML = "(" + fn(parseInt(json.connections.idle)) + " connections in idle keepalive)";
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
// Draw action chart
|
|
Packit |
90a5c9 |
quokkaLines("actions_div", states, arr, { lastsum: true, hires: true, nosum: true, stack: true, curve: true, title: "Thread states" } );
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Get traffic, figure out how much it was this time (0 if just started!)
|
|
Packit |
90a5c9 |
var bytesThisTurn = 0;
|
|
Packit |
90a5c9 |
var connectionsThisTurn = 0;
|
|
Packit |
90a5c9 |
for (var i in json.processes) {
|
|
Packit |
90a5c9 |
var proc = json.processes[i];
|
|
Packit |
90a5c9 |
var pid = proc.pid
|
|
Packit |
90a5c9 |
// if we haven't seen this proc before, ignore its bytes first time
|
|
Packit |
90a5c9 |
if (!processes[pid]) {
|
|
Packit |
90a5c9 |
processes[pid] = {
|
|
Packit |
90a5c9 |
bytes: proc.bytes,
|
|
Packit |
90a5c9 |
connections: proc.connections,
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
} else {
|
|
Packit |
90a5c9 |
bytesThisTurn += proc.bytes - processes[pid].bytes;
|
|
Packit |
90a5c9 |
if (pid) {
|
|
Packit |
90a5c9 |
x = proc.connections - processes[pid].connections;
|
|
Packit |
90a5c9 |
connectionsThisTurn += (x > 0) ? x : 0;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
processes[pid].bytes = proc.bytes;
|
|
Packit |
90a5c9 |
processes[pid].connections = proc.connections;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
if (lastBytes == 0 ) {
|
|
Packit |
90a5c9 |
bytesThisTurn = 0;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
lastBytes = 1;
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Push a new element into cache, prune cache
|
|
Packit |
90a5c9 |
var el = {
|
|
Packit |
90a5c9 |
timestamp: ts,
|
|
Packit |
90a5c9 |
bytes: bytesThisTurn/updateSpeed
|
|
Packit |
90a5c9 |
};
|
|
Packit |
90a5c9 |
trafficCache.push(el);
|
|
Packit |
90a5c9 |
if (trafficCache.length > maxRecords) {
|
|
Packit |
90a5c9 |
trafficCache.shift();
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// construct array for QuokkaLines
|
|
Packit |
90a5c9 |
arr = [];
|
|
Packit |
90a5c9 |
for (var i in trafficCache) {
|
|
Packit |
90a5c9 |
var el = trafficCache[i];
|
|
Packit |
90a5c9 |
arr.push([el.timestamp, el.bytes]);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
// Draw action chart
|
|
Packit |
90a5c9 |
quokkaLines("traffic_div", ['Traffic'], arr, { traffic: true, hires: true, nosum: true, stack: true, curve: true, title: "Traffic per second" } );
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Get connections per second
|
|
Packit |
90a5c9 |
// Push a new element into cache, prune cache
|
|
Packit |
90a5c9 |
var el = {
|
|
Packit |
90a5c9 |
timestamp: ts,
|
|
Packit |
90a5c9 |
connections: (connectionsThisTurn+1)/updateSpeed
|
|
Packit |
90a5c9 |
};
|
|
Packit |
90a5c9 |
connectionCache.push(el);
|
|
Packit |
90a5c9 |
if (connectionCache.length > maxRecords) {
|
|
Packit |
90a5c9 |
connectionCache.shift();
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// construct array for QuokkaLines
|
|
Packit |
90a5c9 |
arr = [];
|
|
Packit |
90a5c9 |
for (var i in connectionCache) {
|
|
Packit |
90a5c9 |
var el = connectionCache[i];
|
|
Packit |
90a5c9 |
arr.push([el.timestamp, el.connections]);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
// Draw connection chart
|
|
Packit |
90a5c9 |
quokkaLines("connection_div", ['Connections/sec'], arr, { traffic: false, hires: true, nosum: true, stack: true, curve: true, title: "Connections per second" } );
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Thread info
|
|
Packit |
90a5c9 |
quokkaCircle("status_div", [
|
|
Packit |
90a5c9 |
{ title: 'Active', value: (json.mpm.threadsPerChild*json.mpm.activeServers)},
|
|
Packit |
90a5c9 |
{ title: 'Reserve', value: (json.mpm.threadsPerChild*(json.mpm.activeServers-json.mpm.maxServers))}
|
|
Packit |
90a5c9 |
],
|
|
Packit |
90a5c9 |
{ title: "Worker pool", hires: true});
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Idle vs active connections
|
|
Packit |
90a5c9 |
var idlecons = json.connections.idle;
|
|
Packit |
90a5c9 |
var activecons = json.connections.active;
|
|
Packit |
90a5c9 |
quokkaCircle("idle_div", [
|
|
Packit |
90a5c9 |
{ title: 'Idle', value: idlecons},
|
|
Packit |
90a5c9 |
{ title: 'Active', value: activecons},
|
|
Packit |
90a5c9 |
],
|
|
Packit |
90a5c9 |
{ hires: true, title: "Idle vs active connections"});
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// CPU info
|
|
Packit |
90a5c9 |
while ( (stime+utime) > cpumax ) {
|
|
Packit |
90a5c9 |
cpumax = cpumax * 2;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
quokkaCircle("cpu_div", [
|
|
Packit |
90a5c9 |
{ title: 'Idle', value: (cpumax - stime - utime) / (cpumax/100)},
|
|
Packit |
90a5c9 |
{ title: 'System', value: stime/(cpumax/100)},
|
|
Packit |
90a5c9 |
{ title: 'User', value: utime/(cpumax/100)}
|
|
Packit |
90a5c9 |
],
|
|
Packit |
90a5c9 |
{ hires: true, title: "CPU usage", pct: true});
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// General stats infobox
|
|
Packit |
90a5c9 |
var gstats = document.getElementById('general_stats');
|
|
Packit |
90a5c9 |
gstats.innerHTML = ''; // wipe the box
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Days since restart
|
|
Packit |
90a5c9 |
var u_f = Math.floor(json.server.uptime/8640.0) / 10;
|
|
Packit |
90a5c9 |
var u_d = Math.floor(json.server.uptime/86400);
|
|
Packit |
90a5c9 |
var u_h = Math.floor((json.server.uptime%86400)/3600);
|
|
Packit |
90a5c9 |
var u_m = Math.floor((json.server.uptime%3600)/60);
|
|
Packit |
90a5c9 |
var u_s = Math.floor(json.server.uptime %60);
|
|
Packit |
90a5c9 |
var str = u_d + " day" + (u_d != 1 ? "s, " : ", ") + u_h + " hour" + (u_h != 1 ? "s, " : ", ") + u_m + " minute" + (u_m != 1 ? "s" : "");
|
|
Packit |
90a5c9 |
var ubox = document.createElement('div');
|
|
Packit |
90a5c9 |
ubox.setAttribute("class", "statsbox");
|
|
Packit |
90a5c9 |
ubox.innerHTML = "" + u_f + " days since last (re)start. <small>" + str;
|
|
Packit |
90a5c9 |
gstats.appendChild(ubox);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Bytes transferred in total
|
|
Packit |
90a5c9 |
var MB = fnmb(json.server.bytes);
|
|
Packit |
90a5c9 |
var KB = (json.server.bytes > 0) ? fnmb(json.server.bytes/json.server.connections) : 0;
|
|
Packit |
90a5c9 |
var KBs = fnmb(json.server.bytes/json.server.uptime);
|
|
Packit |
90a5c9 |
var mbbox = document.createElement('div');
|
|
Packit |
90a5c9 |
mbbox.setAttribute("class", "statsbox");
|
|
Packit |
90a5c9 |
mbbox.innerHTML = "" + MB + " transferred in total. <small>" + KBs + "/sec, " + KB + "/request";
|
|
Packit |
90a5c9 |
gstats.appendChild(mbbox);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// connections in total
|
|
Packit |
90a5c9 |
var cons = fn(json.server.connections);
|
|
Packit |
90a5c9 |
var cps = Math.floor(json.server.connections/json.server.uptime*100)/100;
|
|
Packit |
90a5c9 |
var conbox = document.createElement('div');
|
|
Packit |
90a5c9 |
conbox.setAttribute("class", "statsbox");
|
|
Packit |
90a5c9 |
conbox.innerHTML = "" + cons + " conns since server started. <small>" + cps + " requests per second";
|
|
Packit |
90a5c9 |
gstats.appendChild(conbox);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// threads working
|
|
Packit |
90a5c9 |
var tpc = json.mpm.threadsPerChild;
|
|
Packit |
90a5c9 |
var activeThreads = fn(json.mpm.activeServers * json.mpm.threadsPerChild);
|
|
Packit |
90a5c9 |
var maxThreads = json.mpm.maxServers * json.mpm.threadsPerChild;
|
|
Packit |
90a5c9 |
var tbox = document.createElement('div');
|
|
Packit |
90a5c9 |
tbox.setAttribute("class", "statsbox");
|
|
Packit |
90a5c9 |
tbox.innerHTML = "" + activeThreads + " threads currently at work (" + json.mpm.activeServers + "x" + tpc+" threads). <small>" + maxThreads + " (" + json.mpm.maxServers + "x"+tpc+") threads allowed.";
|
|
Packit |
90a5c9 |
gstats.appendChild(tbox);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
window.setTimeout(waitTwo, updateSpeed*1000);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// resize pane
|
|
Packit |
90a5c9 |
document.getElementById('leftpane').style.height = document.getElementById('wrapper').getBoundingClientRect().height + "px";
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Do we have extended info and module lists??
|
|
Packit |
90a5c9 |
if (json.server.extended) document.getElementById('threads_button').style.display = 'block';
|
|
Packit |
90a5c9 |
if (json.server.modules && json.server.modules.length > 0) {
|
|
Packit |
90a5c9 |
var panel = document.getElementById('modules_breakdown');
|
|
Packit |
90a5c9 |
var list = "
|
|
Packit |
90a5c9 |
for (var i in json.server.modules) {
|
|
Packit |
90a5c9 |
var mod = json.server.modules[i];
|
|
Packit |
90a5c9 |
list += "" + mod + "";
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
list += "";
|
|
Packit |
90a5c9 |
panel.innerHTML = list;
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
document.getElementById('modules_button').style.display = 'block';
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
} else if (json === false) {
|
|
Packit |
90a5c9 |
waitTwo();
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
function refreshThreads(json, state) {
|
|
Packit |
90a5c9 |
var box = document.getElementById('threads_breakdown');
|
|
Packit |
90a5c9 |
box.innerHTML = "";
|
|
Packit |
90a5c9 |
for (var i in json.processes) {
|
|
Packit |
90a5c9 |
var proc = json.processes[i];
|
|
Packit |
90a5c9 |
var phtml = '';
|
|
Packit |
90a5c9 |
if (!proc.active) phtml = '';
|
|
Packit |
90a5c9 |
phtml += "Process " + i + ":";
|
|
Packit |
90a5c9 |
phtml += "PID: " + (proc.pid||"None (not active)") + " ";
|
|
Packit |
90a5c9 |
if (proc.threads && proc.active) {
|
|
Packit |
90a5c9 |
phtml += "";Thread ID | Access count | Bytes served | Last Used | Last client | Last request |
---|
|
|
Packit |
90a5c9 |
for (var j in proc.threads) {
|
|
Packit |
90a5c9 |
var thread = proc.threads[j];
|
|
Packit |
90a5c9 |
thread.request = (thread.request||"(Unknown)").replace(/[<>]+/g, "");
|
|
Packit |
90a5c9 |
phtml += ""+thread.thread+""+thread.count+""+thread.bytes+""+thread.last_used+""+thread.client+""+thread.request+"";
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
phtml += "";
|
|
Packit |
90a5c9 |
} else {
|
|
Packit |
90a5c9 |
phtml += "No thread information avaialable ";
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
phtml += "";
|
|
Packit |
90a5c9 |
box.innerHTML += phtml;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
function waitTwo() {
|
|
Packit |
90a5c9 |
getAsync(location.href + "?view=json&rnd=" + Math.random(), null, refreshCharts)
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
function showPanel(what) {
|
|
Packit |
90a5c9 |
var items = ['dashboard','misc','threads','modules'];
|
|
Packit |
90a5c9 |
for (var i in items) {
|
|
Packit |
90a5c9 |
var item = items[i];
|
|
Packit |
90a5c9 |
var btn = document.getElementById(item+'_button');
|
|
Packit |
90a5c9 |
var panel = document.getElementById(item+'_panel');
|
|
Packit |
90a5c9 |
if (item == what) {
|
|
Packit |
90a5c9 |
btn.setAttribute("class", "btn active");
|
|
Packit |
90a5c9 |
panel.style.display = 'block';
|
|
Packit |
90a5c9 |
} else {
|
|
Packit |
90a5c9 |
btn.setAttribute("class", "btn");
|
|
Packit |
90a5c9 |
panel.style.display = 'none';
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// special constructors
|
|
Packit |
90a5c9 |
if (what == 'threads') {
|
|
Packit |
90a5c9 |
getAsync(location.href + "?view=json&extended=true&rnd=" + Math.random(), null, refreshThreads)
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
function fn(num) {
|
|
Packit |
90a5c9 |
num = num + "";
|
|
Packit |
90a5c9 |
num = num.replace(/(\d)(\d{9})$/, '$1,$2');
|
|
Packit |
90a5c9 |
num = num.replace(/(\d)(\d{6})$/, '$1,$2');
|
|
Packit |
90a5c9 |
num = num.replace(/(\d)(\d{3})$/, '$1,$2');
|
|
Packit |
90a5c9 |
return num;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
function fnmb(num) {
|
|
Packit |
90a5c9 |
var add = "bytes";
|
|
Packit |
90a5c9 |
var dec = "";
|
|
Packit |
90a5c9 |
var mul = 1;
|
|
Packit |
90a5c9 |
if (num > 1024) { add = "KB"; mul= 1024; }
|
|
Packit |
90a5c9 |
if (num > (1024*1024)) { add = "MB"; mul= 1024*1024; }
|
|
Packit |
90a5c9 |
if (num > (1024*1024*1024)) { add = "GB"; mul= 1024*1024*1024; }
|
|
Packit |
90a5c9 |
if (num > (1024*1024*1024*1024)) { add = "TB"; mul= 1024*1024*1024*1024; }
|
|
Packit |
90a5c9 |
num = num / mul;
|
|
Packit |
90a5c9 |
if (add != "bytes") {
|
|
Packit |
90a5c9 |
dec = "." + Math.floor( (num - Math.floor(num)) * 100 );
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
return ( fn(Math.floor(num)) + dec + " " + add );
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
function sort(a,b){
|
|
Packit |
90a5c9 |
last_col = -1;
|
|
Packit |
90a5c9 |
var sort_reverse = false;
|
|
Packit |
90a5c9 |
var sortWay = a.getAttribute("sort_" + b);
|
|
Packit |
90a5c9 |
if (sortWay && sortWay == "forward") {
|
|
Packit |
90a5c9 |
a.setAttribute("sort_" + b, "reverse");
|
|
Packit |
90a5c9 |
sort_reverse = true;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
else {
|
|
Packit |
90a5c9 |
a.setAttribute("sort_" + b, "forward");
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var c,d,e,f,g,h,i;
|
|
Packit |
90a5c9 |
c=a.rows.length;
|
|
Packit |
90a5c9 |
if(c<1){ return; }
|
|
Packit |
90a5c9 |
d=a.rows[1].cells.length;
|
|
Packit |
90a5c9 |
e=1;
|
|
Packit |
90a5c9 |
var j=new Array(c);
|
|
Packit |
90a5c9 |
f=0;
|
|
Packit |
90a5c9 |
for(h=e;h
|
|
Packit |
90a5c9 |
var k=new Array(d);
|
|
Packit |
90a5c9 |
for(i=0;i
|
|
Packit |
90a5c9 |
cell_text="";
|
|
Packit |
90a5c9 |
cell_text=a.rows[h].cells[i].textContent;
|
|
Packit |
90a5c9 |
if(cell_text===undefined){cell_text=a.rows[h].cells[i].innerText;}
|
|
Packit |
90a5c9 |
k[i]=cell_text;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
j[f++]=k;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var l=false;
|
|
Packit |
90a5c9 |
var m,n;
|
|
Packit |
90a5c9 |
if(b!=lastcol) lastseq="A";
|
|
Packit |
90a5c9 |
else{
|
|
Packit |
90a5c9 |
if(lastseq=="A") lastseq="D";
|
|
Packit |
90a5c9 |
lastseq="A";
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
g=c-1;
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
for(h=0;h
|
|
Packit |
90a5c9 |
l=false;
|
|
Packit |
90a5c9 |
for(i=0;i
|
|
Packit |
90a5c9 |
m=j[i];
|
|
Packit |
90a5c9 |
n=j[i+1];
|
|
Packit |
90a5c9 |
if(lastseq=="A"){
|
|
Packit |
90a5c9 |
var gt = (m[b]>n[b]) ? true : false;
|
|
Packit |
90a5c9 |
var lt = (m[b]
|
|
Packit |
90a5c9 |
if (n[b].match(/^(\d+)$/)) { gt = parseInt(m[b], 10) > parseInt(n[b], 10) ? true : false; lt = parseInt(m[b], 10) < parseInt(n[b], 10) ? true : false; }
|
|
Packit |
90a5c9 |
if (sort_reverse) {gt = (!gt); lt = (!lt);}
|
|
Packit |
90a5c9 |
if(gt){
|
|
Packit |
90a5c9 |
j[i+1]=m;
|
|
Packit |
90a5c9 |
j[i]=n;
|
|
Packit |
90a5c9 |
l=true;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
else{
|
|
Packit |
90a5c9 |
if(lt){
|
|
Packit |
90a5c9 |
j[i+1]=m;
|
|
Packit |
90a5c9 |
j[i]=n;
|
|
Packit |
90a5c9 |
l=true;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if(l===false){
|
|
Packit |
90a5c9 |
break;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
f=e;
|
|
Packit |
90a5c9 |
for(h=0;h
|
|
Packit |
90a5c9 |
m=j[h];
|
|
Packit |
90a5c9 |
for(i=0;i
|
|
Packit |
90a5c9 |
if(a.rows[f].cells[i].innerText!==undefined){
|
|
Packit |
90a5c9 |
a.rows[f].cells[i].innerText=m[i];
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
else{
|
|
Packit |
90a5c9 |
a.rows[f].cells[i].textContent=m[i];
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
f++;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
lastcol=b;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
var CPUmax = 1000000;
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
var showing = false;
|
|
Packit |
90a5c9 |
function showDetails() {
|
|
Packit |
90a5c9 |
for (i=1; i < 1000; i++) {
|
|
Packit |
90a5c9 |
var obj = document.getElementById("srv_" + i);
|
|
Packit |
90a5c9 |
if (obj) {
|
|
Packit |
90a5c9 |
if (showing) { obj.style.display = "none"; }
|
|
Packit |
90a5c9 |
else { obj.style.display = "block"; }
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var link = document.getElementById("show_link");
|
|
Packit |
90a5c9 |
showing = (!showing);
|
|
Packit |
90a5c9 |
if (showing) { link.innerHTML = "Hide thread information"; }
|
|
Packit |
90a5c9 |
else { link.innerHTML = "Show thread information"; }
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
var showing_modules = false;
|
|
Packit |
90a5c9 |
function show_modules() {
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
var obj = document.getElementById("modules");
|
|
Packit |
90a5c9 |
if (obj) {
|
|
Packit |
90a5c9 |
if (showing_modules) { obj.style.display = "none"; }
|
|
Packit |
90a5c9 |
else { obj.style.display = "block"; }
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var link = document.getElementById("show_modules_link");
|
|
Packit |
90a5c9 |
showing_modules = (!showing_modules);
|
|
Packit |
90a5c9 |
if (showing_modules) { link.innerHTML = "Hide loaded modules"; }
|
|
Packit |
90a5c9 |
else { link.innerHTML = "Show loaded modules"; }
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
]==]
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
quokka_js = [==[
|
|
Packit |
90a5c9 |
/*
|
|
Packit |
90a5c9 |
*
|
|
Packit |
90a5c9 |
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
Packit |
90a5c9 |
* you may not use this file except in compliance with the License.
|
|
Packit |
90a5c9 |
* You may obtain a copy of the License at
|
|
Packit |
90a5c9 |
*
|
|
Packit |
90a5c9 |
* http://www.apache.org/licenses/LICENSE-2.0
|
|
Packit |
90a5c9 |
*
|
|
Packit |
90a5c9 |
* Unless required by applicable law or agreed to in writing, software
|
|
Packit |
90a5c9 |
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
Packit |
90a5c9 |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
Packit |
90a5c9 |
* See the License for the specific language governing permissions and
|
|
Packit |
90a5c9 |
* limitations under the License.
|
|
Packit |
90a5c9 |
*/
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Traffic shaper
|
|
Packit |
90a5c9 |
function quokka_fnmb(num) {
|
|
Packit |
90a5c9 |
var add = "b";
|
|
Packit |
90a5c9 |
var dec = "";
|
|
Packit |
90a5c9 |
var mul = 1;
|
|
Packit |
90a5c9 |
if (num > 1024) { add = "KB"; mul= 1024; }
|
|
Packit |
90a5c9 |
if (num > (1024*1024)) { add = "MB"; mul= 1024*1024; }
|
|
Packit |
90a5c9 |
if (num > (1024*1024*1024)) { add = "GB"; mul= 1024*1024*1024; }
|
|
Packit |
90a5c9 |
if (num > (1024*1024*1024*1024)) { add = "TB"; mul= 1024*1024*1024*1024; }
|
|
Packit |
90a5c9 |
num = num / mul;
|
|
Packit |
90a5c9 |
if (add != "b" && num < 10) {
|
|
Packit |
90a5c9 |
dec = "." + Math.floor( (num - Math.floor(num)) * 100 );
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
return ( Math.floor(num) + dec + " " + add );
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Hue, Saturation and Lightness to Red, Green and Blue:
|
|
Packit |
90a5c9 |
function quokka_internal_hsl2rgb (h,s,l)
|
|
Packit |
90a5c9 |
{
|
|
Packit |
90a5c9 |
var min, sv, switcher, fract, vsf;
|
|
Packit |
90a5c9 |
h = h % 1;
|
|
Packit |
90a5c9 |
if (s > 1) s = 1;
|
|
Packit |
90a5c9 |
if (l > 1) l = 1;
|
|
Packit |
90a5c9 |
var v = (l <= 0.5) ? (l * (1 + s)) : (l + s - l * s);
|
|
Packit |
90a5c9 |
if (v === 0)
|
|
Packit |
90a5c9 |
return { r: 0, g: 0, b: 0 };
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
min = 2 * l - v;
|
|
Packit |
90a5c9 |
sv = (v - min) / v;
|
|
Packit |
90a5c9 |
var sh = (6 * h) % 6;
|
|
Packit |
90a5c9 |
switcher = Math.floor(sh);
|
|
Packit |
90a5c9 |
fract = sh - switcher;
|
|
Packit |
90a5c9 |
vsf = v * sv * fract;
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
switch (switcher)
|
|
Packit |
90a5c9 |
{
|
|
Packit |
90a5c9 |
case 0: return { r: v, g: min + vsf, b: min };
|
|
Packit |
90a5c9 |
case 1: return { r: v - vsf, g: v, b: min };
|
|
Packit |
90a5c9 |
case 2: return { r: min, g: v, b: min + vsf };
|
|
Packit |
90a5c9 |
case 3: return { r: min, g: v - vsf, b: v };
|
|
Packit |
90a5c9 |
case 4: return { r: min + vsf, g: min, b: v };
|
|
Packit |
90a5c9 |
case 5: return { r: v, g: min, b: v - vsf };
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
return {r:0, g:0, b: 0};
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// RGB to Hex conversion
|
|
Packit |
90a5c9 |
function quokka_internal_rgb2hex(r, g, b) {
|
|
Packit |
90a5c9 |
return "#" + ((1 << 24) + (Math.floor(r) << 16) + (Math.floor(g) << 8) + Math.floor(b)).toString(16).slice(1);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Generate color list used for charts
|
|
Packit |
90a5c9 |
var colors = [];
|
|
Packit |
90a5c9 |
var rgbs = []
|
|
Packit |
90a5c9 |
var numColorRows = 6;
|
|
Packit |
90a5c9 |
var numColorColumns = 20;
|
|
Packit |
90a5c9 |
for (var x=0;x
|
|
Packit |
90a5c9 |
for (var y=0;y
|
|
Packit |
90a5c9 |
var rnd = [[148, 221, 119], [0, 203, 171], [51, 167, 215] , [35, 160, 253], [218, 54, 188], [16, 171, 246], [110, 68, 206], [21, 49, 248], [142, 104, 210]][y]
|
|
Packit |
90a5c9 |
var color = quokka_internal_hsl2rgb(y > 8 ? (Math.random()) : (rnd[0]/255), y > 8 ? (0.75+(y*0.05)) : (rnd[1]/255), y > 8 ? (0.42 + (y*0.05*(x/numColorRows))) : (0.1 + rnd[2]/512));
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Light (primary) color:
|
|
Packit |
90a5c9 |
var hex = quokka_internal_rgb2hex(color.r*255, color.g*255, color.b*255);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Darker variant for gradients:
|
|
Packit |
90a5c9 |
var dhex = quokka_internal_rgb2hex(color.r*131, color.g*131, color.b*131);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Medium variant for legends:
|
|
Packit |
90a5c9 |
var mhex = quokka_internal_rgb2hex(color.r*200, color.g*200, color.b*200);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
colors.push([hex, dhex, color, mhex]);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
/* Function for drawing pie diagrams
|
|
Packit |
90a5c9 |
* Example usage:
|
|
Packit |
90a5c9 |
* quokkaCircle("canvasName", [ { title: 'ups', value: 30}, { title: 'downs', value: 70} ] );
|
|
Packit |
90a5c9 |
*/
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
function quokkaCircle(id, tags, opts) {
|
|
Packit |
90a5c9 |
// Get Canvas object and context
|
|
Packit |
90a5c9 |
var canvas = document.getElementById(id);
|
|
Packit |
90a5c9 |
var ctx=canvas.getContext("2d");
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Calculate the total value of the pie
|
|
Packit |
90a5c9 |
var total = 0;
|
|
Packit |
90a5c9 |
var k;
|
|
Packit |
90a5c9 |
for (k in tags) {
|
|
Packit |
90a5c9 |
tags[k].value = Math.abs(tags[k].value);
|
|
Packit |
90a5c9 |
total += tags[k].value;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw the empty pie
|
|
Packit |
90a5c9 |
var begin = 0;
|
|
Packit |
90a5c9 |
var stop = 0;
|
|
Packit |
90a5c9 |
var radius = (canvas.height*0.75)/2;
|
|
Packit |
90a5c9 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
Packit |
90a5c9 |
ctx.beginPath();
|
|
Packit |
90a5c9 |
ctx.shadowBlur = 6;
|
|
Packit |
90a5c9 |
ctx.shadowOffsetX = 6;
|
|
Packit |
90a5c9 |
ctx.shadowOffsetY = 6;
|
|
Packit |
90a5c9 |
ctx.shadowColor = "#555";
|
|
Packit |
90a5c9 |
ctx.lineWidth = (opts && opts.hires) ? 6 : 2;
|
|
Packit |
90a5c9 |
ctx.strokeStyle = "#222";
|
|
Packit |
90a5c9 |
ctx.arc((canvas.width-140)/2,canvas.height/2,radius, 0, Math.PI * 2);
|
|
Packit |
90a5c9 |
ctx.closePath();
|
|
Packit |
90a5c9 |
ctx.stroke();
|
|
Packit |
90a5c9 |
ctx.fill();
|
|
Packit |
90a5c9 |
ctx.shadowBlur = 0;
|
|
Packit |
90a5c9 |
ctx.shadowOffsetY = 0;
|
|
Packit |
90a5c9 |
ctx.shadowOffsetX = 0;
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw a title if set:
|
|
Packit |
90a5c9 |
if (opts && opts.title) {
|
|
Packit |
90a5c9 |
ctx.font= (opts && opts.hires) ? "28px Sans-Serif" : "15px Sans-Serif";
|
|
Packit |
90a5c9 |
ctx.fillStyle = "#000000";
|
|
Packit |
90a5c9 |
ctx.textAlign = "center";
|
|
Packit |
90a5c9 |
ctx.fillText(opts.title,(canvas.width-140)/2, (opts && opts.hires) ? 30:15);
|
|
Packit |
90a5c9 |
ctx.textAlign = "left";
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
ctx.beginPath();
|
|
Packit |
90a5c9 |
var posY = 50;
|
|
Packit |
90a5c9 |
var left = 120 + ((canvas.width-140)/2) + ((opts && opts.hires) ? 40 : 25)
|
|
Packit |
90a5c9 |
for (k in tags) {
|
|
Packit |
90a5c9 |
var val = tags[k].value;
|
|
Packit |
90a5c9 |
stop = stop + (2 * Math.PI * (val / total));
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Make a pizza slice
|
|
Packit |
90a5c9 |
ctx.beginPath();
|
|
Packit |
90a5c9 |
ctx.lineCap = 'round';
|
|
Packit |
90a5c9 |
ctx.arc((canvas.width-140)/2,canvas.height/2,radius,begin,stop);
|
|
Packit |
90a5c9 |
ctx.lineTo((canvas.width-140)/2,canvas.height/2);
|
|
Packit |
90a5c9 |
ctx.closePath();
|
|
Packit |
90a5c9 |
ctx.lineWidth = 0;
|
|
Packit |
90a5c9 |
ctx.stroke();
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Add color gradient
|
|
Packit |
90a5c9 |
var grd=ctx.createLinearGradient(0,canvas.height*0.2,0,canvas.height);
|
|
Packit |
90a5c9 |
grd.addColorStop(0,colors[k % colors.length][1]);
|
|
Packit |
90a5c9 |
grd.addColorStop(1,colors[k % colors.length][0]);
|
|
Packit |
90a5c9 |
ctx.fillStyle = grd;
|
|
Packit |
90a5c9 |
ctx.fill();
|
|
Packit |
90a5c9 |
begin = stop;
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Make color legend
|
|
Packit |
90a5c9 |
ctx.fillRect(left, posY-((opts && opts.hires) ? 15 : 10), (opts && opts.hires) ? 14 : 7, (opts && opts.hires) ? 14 : 7);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Add legend text
|
|
Packit |
90a5c9 |
ctx.shadowColor = "rgba(0,0,0,0)"
|
|
Packit |
90a5c9 |
ctx.font= (opts && opts.hires) ? "22px Sans-Serif" : "12px Sans-Serif";
|
|
Packit |
90a5c9 |
ctx.fillStyle = "#000";
|
|
Packit |
90a5c9 |
ctx.fillText(tags[k].title + " (" + Math.floor(val) + (opts && opts.pct ? "%" : "") + ")",left+20,posY);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
posY += (opts && opts.hires) ? 28 : 14;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
/* Function for drawing line charts
|
|
Packit |
90a5c9 |
* Example usage:
|
|
Packit |
90a5c9 |
* quokkaLines("myCanvas", ['Line a', 'Line b', 'Line c'], [ [x1,a1,b1,c1], [x2,a2,b2,c2], [x3,a3,b3,c3] ], { stacked: true, curve: false, title: "Some title" } );
|
|
Packit |
90a5c9 |
*/
|
|
Packit |
90a5c9 |
function quokkaLines(id, titles, values, options, sums) {
|
|
Packit |
90a5c9 |
var canvas = document.getElementById(id);
|
|
Packit |
90a5c9 |
var ctx=canvas.getContext("2d");
|
|
Packit |
90a5c9 |
// clear the canvas first
|
|
Packit |
90a5c9 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
ctx.lineWidth = 0.25;
|
|
Packit |
90a5c9 |
ctx.strokeStyle = "#000000";
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
var lwidth = 300;
|
|
Packit |
90a5c9 |
var lheight = 75;
|
|
Packit |
90a5c9 |
wspace = (options && options.hires) ? 110 : 55;
|
|
Packit |
90a5c9 |
var rectwidth = canvas.width - lwidth - wspace;
|
|
Packit |
90a5c9 |
var stack = options ? options.stack : false;
|
|
Packit |
90a5c9 |
var curve = options ? options.curve : false;
|
|
Packit |
90a5c9 |
var title = options ? options.title : null;
|
|
Packit |
90a5c9 |
var spots = options ? options.points : false;
|
|
Packit |
90a5c9 |
var noX = options ? options.nox : false;
|
|
Packit |
90a5c9 |
var verts = options ? options.verts : true;
|
|
Packit |
90a5c9 |
if (noX) {
|
|
Packit |
90a5c9 |
lheight = 0;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// calc rectwidth if titles are large
|
|
Packit |
90a5c9 |
var nlwidth = 0
|
|
Packit |
90a5c9 |
for (var k in titles) {
|
|
Packit |
90a5c9 |
ctx.font= (options && options.hires) ? "24px Sans-Serif" : "12px Sans-Serif";
|
|
Packit |
90a5c9 |
ctx.fillStyle = "#00000";
|
|
Packit |
90a5c9 |
var x = parseInt(k)
|
|
Packit |
90a5c9 |
if (!noX) {
|
|
Packit |
90a5c9 |
x = x + 1;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var sum = 0
|
|
Packit |
90a5c9 |
for (var y in values) {
|
|
Packit |
90a5c9 |
sum += values[y][x]
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var t = titles[k] + (!options.nosum ? " (" + ((sums && sums[k]) ? sums[k] : sum.toFixed(0)) + ")" : "");
|
|
Packit |
90a5c9 |
var w = ctx.measureText(t).width + 48;
|
|
Packit |
90a5c9 |
if (w > lwidth && w > nlwidth) {
|
|
Packit |
90a5c9 |
nlwidth = w
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if (nlwidth > 0) {
|
|
Packit |
90a5c9 |
rectwidth -= nlwidth - lwidth
|
|
Packit |
90a5c9 |
lwidth = nlwidth
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw a border
|
|
Packit |
90a5c9 |
ctx.lineWidth = 0.5;
|
|
Packit |
90a5c9 |
ctx.strokeRect((wspace*0.75), 30, rectwidth, canvas.height - lheight - 40);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw a title if set:
|
|
Packit |
90a5c9 |
if (title != null) {
|
|
Packit |
90a5c9 |
ctx.font= (options && options.hires) ? "24px Sans-Serif" : "15px Sans-Serif";
|
|
Packit |
90a5c9 |
ctx.fillStyle = "#00000";
|
|
Packit |
90a5c9 |
ctx.textAlign = "center";
|
|
Packit |
90a5c9 |
ctx.fillText(title,rectwidth/2, 20);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw legend
|
|
Packit |
90a5c9 |
ctx.textAlign = "left";
|
|
Packit |
90a5c9 |
var posY = 50;
|
|
Packit |
90a5c9 |
for (var k in titles) {
|
|
Packit |
90a5c9 |
var x = parseInt(k)
|
|
Packit |
90a5c9 |
if (!noX) {
|
|
Packit |
90a5c9 |
x = x + 1;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var sum = 0
|
|
Packit |
90a5c9 |
for (var y in values) {
|
|
Packit |
90a5c9 |
sum += values[y][x]
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
var title = titles[k] + (!options.nosum ? (" (" + ((sums && sums[k]) ? sums[k] : sum.toFixed(0)) + ")") : "");
|
|
Packit |
90a5c9 |
if (options && options.lastsum) {
|
|
Packit |
90a5c9 |
title = titles[k] + " (" + values[values.length-1][x].toFixed(0) + ")";
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
ctx.fillStyle = colors[k % colors.length][3];
|
|
Packit |
90a5c9 |
ctx.fillRect(wspace + rectwidth + 75 , posY-((options && options.hires) ? 18:9), (options && options.hires) ? 20:10, (options && options.hires) ?20:10);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Add legend text
|
|
Packit |
90a5c9 |
ctx.font= (options && options.hires) ? "24px Sans-Serif" : "14px Sans-Serif";
|
|
Packit |
90a5c9 |
ctx.fillStyle = "#00000";
|
|
Packit |
90a5c9 |
ctx.fillText(title,canvas.width - lwidth + ((options && options.hires) ? 100:60), posY);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
posY += (options && options.hires) ? 30:15;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Find max and min
|
|
Packit |
90a5c9 |
var max = null;
|
|
Packit |
90a5c9 |
var min = 0;
|
|
Packit |
90a5c9 |
var stacked = null;
|
|
Packit |
90a5c9 |
for (x in values) {
|
|
Packit |
90a5c9 |
var s = 0;
|
|
Packit |
90a5c9 |
for (y in values[x]) {
|
|
Packit |
90a5c9 |
if (y > 0 || noX) {
|
|
Packit |
90a5c9 |
s += values[x][y];
|
|
Packit |
90a5c9 |
if (max === null || max < values[x][y]) {
|
|
Packit |
90a5c9 |
max = values[x][y];
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if (min === null || min > values[x][y]) {
|
|
Packit |
90a5c9 |
min = values[x][y];
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if (stacked === null || stacked < s) {
|
|
Packit |
90a5c9 |
stacked = s;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if (min == max) max++;
|
|
Packit |
90a5c9 |
if (stack) {
|
|
Packit |
90a5c9 |
min = 0;
|
|
Packit |
90a5c9 |
max = stacked;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Set number of lines to draw and each step
|
|
Packit |
90a5c9 |
var numLines = 5;
|
|
Packit |
90a5c9 |
var step = (max-min) / (numLines+1);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Prettify the max value so steps aren't ugly numbers
|
|
Packit |
90a5c9 |
if (step %1 != 0) {
|
|
Packit |
90a5c9 |
step = (Math.round(step+0.5));
|
|
Packit |
90a5c9 |
max = step * (numLines+1);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw horizontal lines
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
for (x = -1; x <= numLines; x++) {
|
|
Packit |
90a5c9 |
ctx.beginPath();
|
|
Packit |
90a5c9 |
var y = 30 + (((canvas.height-40-lheight) / (numLines+1)) * (x+1));
|
|
Packit |
90a5c9 |
ctx.moveTo(wspace*0.75, y);
|
|
Packit |
90a5c9 |
ctx.lineTo(wspace*0.75 + rectwidth, y);
|
|
Packit |
90a5c9 |
ctx.lineWidth = 0.25;
|
|
Packit |
90a5c9 |
ctx.stroke();
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Add values
|
|
Packit |
90a5c9 |
ctx.font= (options && options.hires) ? "20px Sans-Serif" : "12px Sans-Serif";
|
|
Packit |
90a5c9 |
ctx.fillStyle = "#000000";
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
var val = Math.round( ((max-min) - (step*(x+1))) );
|
|
Packit |
90a5c9 |
if (options && options.traffic) {
|
|
Packit |
90a5c9 |
val = quokka_fnmb(val);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
ctx.textAlign = "left";
|
|
Packit |
90a5c9 |
ctx.fillText( val,canvas.width - lwidth - 20, y+8);
|
|
Packit |
90a5c9 |
ctx.textAlign = "right";
|
|
Packit |
90a5c9 |
ctx.fillText( val,wspace-32, y+8);
|
|
Packit |
90a5c9 |
ctx.closePath();
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw vertical lines
|
|
Packit |
90a5c9 |
var sx = 1
|
|
Packit |
90a5c9 |
var numLines = values.length-1;
|
|
Packit |
90a5c9 |
var step = (canvas.width - lwidth - wspace*0.75) / values.length;
|
|
Packit |
90a5c9 |
while (step < 24) {
|
|
Packit |
90a5c9 |
step *= 2
|
|
Packit |
90a5c9 |
sx *= 2
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
if (verts) {
|
|
Packit |
90a5c9 |
ctx.beginPath();
|
|
Packit |
90a5c9 |
for (var x = 1; x < values.length; x++) {
|
|
Packit |
90a5c9 |
if (x % sx == 0) {
|
|
Packit |
90a5c9 |
var y = (wspace*0.75) + (step * (x/sx));
|
|
Packit |
90a5c9 |
ctx.moveTo(y, 30);
|
|
Packit |
90a5c9 |
ctx.lineTo(y, canvas.height - 10 - lheight);
|
|
Packit |
90a5c9 |
ctx.lineWidth = 0.25;
|
|
Packit |
90a5c9 |
ctx.stroke();
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
ctx.closePath();
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Some pre-calculations of steps
|
|
Packit |
90a5c9 |
var step = (rectwidth) / (values.length > 1 ? values.length-1:1);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw X values if noX isn't set:
|
|
Packit |
90a5c9 |
if (noX != true) {
|
|
Packit |
90a5c9 |
ctx.beginPath();
|
|
Packit |
90a5c9 |
for (var i = 0; i < values.length; i++) {
|
|
Packit |
90a5c9 |
zz = 1
|
|
Packit |
90a5c9 |
var x = (wspace*0.75) + ((step) * i);
|
|
Packit |
90a5c9 |
var y = canvas.height - lheight + 5;
|
|
Packit |
90a5c9 |
if (i % sx == 0) {
|
|
Packit |
90a5c9 |
ctx.translate(x, y);
|
|
Packit |
90a5c9 |
ctx.moveTo(0,0);
|
|
Packit |
90a5c9 |
ctx.lineTo(0,-15);
|
|
Packit |
90a5c9 |
ctx.stroke();
|
|
Packit |
90a5c9 |
ctx.rotate(45*Math.PI/180);
|
|
Packit |
90a5c9 |
ctx.textAlign = "left";
|
|
Packit |
90a5c9 |
var val = values[i][0];
|
|
Packit |
90a5c9 |
if (val.constructor.toString().match("Date()")) {
|
|
Packit |
90a5c9 |
val = val.toDateString();
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
ctx.fillText(val.toString(), 0, 0);
|
|
Packit |
90a5c9 |
ctx.rotate(-45*Math.PI/180);
|
|
Packit |
90a5c9 |
ctx.translate(-x,-y);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
ctx.closePath();
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw each line
|
|
Packit |
90a5c9 |
var stacks = [];
|
|
Packit |
90a5c9 |
var pstacks = [];
|
|
Packit |
90a5c9 |
for (k in values) { if (k > 0) { stacks[k] = 0; pstacks[k] = canvas.height - 40 - lheight; }}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
for (k in titles) {
|
|
Packit |
90a5c9 |
var maxY = 0, minY = 99999;
|
|
Packit |
90a5c9 |
ctx.beginPath();
|
|
Packit |
90a5c9 |
var color = colors[k % colors.length][0];
|
|
Packit |
90a5c9 |
var f = parseInt(k) + 1;
|
|
Packit |
90a5c9 |
if (noX) {
|
|
Packit |
90a5c9 |
f = parseInt(k);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var value = values[0][f];
|
|
Packit |
90a5c9 |
var step = rectwidth / numLines;
|
|
Packit |
90a5c9 |
var x = (wspace*0.75);
|
|
Packit |
90a5c9 |
var y = (canvas.height - 10 - lheight) - (((value-min) / (max-min)) * (canvas.height - 40 - lheight));
|
|
Packit |
90a5c9 |
var py = y;
|
|
Packit |
90a5c9 |
if (stack) {
|
|
Packit |
90a5c9 |
stacks[0] = stacks[0] ? stacks[0] : 0
|
|
Packit |
90a5c9 |
y -= stacks[0];
|
|
Packit |
90a5c9 |
pstacks[0] = stacks[0];
|
|
Packit |
90a5c9 |
stacks[0] += (((value-min) / (max-min)) * (canvas.height - 40 - lheight));
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw line
|
|
Packit |
90a5c9 |
ctx.moveTo(x, y);
|
|
Packit |
90a5c9 |
var pvalY = y;
|
|
Packit |
90a5c9 |
var pvalX = x;
|
|
Packit |
90a5c9 |
for (var i in values) {
|
|
Packit |
90a5c9 |
if (i > 0) {
|
|
Packit |
90a5c9 |
x = (wspace*0.75) + (step*i);
|
|
Packit |
90a5c9 |
var f = parseInt(k) + 1;
|
|
Packit |
90a5c9 |
if (noX == true) {
|
|
Packit |
90a5c9 |
f = parseInt(k);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
value = values[i][f];
|
|
Packit |
90a5c9 |
y = (canvas.height - 10 - lheight) - (((value-min) / (max-min)) * (canvas.height - 40 - lheight));
|
|
Packit |
90a5c9 |
if (stack) {
|
|
Packit |
90a5c9 |
y -= stacks[i];
|
|
Packit |
90a5c9 |
pstacks[i] = stacks[i];
|
|
Packit |
90a5c9 |
stacks[i] += (((value-min) / (max-min)) * (canvas.height - 40- lheight));
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if (y > maxY) maxY = y;
|
|
Packit |
90a5c9 |
if (y < minY) minY = y;
|
|
Packit |
90a5c9 |
// Draw curved lines??
|
|
Packit |
90a5c9 |
/* We'll do: (x1,y1)-----(x1.5,y1)
|
|
Packit |
90a5c9 |
* |
|
|
Packit |
90a5c9 |
* (x1.5,y2)-----(x2,y2)
|
|
Packit |
90a5c9 |
* with a quadratic beizer thingy
|
|
Packit |
90a5c9 |
*/
|
|
Packit |
90a5c9 |
if (curve) {
|
|
Packit |
90a5c9 |
ctx.bezierCurveTo((pvalX + x) / 2, pvalY, (pvalX + x) / 2, y, x, y);
|
|
Packit |
90a5c9 |
pvalX = x;
|
|
Packit |
90a5c9 |
pvalY = y;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
// Nope, just draw straight lines
|
|
Packit |
90a5c9 |
else {
|
|
Packit |
90a5c9 |
ctx.lineTo(x, y);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if (spots) {
|
|
Packit |
90a5c9 |
ctx.fillStyle = color;
|
|
Packit |
90a5c9 |
ctx.translate(x-2, y-2);
|
|
Packit |
90a5c9 |
ctx.rotate(-45*Math.PI/180);
|
|
Packit |
90a5c9 |
ctx.fillRect(-2,1,4,4);
|
|
Packit |
90a5c9 |
ctx.rotate(45*Math.PI/180);
|
|
Packit |
90a5c9 |
ctx.translate(-x+2, -y+2);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
ctx.lineWidth = 4;
|
|
Packit |
90a5c9 |
ctx.strokeStyle = color;
|
|
Packit |
90a5c9 |
ctx.stroke();
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
if (minY == maxY) maxY++;
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw stack area
|
|
Packit |
90a5c9 |
if (stack) {
|
|
Packit |
90a5c9 |
ctx.globalAlpha = 0.65;
|
|
Packit |
90a5c9 |
for (i in values) {
|
|
Packit |
90a5c9 |
if (i > 0) {
|
|
Packit |
90a5c9 |
var f = parseInt(k) + 1;
|
|
Packit |
90a5c9 |
if (noX == true) {
|
|
Packit |
90a5c9 |
f = parseInt(k);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
x = (wspace*0.75) + (step*i);
|
|
Packit |
90a5c9 |
value = values[i][f];
|
|
Packit |
90a5c9 |
y = (canvas.height - 10 - lheight) - (((value-min) / (max-min)) * (canvas.height - 40 - lheight));
|
|
Packit |
90a5c9 |
y -= stacks[i];
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var pvalY = y;
|
|
Packit |
90a5c9 |
var pvalX = x;
|
|
Packit |
90a5c9 |
if (y > maxY) maxY = y;
|
|
Packit |
90a5c9 |
if (y < minY) minY = y;
|
|
Packit |
90a5c9 |
for (i in values) {
|
|
Packit |
90a5c9 |
var l = values.length - i - 1;
|
|
Packit |
90a5c9 |
x = (wspace*0.75) + (step*l);
|
|
Packit |
90a5c9 |
y = canvas.height - 10 - lheight - pstacks[l];
|
|
Packit |
90a5c9 |
if (y > maxY) maxY = y;
|
|
Packit |
90a5c9 |
if (y < minY) minY = y;
|
|
Packit |
90a5c9 |
if (curve) {
|
|
Packit |
90a5c9 |
ctx.bezierCurveTo((pvalX + x) / 2, pvalY, (pvalX + x) / 2, y, x, y);
|
|
Packit |
90a5c9 |
pvalX = x;
|
|
Packit |
90a5c9 |
pvalY = y;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
else {
|
|
Packit |
90a5c9 |
ctx.lineTo(x, y);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
ctx.lineTo((wspace*0.75), py - pstacks[0]);
|
|
Packit |
90a5c9 |
ctx.lineWidth = 0;
|
|
Packit |
90a5c9 |
var grad = ctx.createLinearGradient(0, minY, 0, maxY);
|
|
Packit |
90a5c9 |
grad.addColorStop(0.25, colors[k % colors.length][0])
|
|
Packit |
90a5c9 |
grad.addColorStop(1, colors[k % colors.length][1])
|
|
Packit |
90a5c9 |
ctx.strokeStyle = colors[k % colors.length][0];
|
|
Packit |
90a5c9 |
ctx.fillStyle = grad;
|
|
Packit |
90a5c9 |
ctx.fill();
|
|
Packit |
90a5c9 |
ctx.fillStyle = "#000"
|
|
Packit |
90a5c9 |
ctx.strokeStyle = "#000"
|
|
Packit |
90a5c9 |
ctx.globalAlpha = 1;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
ctx.closePath();
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// draw feather
|
|
Packit |
90a5c9 |
base_image = new Image();
|
|
Packit |
90a5c9 |
base_image.src = 'data:image/png;base64,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';
|
|
Packit |
90a5c9 |
base_image.onload = function(){
|
|
Packit |
90a5c9 |
ctx.globalAlpha = 0.15
|
|
Packit |
90a5c9 |
ctx.drawImage(base_image, (canvas.width/2) - 64 - (lwidth/2), (canvas.height/2) - 128);
|
|
Packit |
90a5c9 |
ctx.globalAlpha = 1
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
/* Function for drawing line charts
|
|
Packit |
90a5c9 |
* Example usage:
|
|
Packit |
90a5c9 |
* quokkaLines("myCanvas", ['Line a', 'Line b', 'Line c'], [ [x1,a1,b1,c1], [x2,a2,b2,c2], [x3,a3,b3,c3] ], { stacked: true, curve: false, title: "Some title" } );
|
|
Packit |
90a5c9 |
*/
|
|
Packit |
90a5c9 |
function quokkaBars(id, titles, values, options) {
|
|
Packit |
90a5c9 |
var canvas = document.getElementById(id);
|
|
Packit |
90a5c9 |
var ctx=canvas.getContext("2d");
|
|
Packit |
90a5c9 |
// clear the canvas first
|
|
Packit |
90a5c9 |
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
Packit |
90a5c9 |
var lwidth = 150;
|
|
Packit |
90a5c9 |
var lheight = 75;
|
|
Packit |
90a5c9 |
var stack = options ? options.stack : false;
|
|
Packit |
90a5c9 |
var astack = options ? options.astack : false;
|
|
Packit |
90a5c9 |
var curve = options ? options.curve : false;
|
|
Packit |
90a5c9 |
var title = options ? options.title : null;
|
|
Packit |
90a5c9 |
var noX = options ? options.nox : false;
|
|
Packit |
90a5c9 |
var verts = options ? options.verts : true;
|
|
Packit |
90a5c9 |
if (noX) {
|
|
Packit |
90a5c9 |
lheight = 0;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw a border
|
|
Packit |
90a5c9 |
ctx.lineWidth = 0.5;
|
|
Packit |
90a5c9 |
ctx.strokeRect(25, 30, canvas.width - lwidth - 40, canvas.height - lheight - 40);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw a title if set:
|
|
Packit |
90a5c9 |
if (title != null) {
|
|
Packit |
90a5c9 |
ctx.font="15px Arial";
|
|
Packit |
90a5c9 |
ctx.fillStyle = "#000";
|
|
Packit |
90a5c9 |
ctx.textAlign = "center";
|
|
Packit |
90a5c9 |
ctx.fillText(title,(canvas.width-lwidth)/2, 15);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw legend
|
|
Packit |
90a5c9 |
ctx.textAlign = "left";
|
|
Packit |
90a5c9 |
var posY = 50;
|
|
Packit |
90a5c9 |
for (var k in titles) {
|
|
Packit |
90a5c9 |
var x = parseInt(k)
|
|
Packit |
90a5c9 |
if (!noX) {
|
|
Packit |
90a5c9 |
x = x + 1;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var title = titles[k];
|
|
Packit |
90a5c9 |
if (title && title.length > 0) {
|
|
Packit |
90a5c9 |
ctx.fillStyle = colors[k % colors.length][0];
|
|
Packit |
90a5c9 |
ctx.fillRect(canvas.width - lwidth + 20, posY-10, 10, 10);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Add legend text
|
|
Packit |
90a5c9 |
ctx.font="12px Arial";
|
|
Packit |
90a5c9 |
ctx.fillStyle = "#000";
|
|
Packit |
90a5c9 |
ctx.fillText(title,canvas.width - lwidth + 40, posY);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
posY += 15;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Find max and min
|
|
Packit |
90a5c9 |
var max = null;
|
|
Packit |
90a5c9 |
var min = 0;
|
|
Packit |
90a5c9 |
var stacked = null;
|
|
Packit |
90a5c9 |
for (x in values) {
|
|
Packit |
90a5c9 |
var s = 0;
|
|
Packit |
90a5c9 |
for (y in values[x]) {
|
|
Packit |
90a5c9 |
if (y > 0 || noX) {
|
|
Packit |
90a5c9 |
s += values[x][y];
|
|
Packit |
90a5c9 |
if (max == null || max < values[x][y]) {
|
|
Packit |
90a5c9 |
max = values[x][y];
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if (min == null || min > values[x][y]) {
|
|
Packit |
90a5c9 |
min = values[x][y];
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if (stacked == null || stacked < s) {
|
|
Packit |
90a5c9 |
stacked = s;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if (min == max) {
|
|
Packit |
90a5c9 |
max++;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if (stack) {
|
|
Packit |
90a5c9 |
min = 0;
|
|
Packit |
90a5c9 |
max = stacked;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Set number of lines to draw and each step
|
|
Packit |
90a5c9 |
var numLines = 5;
|
|
Packit |
90a5c9 |
var step = (max-min) / (numLines+1);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Prettify the max value so steps aren't ugly numbers
|
|
Packit |
90a5c9 |
if (step %1 != 0) {
|
|
Packit |
90a5c9 |
step = (Math.round(step+0.5));
|
|
Packit |
90a5c9 |
max = step * (numLines+1);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw horizontal lines
|
|
Packit |
90a5c9 |
for (x = numLines; x >= 0; x--) {
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
var y = 30 + (((canvas.height-40-lheight) / (numLines+1)) * (x+1));
|
|
Packit |
90a5c9 |
ctx.moveTo(25, y);
|
|
Packit |
90a5c9 |
ctx.lineTo(canvas.width - lwidth - 15, y);
|
|
Packit |
90a5c9 |
ctx.lineWidth = 0.25;
|
|
Packit |
90a5c9 |
ctx.stroke();
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Add values
|
|
Packit |
90a5c9 |
ctx.font="10px Arial";
|
|
Packit |
90a5c9 |
ctx.fillStyle = "#000";
|
|
Packit |
90a5c9 |
ctx.textAlign = "right";
|
|
Packit |
90a5c9 |
ctx.fillText( Math.round( ((max-min) - (step*(x+1))) * 100 ) / 100,canvas.width - lwidth + 12, y-4);
|
|
Packit |
90a5c9 |
ctx.fillText( Math.round( ((max-min) - (step*(x+1))) * 100 ) / 100,20, y-4);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw vertical lines
|
|
Packit |
90a5c9 |
var sx = 1
|
|
Packit |
90a5c9 |
var numLines = values.length-1;
|
|
Packit |
90a5c9 |
var step = (canvas.width - lwidth - 40) / values.length;
|
|
Packit |
90a5c9 |
while (step < 24) {
|
|
Packit |
90a5c9 |
step *= 2
|
|
Packit |
90a5c9 |
sx *= 2
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
if (verts) {
|
|
Packit |
90a5c9 |
ctx.beginPath();
|
|
Packit |
90a5c9 |
for (var x = 1; x < values.length; x++) {
|
|
Packit |
90a5c9 |
if (x % sx == 0) {
|
|
Packit |
90a5c9 |
var y = 35 + (step * (x/sx));
|
|
Packit |
90a5c9 |
ctx.moveTo(y, 30);
|
|
Packit |
90a5c9 |
ctx.lineTo(y, canvas.height - 10 - lheight);
|
|
Packit |
90a5c9 |
ctx.lineWidth = 0.25;
|
|
Packit |
90a5c9 |
ctx.stroke();
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Some pre-calculations of steps
|
|
Packit |
90a5c9 |
var step = (canvas.width - lwidth - 48) / values.length;
|
|
Packit |
90a5c9 |
var smallstep = (step / titles.length) - 2;
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw X values if noX isn't set:
|
|
Packit |
90a5c9 |
if (noX != true) {
|
|
Packit |
90a5c9 |
ctx.beginPath();
|
|
Packit |
90a5c9 |
for (var i = 0; i < values.length; i++) {
|
|
Packit |
90a5c9 |
smallstep = (step / (values[i].length-1)) - 2;
|
|
Packit |
90a5c9 |
zz = 1
|
|
Packit |
90a5c9 |
var x = 35 + ((step) * i);
|
|
Packit |
90a5c9 |
var y = canvas.height - lheight + 5;
|
|
Packit |
90a5c9 |
if (i % sx == 0) {
|
|
Packit |
90a5c9 |
ctx.translate(x, y);
|
|
Packit |
90a5c9 |
ctx.moveTo(0,0);
|
|
Packit |
90a5c9 |
ctx.lineTo(0,-15);
|
|
Packit |
90a5c9 |
ctx.stroke();
|
|
Packit |
90a5c9 |
ctx.rotate(45*Math.PI/180);
|
|
Packit |
90a5c9 |
ctx.textAlign = "left";
|
|
Packit |
90a5c9 |
var val = values[i][0];
|
|
Packit |
90a5c9 |
if (val.constructor.toString().match("Date()")) {
|
|
Packit |
90a5c9 |
val = val.toDateString();
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
ctx.fillText(val.toString(), 0, 0);
|
|
Packit |
90a5c9 |
ctx.rotate(-45*Math.PI/180);
|
|
Packit |
90a5c9 |
ctx.translate(-x,-y);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw each line
|
|
Packit |
90a5c9 |
var stacks = [];
|
|
Packit |
90a5c9 |
var pstacks = [];
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
for (k in values) {
|
|
Packit |
90a5c9 |
smallstep = (step / (values[k].length)) - 2;
|
|
Packit |
90a5c9 |
stacks[k] = 0;
|
|
Packit |
90a5c9 |
pstacks[k] = canvas.height - 40 - lheight;
|
|
Packit |
90a5c9 |
var beginX = 0;
|
|
Packit |
90a5c9 |
for (i in values[k]) {
|
|
Packit |
90a5c9 |
if (i > 0 || noX) {
|
|
Packit |
90a5c9 |
var z = parseInt(i);
|
|
Packit |
90a5c9 |
var zz = z;
|
|
Packit |
90a5c9 |
if (!noX) {
|
|
Packit |
90a5c9 |
z = parseInt(i) + 1;
|
|
Packit |
90a5c9 |
zz = z - 2;
|
|
Packit |
90a5c9 |
if (z > values[k].length) {
|
|
Packit |
90a5c9 |
break;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var value = values[k][i];
|
|
Packit |
90a5c9 |
var title = titles[i];
|
|
Packit |
90a5c9 |
var color = colors[zz % colors.length][1];
|
|
Packit |
90a5c9 |
var fcolor = colors[zz % colors.length][2];
|
|
Packit |
90a5c9 |
if (values[k][2] && values[k][2].toString().match(/^#.+$/)) {
|
|
Packit |
90a5c9 |
color = values[k][2]
|
|
Packit |
90a5c9 |
fcolor = values[k][2]
|
|
Packit |
90a5c9 |
smallstep = (step / (values[k].length-2)) - 2;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
var x = ((step) * k) + ((smallstep+2) * zz) + 5;
|
|
Packit |
90a5c9 |
var y = canvas.height - 10 - lheight;
|
|
Packit |
90a5c9 |
var mdiff = (max-min);
|
|
Packit |
90a5c9 |
mdiff = (mdiff == 0) ? 1 : mdiff;
|
|
Packit |
90a5c9 |
var height = ((canvas.height - 40 - lheight) / (mdiff)) * value * -1;
|
|
Packit |
90a5c9 |
var width = smallstep - 2;
|
|
Packit |
90a5c9 |
if (width <= 1) {
|
|
Packit |
90a5c9 |
width = 1
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
if (stack) {
|
|
Packit |
90a5c9 |
width = step - 10;
|
|
Packit |
90a5c9 |
y -= stacks[k];
|
|
Packit |
90a5c9 |
stacks[k] -= height;
|
|
Packit |
90a5c9 |
x = (step * k) + 4;
|
|
Packit |
90a5c9 |
if (astack) {
|
|
Packit |
90a5c9 |
y = canvas.height - 10 - lheight;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
// Draw bar
|
|
Packit |
90a5c9 |
ctx.beginPath();
|
|
Packit |
90a5c9 |
ctx.lineWidth = 2;
|
|
Packit |
90a5c9 |
ctx.strokeStyle = color;
|
|
Packit |
90a5c9 |
ctx.strokeRect(27 + x, y, width, height);
|
|
Packit |
90a5c9 |
var alpha = 0.75
|
|
Packit |
90a5c9 |
if (fcolor.r) {
|
|
Packit |
90a5c9 |
ctx.fillStyle = 'rgba('+ [parseInt(fcolor.r*255),parseInt(fcolor.g*255),parseInt(fcolor.b*255),alpha].join(",") + ')';
|
|
Packit |
90a5c9 |
} else {
|
|
Packit |
90a5c9 |
ctx.fillStyle = fcolor;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
ctx.fillRect(27 + x, y, width, height);
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
]==]
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
status_css = [[
|
|
Packit |
90a5c9 |
html {
|
|
Packit |
90a5c9 |
font-size: 14px;
|
|
Packit |
90a5c9 |
position: relative;
|
|
Packit |
90a5c9 |
background: #272B30;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
body {
|
|
Packit |
90a5c9 |
background-color: #272B30;
|
|
Packit |
90a5c9 |
color: #000;
|
|
Packit |
90a5c9 |
margin: 0 auto;
|
|
Packit |
90a5c9 |
min-height: 100%;
|
|
Packit |
90a5c9 |
font-family: Arial, Helvetica, sans-serif;
|
|
Packit |
90a5c9 |
font-weight: normal;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.navbarLeft {
|
|
Packit |
90a5c9 |
background: linear-gradient(to bottom, #F8A900 0%,#D88900 100%);
|
|
Packit |
90a5c9 |
width: 200px;
|
|
Packit |
90a5c9 |
height: 30px;
|
|
Packit |
90a5c9 |
padding-top: 2px;
|
|
Packit |
90a5c9 |
font-size: 1.35rem;
|
|
Packit |
90a5c9 |
color: #FFF;
|
|
Packit |
90a5c9 |
border-bottom: 2px solid #000;
|
|
Packit |
90a5c9 |
float: left;
|
|
Packit |
90a5c9 |
text-align: center;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.navbarRight {
|
|
Packit |
90a5c9 |
background: linear-gradient(to bottom, #EFEFEF 0%,#EEE 100%);
|
|
Packit |
90a5c9 |
width: calc(100% - 240px);
|
|
Packit |
90a5c9 |
height: 28px;
|
|
Packit |
90a5c9 |
color: #333;
|
|
Packit |
90a5c9 |
border-bottom: 2px solid #000;
|
|
Packit |
90a5c9 |
float: left;
|
|
Packit |
90a5c9 |
font-size: 1.3rem;
|
|
Packit |
90a5c9 |
padding-top: 4px;
|
|
Packit |
90a5c9 |
text-align: left;
|
|
Packit |
90a5c9 |
padding-left: 40px;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.wrapper {
|
|
Packit |
90a5c9 |
width: 100%;
|
|
Packit |
90a5c9 |
float: left;
|
|
Packit |
90a5c9 |
background: #33363F;
|
|
Packit |
90a5c9 |
min-height: calc(100% - 80px);
|
|
Packit |
90a5c9 |
position: relative;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.serverinfo {
|
|
Packit |
90a5c9 |
float: left;
|
|
Packit |
90a5c9 |
width: 200px;
|
|
Packit |
90a5c9 |
height: calc(100% - 34px);
|
|
Packit |
90a5c9 |
background: #293D4C;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.skey {
|
|
Packit |
90a5c9 |
background: rgba(30,30,30,0.3);
|
|
Packit |
90a5c9 |
color: #C6E7FF;
|
|
Packit |
90a5c9 |
font-weight: bold;
|
|
Packit |
90a5c9 |
padding: 2px;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.sval {
|
|
Packit |
90a5c9 |
padding: 2px;
|
|
Packit |
90a5c9 |
background: rgba(30,30,30,0.3);
|
|
Packit |
90a5c9 |
color: #FFF;
|
|
Packit |
90a5c9 |
font-size: 0.8rem;
|
|
Packit |
90a5c9 |
border-bottom: 1px solid rgba(200,200,200,0.2);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.charts {
|
|
Packit |
90a5c9 |
padding: 0px;
|
|
Packit |
90a5c9 |
width: calc(100% - 220px);
|
|
Packit |
90a5c9 |
max-width: 1000px;
|
|
Packit |
90a5c9 |
min-height: 100%;
|
|
Packit |
90a5c9 |
margin: 0px auto;
|
|
Packit |
90a5c9 |
position: relative;
|
|
Packit |
90a5c9 |
float: left;
|
|
Packit |
90a5c9 |
margin-left: 20px;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
pre, code {
|
|
Packit |
90a5c9 |
font-family: "Courier New", Courier, monospace;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
strong {
|
|
Packit |
90a5c9 |
font-weight: bold;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
q, em, var {
|
|
Packit |
90a5c9 |
font-style: italic;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
/* h1 */
|
|
Packit |
90a5c9 |
/* ====================== */
|
|
Packit |
90a5c9 |
h1 {
|
|
Packit |
90a5c9 |
padding: 0.2em;
|
|
Packit |
90a5c9 |
margin: 0;
|
|
Packit |
90a5c9 |
border: 1px solid #405871;
|
|
Packit |
90a5c9 |
background-color: inherit;
|
|
Packit |
90a5c9 |
color: #036;
|
|
Packit |
90a5c9 |
text-decoration: none;
|
|
Packit |
90a5c9 |
font-size: 22px;
|
|
Packit |
90a5c9 |
font-weight: bold;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
/* h2 */
|
|
Packit |
90a5c9 |
/* ====================== */
|
|
Packit |
90a5c9 |
h2 {
|
|
Packit |
90a5c9 |
padding: 0.2em 0 0.2em 0.7em;
|
|
Packit |
90a5c9 |
margin: 0 0 0.5em 0;
|
|
Packit |
90a5c9 |
text-decoration: none;
|
|
Packit |
90a5c9 |
font-size: 18px;
|
|
Packit |
90a5c9 |
font-weight: bold;
|
|
Packit |
90a5c9 |
text-align: center;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
#modules {
|
|
Packit |
90a5c9 |
margin-top:20px;
|
|
Packit |
90a5c9 |
display:none;
|
|
Packit |
90a5c9 |
width:400px;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.servers {
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
width: 1244px;
|
|
Packit |
90a5c9 |
background: #EEE;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
tr:nth-child(odd) {
|
|
Packit |
90a5c9 |
background: #F6F6F6;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
tr:nth-child(even) {
|
|
Packit |
90a5c9 |
background: #EBEBEB;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
td {
|
|
Packit |
90a5c9 |
padding: 2px;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
table {
|
|
Packit |
90a5c9 |
border: 1px solid #333;
|
|
Packit |
90a5c9 |
padding: 0px;
|
|
Packit |
90a5c9 |
margin: 5px;
|
|
Packit |
90a5c9 |
min-width: 360px;
|
|
Packit |
90a5c9 |
background: #999;
|
|
Packit |
90a5c9 |
font-size: 0.8rem;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
canvas {
|
|
Packit |
90a5c9 |
background: #FFF;
|
|
Packit |
90a5c9 |
margin: 3px;
|
|
Packit |
90a5c9 |
text-align: center;
|
|
Packit |
90a5c9 |
padding: 2px;
|
|
Packit |
90a5c9 |
border-radius: 10px;
|
|
Packit |
90a5c9 |
border: 1px solid #999;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.canvas_wide {
|
|
Packit |
90a5c9 |
position: relative;
|
|
Packit |
90a5c9 |
width: 65%;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
.canvas_narrow {
|
|
Packit |
90a5c9 |
position: relative;
|
|
Packit |
90a5c9 |
width: 27%;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
a {
|
|
Packit |
90a5c9 |
color: #FFA;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.statsbox {
|
|
Packit |
90a5c9 |
border-radius: 3px;
|
|
Packit |
90a5c9 |
background: #3C3E47;
|
|
Packit |
90a5c9 |
min-width: 150px;
|
|
Packit |
90a5c9 |
height: 60px;
|
|
Packit |
90a5c9 |
float: left;
|
|
Packit |
90a5c9 |
margin: 15px;
|
|
Packit |
90a5c9 |
padding: 10px;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.btn {
|
|
Packit |
90a5c9 |
background: linear-gradient(to bottom, #72ca72 0%,#55bf55 100%);
|
|
Packit |
90a5c9 |
border-radius: 5px;
|
|
Packit |
90a5c9 |
color: #FFF;
|
|
Packit |
90a5c9 |
text-decoration: none;
|
|
Packit |
90a5c9 |
padding-top: 6px;
|
|
Packit |
90a5c9 |
padding-bottom: 6px;
|
|
Packit |
90a5c9 |
padding-left: 3px;
|
|
Packit |
90a5c9 |
padding-right: 3px;
|
|
Packit |
90a5c9 |
font-weight: bold;
|
|
Packit |
90a5c9 |
text-shadow: 1px 1px rgba(0,0,0,0.4);
|
|
Packit |
90a5c9 |
margin: 12px;
|
|
Packit |
90a5c9 |
float: left;
|
|
Packit |
90a5c9 |
clear: none;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.infobox_wrapper {
|
|
Packit |
90a5c9 |
float: left;
|
|
Packit |
90a5c9 |
min-width: 200px;
|
|
Packit |
90a5c9 |
margin: 10px;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
.infobox_title {
|
|
Packit |
90a5c9 |
border-top-left-radius: 4px;
|
|
Packit |
90a5c9 |
border-top-right-radius: 4px;
|
|
Packit |
90a5c9 |
background: #FAB227;
|
|
Packit |
90a5c9 |
color: #FFF;
|
|
Packit |
90a5c9 |
border: 2px solid #FAB227;
|
|
Packit |
90a5c9 |
border-bottom: none;
|
|
Packit |
90a5c9 |
font-weight: bold;
|
|
Packit |
90a5c9 |
text-align: center;
|
|
Packit |
90a5c9 |
width: 100%;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
.infobox {
|
|
Packit |
90a5c9 |
background: #222222;
|
|
Packit |
90a5c9 |
border: 2px solid #FAB227;
|
|
Packit |
90a5c9 |
border-top: none;
|
|
Packit |
90a5c9 |
color: #EFEFEF;
|
|
Packit |
90a5c9 |
border-bottom-left-radius: 4px;
|
|
Packit |
90a5c9 |
border-bottom-right-radius: 4px;
|
|
Packit |
90a5c9 |
float: left;
|
|
Packit |
90a5c9 |
width: 100%;
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.serverinfo ul {
|
|
Packit |
90a5c9 |
margin: 0px;
|
|
Packit |
90a5c9 |
padding: 0px;
|
|
Packit |
90a5c9 |
margin-top: 20px;
|
|
Packit |
90a5c9 |
list-style: none;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.serverinfo ul li .btn {
|
|
Packit |
90a5c9 |
width: calc(100% - 8px);
|
|
Packit |
90a5c9 |
margin: 0px;
|
|
Packit |
90a5c9 |
border: 0px;
|
|
Packit |
90a5c9 |
border-radius: 0px;
|
|
Packit |
90a5c9 |
padding: 0px;
|
|
Packit |
90a5c9 |
padding-top: 8px;
|
|
Packit |
90a5c9 |
padding-left: 8px;
|
|
Packit |
90a5c9 |
height: 24px;
|
|
Packit |
90a5c9 |
background: rgba(0,0,0,0.2);
|
|
Packit |
90a5c9 |
border-bottom: 1px solid rgba(100,100,100,0.3);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.serverinfo ul li:nth-child(1) {
|
|
Packit |
90a5c9 |
border-top: 1px solid rgba(100,100,100,0.3);
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
.serverinfo ul li .btn.active {
|
|
Packit |
90a5c9 |
background: rgba(30,30,50,0.2);
|
|
Packit |
90a5c9 |
border-left: 4px solid #27FAB2;
|
|
Packit |
90a5c9 |
padding-left: 4px;
|
|
Packit |
90a5c9 |
color: #FFE;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
|
|
Packit |
90a5c9 |
.serverinfo ul li .btn:hover {
|
|
Packit |
90a5c9 |
background: rgba(50,50,50,0.15);
|
|
Packit |
90a5c9 |
border-left: 4px solid #FAB227;
|
|
Packit |
90a5c9 |
padding-left: 4px;
|
|
Packit |
90a5c9 |
color: #FFE;
|
|
Packit |
90a5c9 |
}
|
|
Packit |
90a5c9 |
]]
|