From 4f7359298ef0bde0dbb9b42763c4eefa840a1cf7 Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Sun, 8 May 2005 23:39:46 +0000 Subject: [PATCH] * src/web/ejabberd_web_admin.erl: Images now specified via CSS, design slightly updated, added last activity statistics (thanks to Sergei Golovan) SVN Revision: 344 --- ChangeLog | 6 + src/web/ejabberd_web_admin.erl | 587 ++++++++++++++++++--------------- 2 files changed, 333 insertions(+), 260 deletions(-) diff --git a/ChangeLog b/ChangeLog index 4e14eb4a0..195db8b22 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +2005-05-09 Alexey Shchepin + + * src/web/ejabberd_web_admin.erl: Images now specified via CSS, + design slightly updated, added last activity statistics (thanks to + Sergei Golovan) + 2005-05-07 Alexey Shchepin * src/stringprep/stringprep_drv.c: Added check for bidi diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl index 7fab28e3a..754e89c37 100644 --- a/src/web/ejabberd_web_admin.erl +++ b/src/web/ejabberd_web_admin.erl @@ -64,154 +64,117 @@ make_xhtml(Els, Lang) -> {"type", "text/css"}, {"rel", "stylesheet"}], []}]}, ?XE("body", - [?XAE("table", - [{"id", "main"}], - [?XE("tbody", - [?XAE("tr", - [{"id", "top"}], - [?XE("td", - [?XE("table", - [?XE("tbody", - [?XE("tr", - [?XE("td", - [?XAE("a", [{"href", "/admin/"}], - [?XA("img", [{"src", "/admin/logo.png"}, - {"width", "343"}, - {"height", "55"}, - {"alt", "ejabberd"}, - {"border", "0"}])])]), - ?XAE("td", [{"width", "100%"}, - {"background", "/admin/logo-fill.png"}], - [?XAE("a", [{"href", "/admin/"}], - [?XA("img", [{"src", "/admin/1x1tr.gif"}, - {"width", "100%"}, - {"height", "55"}, - {"alt", ""}, - {"border", "0"}])])] - )])]) - ])])]), - ?XAE("tr", - [{"id", "middle"}], - [?XE("td", - [?XAE("table", - [{"id", "middle-table"}], - [?XE("tbody", - [?XE("tr", - [?XAE("td", - [{"id", "middle-td1"}], - [?XAE("ul", - [{"id", "navlist"}], - [?LI([?ACT("/admin/acls/", "Access Control Lists")]), - ?LI([?ACT("/admin/access/", "Access Rules")]), - ?LI([?ACT("/admin/users/", "Users")]), - ?LI([?ACT("/admin/online-users/", "Online Users")]), - ?LI([?ACT("/admin/nodes/", "Nodes")]), - ?LI([?ACT("/admin/stats/", "Statistics")]) - ] ++ - case lists:member(mod_shared_roster, - gen_mod:loaded_modules()) of - true -> - [?LI([?ACT("/admin/shared-roster/", "Shared Roster")])]; - false -> - [] - end - )]), - ?XAE("td", - [{"id", "middle-td2"}], - [?XAE("div", [{"id", "content"}], Els)])])]) - ])])]), - ?XAE("tr", - [{"id", "bottom"}], - [?XE("td", - [?XE("table", - [?XE("tbody", - [?XE("tr", - [?XCT("td", - "ejabberd (c) 2002-2005 Alexey Shchepin, 2004-2005 Process One") - ])]) - ])])])])])]) + [?XAE("div", + [{"id", "container"}], + [?XAE("div", + [{"id", "header"}], + [?XE("h1", + [?ACT("/admin/", "ejabberd administration")] + )]), + ?XAE("div", + [{"id", "navigation"}], + [?XE("ul", + [?LI([?ACT("/admin/acls/", "Access Control Lists")]), + ?LI([?ACT("/admin/access/", "Access Rules")]), + ?LI([?ACT("/admin/users/", "Users")]), + ?LI([?ACT("/admin/online-users/", "Online Users")]), + ?LI([?ACT("/admin/last-activity/", "Last Activity")]), + ?LI([?ACT("/admin/nodes/", "Nodes")]), + ?LI([?ACT("/admin/stats/", "Statistics")]) + ] ++ + case lists:member(mod_shared_roster, + gen_mod:loaded_modules()) of + true -> + [?LI([?ACT("/admin/shared-roster/", "Shared Roster")])]; + false -> + [] + end + )]), + ?XAE("div", + [{"id", "content"}], + Els), + ?XAE("div", + [{"id", "clearcopyright"}], + [{xmlcdata, ""}])]), + ?XAE("div", + [{"id", "copyrightouter"}], + [?XAE("div", + [{"id", "copyright"}], + [?XCT("p", + "ejabberd (c) 2002-2005 Alexey Shchepin, 2004-2005 Process One") + ])])]) ]}}. css() -> " -body { +html,body { + background: white; + margin: 0; + padding: 0; + height: 100%; +} + +#container { + padding: 0; + margin: 0; + min-height: 100%; + height: 100%; + margin-bottom: -30px; +} + +html>body #container { + height: auto; +} + +#header h1 { + width: 100%; + height: 55px; + padding: 0; + margin: 0; + background: transparent url(\"/admin/logo-fill.png\"); +} + +#header h1 a { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 55px; + padding: 0; + margin: 0; + background: transparent url(\"/admin/logo.png\") no-repeat; + display: block; + text-indent: -700em; +} + +#clearcopyright { + display: block; + width: 100%; + height: 30px; +} + +#copyrightouter { + display: table; + width: 100%; + height: 30px; +} + +#copyright { + display: table-cell; + vertical-align: bottom; + width: 100%; + height: 30px; +} + +#copyright p { margin-left: 0; margin-right: 0; - margin-top: 0; + margin-top: 5px; margin-bottom: 0; -} - -#main { - border: none; - border-spacing: 0; - border-collapse: collapse; - background-color: #fe8a00; + padding-left: 0; + padding-right: 0; + padding-top: 1px; + padding-bottom: 1px; width: 100%; - height: 100%; - padding: 0; -} - -#main > tbody > tr > td { - padding: 0; -} - -#top > td > table { - border: none; - border-spacing: 0; - border-collapse: collapse; - background-color: #fe8a00; - width: 100%; - padding-top: 2px; -} - -#top table { - border: none; - border-spacing: 0; - border-collapse: collapse; - background-color: #fe8a00; - width: 100%; - padding-top: 2px; -} - -#top td { - padding: 0; -} - -#top img { - margin-bottom: 0px; -} - -#middle { - height: 100%; -} - -#middle-table { - border: none; - border-spacing: 0; - border-collapse: collapse; - width: 100%; - height: 100%; - empty-cells: show; -} - -#middle-td1, #middle-td2 { - padding: 0; - background-color: #ffffff; - vertical-align: top; -} - -#middle-td2 { - width: 100%; -} - -#bottom table { - border: none; - border-spacing: 0; - border-collapse: collapse; - width: 100%; -} - -#bottom table td { - padding: 0; color: #ffffff; background-color: #fe8a00; font-family: Verdana, Arial, Helvetica, sans-serif; @@ -220,86 +183,76 @@ body { text-align: center; } - /*td{ - font-size: 3pt; - } - td.a{ - color: #fc8800; - background-color: #fe8a00; - } - td.b{ - color: #333333; - background-color: #000000; - } - td.c{ - color: #743300; - background-color: #723100; - } - td.d{ - color: #fdc58a; - background-color: #ffc78c; - } - td.e{ - color: #fde1c7; - background-color: #ffe3c9; - } - td.f{ - color: #fdfdfd; - background-color: #ffffff; - }*/ - td.copy{ - color: #ffffff; - background-color: #fe8a00; - font-family: Verdana, Arial, Helvetica, sans-serif; - font-size: 7pt; - font-weight: bold; - text-align: center; - } +#navigation ul { + position: absolute; + top: 55px; + left: 0; + padding: 0 1px 1px; + margin: 0; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 7.5pt; + font-weight: bold; + background: #d47911; + width: 13em; +} - #navlist - { - padding: 0 1px 1px; - margin-left: 0; - font: bold 10px Verdana, sans-serif; - background: #d47911; - width: 13em; - } +#navigation ul li { + list-style: none; + margin: 0; + text-align: left; + display: inline; +} - #navlist li - { - list-style: none; - margin: 0; - text-align: left; - display: inline; - } +#navigation ul li a { + margin: 0; + display: block; + padding: 0.25em 0.5em 0.25em 0.75em; + border-left: 1em solid #ffc78c; + border-top: 1px solid gray; + background: #ffe3c9; + text-decoration: none; +} - #navlist li a - { - display: block; - padding: 0.25em 0.5em 0.25em 0.75em; - border-left: 1em solid #ffc78c; - border-top: 1px solid gray; - background: #ffe3c9; - text-decoration: none; - } +#navigation ul li a:link { + color: #844; +} - #navlist li a:link { color: #844; } - #navlist li a:visited { color: #766; } +#navigation ul li a:visited { + color: #766; +} - #navlist li a:hover - { - border-color: #fc8800; - color: #FFF; - background: #332; - } +#navigation ul li a:hover { + border-color: #fc8800; + color: #FFF; + background: #332; +} + +#lastactivity li { + font-weight: bold; + border: 1px solid #d6760e; + background-color: #fff2e8; + padding: 2px; + margin-bottom: -1px; +} + +td.copy { + color: #ffffff; + background-color: #fe8a00; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 7pt; + font-weight: bold; + text-align: center; +} input { - border: 1px solid #d6760e; - color: #723202; - background-color: #fff2e8; - vertical-align: middle; - margin-bottom: 0px; - padding: 0.1em; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 10pt; + border: 1px solid #d6760e; + color: #723202; + background-color: #fff2e8; + vertical-align: middle; + margin-bottom: 0px; + padding: 0.1em; } input[type=submit] { @@ -312,24 +265,24 @@ input[type=submit] { } textarea { - border: 1px solid #d6760e; - color: #723202; - background-color: #fff2e8; - vertical-align: middle; - margin-top: 7px; - /*margin-left: 7px; - margin-right: 7px;*/ - margin-bottom: 5px; - padding: 0.1em; + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 10pt; + border: 1px solid #d6760e; + color: #723202; + background-color: #fff2e8; + vertical-align: middle; + margin-top: 7px; + margin-bottom: 5px; + padding: 0.1em; } select { - border: 1px solid #d6760e; - color: #723202; - background-color: #fff2e8; - vertical-align: middle; - margin-bottom: 0px; - padding: 0.1em; + border: 1px solid #d6760e; + color: #723202; + background-color: #fff2e8; + vertical-align: middle; + margin-bottom: 0px; + padding: 0.1em; } thead { @@ -412,7 +365,6 @@ h1 { font-size: 14pt; font-weight: bold; text-align: center; - padding-left: 5px; padding-top: 2px; padding-bottom: 2px; margin-top: 0px; @@ -425,7 +377,6 @@ h2 { font-size: 12pt; font-weight: bold; text-align: center; - padding-left: 5px; padding-top: 2px; padding-bottom: 2px; margin-top: 0px; @@ -438,7 +389,6 @@ h3 { font-size: 10pt; font-weight: bold; text-align: left; - padding-left: 5px; padding-top: 20px; padding-bottom: 2px; margin-top: 0px; @@ -446,21 +396,21 @@ h3 { } #content a:link { - color: #444466; + color: #990000; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt; font-weight: bold; text-decoration: underlined; } #content a:visited { - color: #444466; + color: #990000; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt; font-weight: bold; text-decoration: underlined; } #content a:hover { - color: #222266; + color: #cc6600; font-family: Verdana, Arial, Helvetica, sans-serif; font-size: 10pt; font-weight: bold; @@ -468,7 +418,7 @@ h3 { } -#content li { +#content ul li { list-style-type: dot; font-size: 10pt; /*font-size: 7pt;*/ @@ -479,9 +429,11 @@ h3 { font-size: 10pt; } -div#content { - margin-left: 10px; - margin-top: 5px; +#content { + font-family: Verdana, Arial, Helvetica, sans-serif; + font-size: 10pt; + padding-left: 13em; + padding-top: 5px; } *.alignright { @@ -492,15 +444,27 @@ div#content { logo() -> jlib:decode_base64( - "iVBORw0KGgoAAAANSUhEUgAAAVcAAAA3CAMAAACPbPnEAAAAYFBMVEX///8CAgJyMgL+vm7Wdg7+igL+/v7+slb+qkb+4sr+ojP+nir+lhr+1qb+khL+wnb+wn7+zpb+jgb+yoz+xo7+tmL+pj7+mib+jg7+5sb+rlL+rkr+mh7+tl7+2q7+umpJ0uikAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxIAAAsSAdLdfvwAAAAHdElNRQfUBAUJBhWzc9qJAAABQ0lEQVR42u2bXU/CQBBFUUZFURAU5Ev4//+S3Ow+tFl3s6adtE3Oebghzc4DJ/Nw04WZgQczexJkz4lXvOKVxKuXV6APTCFXAq94xSte8ermFYbrA6+ilemZRxGz+fxBxMydL0/Vz5anvkUrPfb1IPCKV7ziFa9uXsG/DzyLPz7ndjS3tc3tSbcwPdl9tmYq3dHmk9x3r8mtiM11KfCKV7ziFa9uXmEc7wf+u6+5TtlXf62fKu9rl3wX9ibsLPCKV7ziFa9uXmF87wf67aBT6a+hp4bOehFxU0/CbgKveMUrXvHq5hXG+vuBcpss75zH/VZ5X7vcb4W7q5A/wvbCXoTNhX0JvOIVr3jFq5tX4P8Fw2V6g7UQ9itsLeKmfgi84hWveMWrm1egDwyX6Q3WTtinsI2wq7CjwCte8YpXvLp5BQ/utIiGbwh9RAEAAAAASUVORK5CYII="). + "iVBORw0KGgoAAAANSUhEUgAAAVcAAAA3CAMAAACPbPnEAAAAYFBMVEX///8C" + "AgJyMgL+vm7Wdg7+igL+/v7+slb+qkb+4sr+ojP+nir+lhr+1qb+khL+wnb+" + "wn7+zpb+jgb+yoz+xo7+tmL+pj7+mib+jg7+5sb+rlL+rkr+mh7+tl7+2q7+" + "umpJ0uikAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxIA" + "AAsSAdLdfvwAAAAHdElNRQfUBAUJBhWzc9qJAAABQ0lEQVR42u2bXU/CQBBF" + "UUZFURAU5Ev4//+S3Ow+tFl3s6adtE3Oebghzc4DJ/Nw04WZgQczexJkz4lX" + "vOKVxKuXV6APTCFXAq94xSte8ermFYbrA6+ilemZRxGz+fxBxMydL0/Vz5an" + "vkUrPfb1IPCKV7ziFa9uXsG/DzyLPz7ndjS3tc3tSbcwPdl9tmYq3dHmk9x3" + "r8mtiM11KfCKV7ziFa9uXmEc7wf+u6+5TtlXf62fKu9rl3wX9ibsLPCKV7zi" + "Fa9uXmF87wf67aBT6a+hp4bOehFxU0/CbgKveMUrXvHq5hXG+vuBcpss75zH" + "/VZ5X7vcb4W7q5A/wvbCXoTNhX0JvOIVr3jFq5tX4P8Fw2V6g7UQ9itsLeKm" + "fgi84hWveMWrm1egDwyX6Q3WTtinsI2wq7CjwCte8YpXvLp5BQ/utIiGbwh9" + "RAEAAAAASUVORK5CYII="). logo_fill() -> jlib:decode_base64( - "iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzAAAAIVBMVEX////Wdg7+igL+khL+jg7+nir+rkr+umr+yoz+1qb+5sYp3v/aAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxEAAAsRAX9kX5EAAAAHdElNRQfUBAYHDzOol2bZAAAASElEQVR42mMQFBRkUFJSxMAgcWNjQwwMEndxccTAIPHQ0EAMDBJPS0vEwCDx8vJCDAwS7+hoxMAg8ZkzJ2JgkPiqVQsxMFAcABvNNugXg2QkAAAAAElFTkSuQmCC"). - -empty() -> - jlib:decode_base64( - "R0lGODlhAQABAIAAAP///////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgABACwAAAAAAQABAAACAkwBADs="). + "iVBORw0KGgoAAAANSUhEUgAAAAYAAAA3BAMAAADdxCZzAAAAHlBMVEXWdg7+" + "igL+jg7+khL+nir+rkr+umr+yoz+1qb+5sbOf9L8AAAACXBIWXMAAA9hAAAP" + "YQGoP6dpAAAAQUlEQVQI12XDSxHAIBAFQT6BJEcsYAELWMACFtYCFnAL7zxd" + "1c5dvhSU2BpKqBXl6R0ljYGS50R5zVC+tVD+vfE6YyUexE9x7g4AAAAASUVO" + "RK5CYII="). process_admin(#request{us = US, path = [], @@ -508,19 +472,20 @@ process_admin(#request{us = US, lang = Lang} = Request) -> make_xhtml([?XCT("h1", "ejabberd administration"), ?XE("ul", - [?LI([?ACT("acls/", "Access Control Lists"), ?C(" "), - ?ACT("acls-raw/", "(raw)")]), - ?LI([?ACT("access/", "Access Rules"), ?C(" "), - ?ACT("access-raw/", "(raw)")]), - ?LI([?ACT("users/", "Users")]), - ?LI([?ACT("online-users/", "Online Users")]), - ?LI([?ACT("nodes/", "Nodes")]), - ?LI([?ACT("stats/", "Statistics")]) + [?LI([?ACT("/admin/acls/", "Access Control Lists"), ?C(" "), + ?ACT("/admin/acls-raw/", "(raw)")]), + ?LI([?ACT("/admin/access/", "Access Rules"), ?C(" "), + ?ACT("/admin/access-raw/", "(raw)")]), + ?LI([?ACT("/admin/users/", "Users")]), + ?LI([?ACT("/admin/online-users/", "Online Users")]), + ?LI([?ACT("/admin/last-activity/", "Last Activity")]), + ?LI([?ACT("/admin/nodes/", "Nodes")]), + ?LI([?ACT("/admin/stats/", "Statistics")]) ] ++ case lists:member(mod_shared_roster, gen_mod:loaded_modules()) of true -> - [?LI([?ACT("shared-roster/", "Shared Roster")])]; + [?LI([?ACT("/admin/shared-roster/", "Shared Roster")])]; false -> [] end @@ -545,12 +510,6 @@ process_admin(#request{us = US, lang = Lang} = Request) -> {200, [{"Content-Type", "image/png"}], logo_fill()}; -process_admin(#request{us = US, - path = ["1x1tr.gif"], - q = Query, - lang = Lang} = Request) -> - {200, [{"Content-Type", "image/gif"}], empty()}; - process_admin(#request{us = US, path = ["acls-raw"], q = Query, @@ -798,6 +757,46 @@ process_admin(#request{us = US, Res = list_online_users(Lang), make_xhtml([?XCT("h1", "ejabberd users")] ++ Res, Lang); +process_admin(#request{method = Method, + us = US, + path = ["last-activity"], + q = Query, + lang = Lang} = Request) -> + ?INFO_MSG("query: ~p", [Query]), + Month = case lists:keysearch("period", 1, Query) of + {value, {_, Val}} -> + Val; + _ -> + "month" + end, + Res = case lists:keysearch("ordinary", 1, Query) of + {value, {_, _}} -> + list_last_activity(Lang, false, Month); + _ -> + list_last_activity(Lang, true, Month) + end, + make_xhtml([?XCT("h1", "Users last activity")] ++ + [?XAE("form", [{"method", "post"}], + [?CT("Period: "), + ?XAE("select", [{"name", "period"}], + lists:map( + fun({O, V}) -> + Sel = if + O == Month -> [{"selected", "selected"}]; + true -> [] + end, + ?XAC("option", + Sel ++ [{"value", O}], V) + end, [{"month", ?T("Last month")}, + {"year", ?T("Last year")}, + {"all", ?T("All activity")}])), + ?C(" "), + ?INPUTT("submit", "ordinary", "Show Ordinary Table"), + ?C(" "), + ?INPUTT("submit", "integral", "Show Integral Table") + ])] ++ + Res, Lang); + process_admin(#request{us = US, path = ["stats"], q = Query, @@ -1557,6 +1556,74 @@ user_roster_item_parse_query(User, Server, Items, Query) -> nothing. +list_last_activity(_Lang, Integral, Period) -> + {MegaSecs, Secs, _MicroSecs} = now(), + TimeStamp = MegaSecs * 1000000 + Secs, + case Period of + "all" -> + TS = 0, + Days = infinity; + "year" -> + TS = TimeStamp - 366 * 86400, + Days = 366; + _ -> + TS = TimeStamp - 31 * 86400, + Days = 31 + end, + case catch mnesia:dirty_select( + last_activity, [{{last_activity, '_', '$1', '_'}, + [{'>', '$1', TS}], + [{'trunc', {'/', + {'-', TimeStamp, '$1'}, + 86400}}]}]) of + {'EXIT', _Reason} -> + []; + Vals -> + Hist = histogram(Vals, Integral), + Left = case Days of + infinity -> + 0; + _ -> + Days - length(Hist) + end, + Tail = case Integral andalso Hist /= [] of + true -> + lists:duplicate(Left, lists:last(Hist)); + _ -> + lists:duplicate(Left, 0) + end, + Max = lists:max(Hist), + [?XAE("ol", + [{"id", "lastactivity"}, {"start", "0"}], + [?XAE("li", + [{"style", "width:" ++ integer_to_list( + trunc(90 * V / Max)) ++ "%;"}], + [{xmlcdata, integer_to_list(V)}]) + || V <- Hist ++ Tail])] + end. + +histogram(Values, Integral) -> + histogram(lists:sort(Values), Integral, 0, 0, []). + +histogram([H | T], Integral, Current, Count, Hist) when Current == H -> + histogram(T, Integral, Current, Count + 1, Hist); + +histogram([H | _] = Values, Integral, Current, Count, Hist) when Current < H -> + if + Integral -> + histogram(Values, Integral, Current + 1, Count, [Count | Hist]); + true -> + histogram(Values, Integral, Current + 1, 0, [Count | Hist]) + end; + +histogram([], _Integral, _Current, Count, Hist) -> + if + Count > 0 -> + lists:reverse([Count | Hist]); + true -> + lists:reverse(Hist) + end. + get_nodes(Lang) -> RunningNodes = mnesia:system_info(running_db_nodes),