]> granicus.if.org Git - ejabberd/commitdiff
* src/web/ejabberd_web_admin.erl: Better design for administration
authorAlexey Shchepin <alexey@process-one.net>
Mon, 26 Apr 2004 15:38:07 +0000 (15:38 +0000)
committerAlexey Shchepin <alexey@process-one.net>
Mon, 26 Apr 2004 15:38:07 +0000 (15:38 +0000)
interface (not completed) (thanks to Andrey Zamaraev)
* src/web/ejabberd_http.erl: Updated
* src/web/ejabberd_web.erl: Likewise

SVN Revision: 225

ChangeLog
src/web/Makefile.in
src/web/ejabberd_http.erl
src/web/ejabberd_web.erl
src/web/ejabberd_web_admin.erl [new file with mode: 0644]

index 5120bda74786a392b89359ac6695a9a3a9bdcca5..7a2349e26780f5a7415a01785197fd0a5f05c9ce 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,10 @@
+2004-04-26  Alexey Shchepin  <alexey@sevcom.net>
+
+       * src/web/ejabberd_web_admin.erl: Better design for administration
+       interface (not completed) (thanks to Andrey Zamaraev)
+       * src/web/ejabberd_http.erl: Updated
+       * src/web/ejabberd_web.erl: Likewise
+
 2004-04-17  Alexey Shchepin  <alexey@sevcom.net>
 
        * src/web/ejabberd_http.erl: Increased receive buffer
index fd8ec5deb8226fab53ed97a78dcca329f2547d83..a19a4dc65bce8b875ebc0ef938dfae6d00cd92b6 100644 (file)
@@ -18,6 +18,7 @@ EFLAGS = -I .. -pz ..
 OBJS   = \
        $(OUTDIR)/ejabberd_http.beam \
        $(OUTDIR)/ejabberd_web.beam  \
+       $(OUTDIR)/ejabberd_web_admin.beam  \
        $(OUTDIR)/ejabberd_http_poll.beam
 
 all:    $(OBJS)
index 7569e11a98d88f94f7ac6e5822ab5d430ce16e9f..1192791ad388fae474a4e9e934663481fa355bed 100644 (file)
@@ -32,6 +32,9 @@
        "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" "
        "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n").
 
+-define(HTML_DOCTYPE,
+       "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\">").
+
 
 start(SockData, Opts) ->
     supervisor:start_child(ejabberd_http_sup, [SockData, Opts]).
@@ -54,7 +57,9 @@ send_text(State, Text) ->
 
 
 receive_headers(State) ->
-    Data = (State#state.sockmod):recv(State#state.socket, 0, 300000),
+    SockMod = State#state.sockmod,
+    Socket = State#state.socket,
+    Data = SockMod:recv(Socket, 0, 300000),
     ?DEBUG("recv: ~p~n", [Data]),
     case Data of
        {ok, {http_request, Method, Path, _Version}} ->
@@ -78,7 +83,13 @@ receive_headers(State) ->
                       element(2, State#state.request_path)]),
            Out = process_request(State),
            send_text(State, Out),
-           ok;
+           case SockMod of
+               gen_tcp ->
+                   inet:setopts(Socket, [{packet, http}]);
+               ssl ->
+                   ssl:setopts(Socket, [{packet, http}])
+           end,
+           receive_headers(#state{sockmod = SockMod, socket = Socket});
        {error, _Reason} ->
            ok;
        _ ->
@@ -216,7 +227,14 @@ recv_data(State, Len, Acc) ->
 
 
 make_xhtml_output(Status, Headers, XHTML) ->
-    Data = list_to_binary([?XHTML_DOCTYPE, xml:element_to_string(XHTML)]),
+    Data = case lists:member(html, Headers) of
+              true ->
+                  list_to_binary([?HTML_DOCTYPE,
+                                  xml:element_to_string(XHTML)]);
+              _ ->
+                  list_to_binary([?XHTML_DOCTYPE,
+                                  xml:element_to_string(XHTML)])
+          end,
     Headers1 = case lists:keysearch("Content-Type", 1, Headers) of
                   {value, _} ->
                       [{"Content-Length", integer_to_list(size(Data))} |
@@ -227,7 +245,9 @@ make_xhtml_output(Status, Headers, XHTML) ->
                        Headers]
               end,
     H = lists:map(fun({Attr, Val}) ->
-                         [Attr, ": ", Val, "\r\n"]
+                         [Attr, ": ", Val, "\r\n"];
+                    (_) ->
+                         []
                  end, Headers1),
     SL = ["HTTP/1.1 ", integer_to_list(Status), " ",
          code_to_phrase(Status), "\r\n"],
index ca89952da05779a018b88bba1027951b090bd3de..b59641c44afde0c86636ac88cb6a76b6db1f3c27 100644 (file)
@@ -59,7 +59,8 @@ process_get(#request{user = User,
                deny ->
                    {401, [], make_xhtml([?XC("h1", "Not Allowed")])};
                allow ->
-                   process_admin(Request#request{path = RPath})
+                   ejabberd_web_admin:process_admin(
+                     Request#request{path = RPath})
            end;
        true ->
            {401,
@@ -78,560 +79,3 @@ process_get(_Request) ->
     {404, [], make_xhtml([?XC("h1", "Not found")])}.
 
 
-
-process_admin(#request{user = User,
-                       path = [],
-                       q = Query,
-                       lang = Lang} = Request) ->
-    make_xhtml([?XC("h1", "ejabberd administration"),
-               ?XE("ul",
-                   [?LI([?AC("acls/", "Access Control Lists"), ?C(" "),
-                         ?AC("acls-raw/", "(raw)")]),
-                    ?LI([?AC("access/", "Access Rules"), ?C(" "),
-                         ?AC("access-raw/", "(raw)")]),
-                    ?LI([?AC("users/", "Users")]),
-                    ?LI([?AC("nodes/", "Nodes")]),
-                    ?LI([?AC("stats/", "Statistics")])
-                   ])
-              ]);
-
-process_admin(#request{user = User,
-                       path = ["acls-raw"],
-                       q = Query,
-                       lang = Lang} = Request) ->
-    Res = case lists:keysearch("acls", 1, Query) of
-             {value, {_, String}} ->
-                 case erl_scan:string(String) of
-                     {ok, Tokens, _} ->
-                         case erl_parse:parse_term(Tokens) of
-                             {ok, NewACLs} ->
-                                 case acl:add_list(NewACLs, true) of
-                                     ok ->
-                                         ok;
-                                     _ ->
-                                         error
-                                 end;
-                             _ ->
-                                 error
-                         end;
-                     _ ->
-                         error
-                 end;
-             _ ->
-                 nothing
-         end,
-    ACLs = lists:flatten(io_lib:format("~p.", [ets:tab2list(acl)])),
-    make_xhtml([?XC("h1", "ejabberd ACLs configuration")] ++
-              case Res of
-                  ok -> [?C("submited"), ?P];
-                  error -> [?C("bad format"), ?P];
-                  nothing -> []
-              end ++
-              [?XAE("form", [{"method", "post"}],
-                    [?XAC("textarea", [{"name", "acls"},
-                                       {"rows", "16"},
-                                       {"cols", "80"}],
-                          ACLs),
-                     ?BR,
-                     ?INPUT("submit", "", "")
-                    ])
-              ]);
-
-process_admin(#request{method = Method,
-                       user = User,
-                       path = ["acls"],
-                       q = Query,
-                       lang = Lang} = Request) ->
-    ?INFO_MSG("query: ~p", [Query]),
-    Res = case Method of
-             'POST' ->
-                 case catch acl_parse_query(Query) of
-                     {'EXIT', _} ->
-                         error;
-                     NewACLs ->
-                         ?INFO_MSG("NewACLs: ~p", [NewACLs]),
-                         case acl:add_list(NewACLs, true) of
-                             ok ->
-                                 ?INFO_MSG("NewACLs: ok", []),
-                                 ok;
-                             _ ->
-                                 error
-                         end
-                 end;
-             _ ->
-                 nothing
-         end,
-    ACLs = lists:keysort(2, ets:tab2list(acl)),
-    make_xhtml([?XC("h1", "ejabberd ACLs configuration")] ++
-              case Res of
-                  ok -> [?C("submited"), ?P];
-                  error -> [?C("bad format"), ?P];
-                  nothing -> []
-              end ++
-              [?XAE("form", [{"method", "post"}],
-                    [acls_to_xhtml(ACLs),
-                     ?BR,
-                     ?INPUT("submit", "delete", "Delete Selected"),
-                     ?C(" "),
-                     ?INPUT("submit", "submit", "Submit")
-                    ])
-              ]);
-
-process_admin(#request{user = User,
-                       path = ["access-raw"],
-                       q = Query,
-                       lang = Lang} = Request) ->
-    SetAccess =
-       fun(Rs) ->
-               mnesia:transaction(
-                 fun() ->
-                         Os = mnesia:select(config,
-                                            [{{config, {access, '$1'}, '$2'},
-                                              [],
-                                              ['$_']}]),
-                         lists:foreach(fun(O) ->
-                                               mnesia:delete_object(O)
-                                       end, Os),
-                         lists:foreach(
-                           fun({access, Name, Rules}) ->
-                                   mnesia:write({config,
-                                                 {access, Name},
-                                                 Rules})
-                           end, Rs)
-                 end)
-       end,
-    Res = case lists:keysearch("access", 1, Query) of
-             {value, {_, String}} ->
-                 case erl_scan:string(String) of
-                     {ok, Tokens, _} ->
-                         case erl_parse:parse_term(Tokens) of
-                             {ok, Rs} ->
-                                 case SetAccess(Rs) of
-                                     {atomic, _} ->
-                                         ok;
-                                     _ ->
-                                         error
-                                 end;
-                             _ ->
-                                 error
-                         end;
-                     _ ->
-                         error
-                 end;
-             _ ->
-                 nothing
-         end,
-    Access =
-       lists:flatten(
-         io_lib:format(
-           "~p.", [ets:select(config,
-                              [{{config, {access, '$1'}, '$2'},
-                                [],
-                                [{{access, '$1', '$2'}}]}])])),
-    make_xhtml([?XC("h1", "ejabberd access rules configuration")] ++
-              case Res of
-                  ok -> [?C("submited"), ?P];
-                  error -> [?C("bad format"), ?P];
-                  nothing -> []
-              end ++
-              [?XAE("form", [{"method", "post"}],
-                    [?XAC("textarea", [{"name", "access"},
-                                       {"rows", "16"},
-                                       {"cols", "80"}],
-                          Access),
-                     ?BR,
-                     ?INPUT("submit", "", "")
-                    ])
-              ]);
-
-process_admin(#request{method = Method,
-                      user = User,
-                      path = ["access"],
-                      q = Query,
-                      lang = Lang} = Request) ->
-    ?INFO_MSG("query: ~p", [Query]),
-    Res = case Method of
-             'POST' ->
-                 case catch access_parse_query(Query) of
-                     {'EXIT', _} ->
-                         error;
-                     ok ->
-                         ok
-                 end;
-             _ ->
-                 nothing
-         end,
-    AccessRules =
-       ets:select(config,
-                  [{{config, {access, '$1'}, '$2'},
-                    [],
-                    [{{access, '$1', '$2'}}]}]),
-    make_xhtml([?XC("h1", "ejabberd access rules configuration")] ++
-              case Res of
-                  ok -> [?C("submited"), ?P];
-                  error -> [?C("bad format"), ?P];
-                  nothing -> []
-              end ++
-              [?XAE("form", [{"method", "post"}],
-                    [access_rules_to_xhtml(AccessRules),
-                     ?BR,
-                     ?INPUT("submit", "delete", "Delete Selected")
-                    ])
-              ]);
-
-process_admin(#request{method = Method,
-                      user = User,
-                      path = ["access", SName],
-                      q = Query,
-                      lang = Lang} = Request) ->
-    ?INFO_MSG("query: ~p", [Query]),
-    Name = list_to_atom(SName),
-    Res = case lists:keysearch("rules", 1, Query) of
-             {value, {_, String}} ->
-                 case parse_access_rule(String) of
-                     {ok, Rs} ->
-                         ejabberd_config:add_global_option(
-                           {access, Name}, Rs),
-                         ok;
-                     _ ->
-                         error
-                 end;
-             _ ->
-                 nothing
-         end,
-    Rules = case ejabberd_config:get_global_option({access, Name}) of
-               undefined ->
-                   [];
-               Rs1 ->
-                   Rs1
-           end,
-    make_xhtml([?XC("h1",
-                   "ejabberd access rule '" ++ SName ++ "' configuration")] ++
-              case Res of
-                  ok -> [?C("submited"), ?P];
-                  error -> [?C("bad format"), ?P];
-                  nothing -> []
-              end ++
-              [?XAE("form", [{"method", "post"}],
-                    [access_rule_to_xhtml(Rules),
-                     ?BR,
-                     ?INPUT("submit", "submit", "")
-                    ])
-              ]);
-
-process_admin(#request{user = User,
-                       path = ["users"],
-                       q = Query,
-                       lang = Lang} = Request) ->
-    Res = list_users(),
-    make_xhtml([?XC("h1", "ejabberd users")] ++ Res);
-
-process_admin(#request{user = User,
-                       path = ["users", Diap],
-                       q = Query,
-                       lang = Lang} = Request) ->
-    Res = list_users_in_diapason(Diap),
-    make_xhtml([?XC("h1", "ejabberd users")] ++ Res);
-
-process_admin(#request{user = User,
-                       path = ["stats"],
-                       q = Query,
-                       lang = Lang} = Request) ->
-    Res = get_stats(),
-    make_xhtml([?XC("h1", "ejabberd stats")] ++ Res);
-
-process_admin(_Request) ->
-    {404, [], make_xhtml([?XC("h1", "Not found")])}.
-
-
-
-acls_to_xhtml(ACLs) ->
-    ?XAE("table", [],
-        [?XE("tbody",
-             lists:map(
-               fun({acl, Name, Spec} = ACL) ->
-                       SName = atom_to_list(Name),
-                       ID = term_to_id(ACL),
-                       ?XE("tr",
-                           [?XE("td", [?INPUT("checkbox", "selected", ID)]),
-                            ?XC("td", SName)] ++
-                           acl_spec_to_xhtml(ID, Spec)
-                          )
-               end, ACLs) ++
-             [?XE("tr",
-                  [?X("td"),
-                   ?XE("td", [?INPUT("text", "namenew", "")])
-                  ] ++
-                  acl_spec_to_xhtml("new", {user, ""})
-                 )]
-            )]).
-
--define(ACLINPUT(Text), ?XE("td", [?INPUT("text", "value" ++ ID, Text)])).
-
-acl_spec_to_text({user, U}) ->
-    {user, U};
-
-acl_spec_to_text({server, S}) ->
-    {server, S};
-
-acl_spec_to_text({user, U, S}) ->
-    {user, U ++ "@" ++ S};
-
-acl_spec_to_text(Spec) ->
-    {raw, term_to_string(Spec)}.
-
-acl_spec_to_xhtml(ID, Spec) ->
-    {Type, Str} = acl_spec_to_text(Spec),
-    [acl_spec_select(ID, Type), ?ACLINPUT(Str)].
-
-acl_spec_select(ID, Opt) ->
-    ?XE("td",
-       [?XAE("select", [{"name", "type" ++ ID}],
-             lists:map(
-               fun(O) ->
-                       Sel = if
-                                 O == Opt -> [{"selected", "selected"}];
-                                 true -> []
-                             end,
-                       ?XAC("option",
-                            Sel ++ [{"value", atom_to_list(O)}],
-                            atom_to_list(O))
-               end, [user, server, user_server, raw]))]).
-
-
-term_to_string(T) ->
-    lists:flatten(io_lib:format("~1000000p", [T])).
-
-term_to_id(T) ->
-    jlib:encode_base64(binary_to_list(term_to_binary(T))).
-
-
-acl_parse_query(Query) ->
-    ACLs = ets:tab2list(acl),
-    case lists:keysearch("submit", 1, Query) of
-       {value, _} ->
-           acl_parse_submit(ACLs, Query);
-       _ ->
-           case lists:keysearch("delete", 1, Query) of
-               {value, _} ->
-                   acl_parse_delete(ACLs, Query)
-           end
-    end.
-
-acl_parse_submit(ACLs, Query) ->
-    NewACLs =
-       lists:map(
-         fun({acl, Name, Spec} = ACL) ->
-                 SName = atom_to_list(Name),
-                 ID = term_to_id(ACL),
-                 case {lists:keysearch("type" ++ ID, 1, Query),
-                       lists:keysearch("value" ++ ID, 1, Query)} of
-                     {{value, {_, T}}, {value, {_, V}}} ->
-                         {Type, Str} = acl_spec_to_text(Spec),
-                         case {atom_to_list(Type), Str} of
-                             {T, V} ->
-                                 ACL;
-                             _ ->
-                                 NewSpec = string_to_spec(T, V),
-                                 {acl, Name, NewSpec}
-                         end;
-                     _ ->
-                         ACL
-                 end
-         end, ACLs),
-    NewACL = case {lists:keysearch("namenew", 1, Query),
-                  lists:keysearch("typenew", 1, Query),
-                  lists:keysearch("valuenew", 1, Query)} of
-                {{value, {_, ""}}, _, _} ->
-                    [];
-                {{value, {_, N}}, {value, {_, T}}, {value, {_, V}}} ->
-                    NewName = list_to_atom(N),
-                    NewSpec = string_to_spec(T, V),
-                    [{acl, NewName, NewSpec}];
-                _ ->
-                    []
-            end,
-    NewACLs ++ NewACL.
-
-string_to_spec("user", Val) ->
-    {user, Val};
-string_to_spec("server", Val) ->
-    {server, Val};
-string_to_spec("user_server", Val) ->
-    #jid{luser = U, lserver = S, resource = ""} = jlib:string_to_jid(Val),
-    {user_server, U, S};
-string_to_spec("raw", Val) ->
-    {ok, Tokens, _} = erl_scan:string(Val ++ "."),
-    {ok, NewSpec} = erl_parse:parse_term(Tokens),
-    NewSpec.
-
-
-acl_parse_delete(ACLs, Query) ->
-    NewACLs =
-       lists:filter(
-         fun({acl, Name, Spec} = ACL) ->
-                 ID = term_to_id(ACL),
-                 not lists:member({"selected", ID}, Query)
-         end, ACLs),
-    NewACLs.
-
-
-access_rules_to_xhtml(AccessRules) ->
-    ?XAE("table", [],
-        [?XE("tbody",
-             lists:map(
-               fun({access, Name, Rules} = Access) ->
-                       SName = atom_to_list(Name),
-                       ID = term_to_id(Access),
-                       ?XE("tr",
-                           [?XE("td", [?INPUT("checkbox", "selected", ID)]),
-                            ?XE("td", [?AC(SName ++ "/", SName)]),
-                            ?XC("td", term_to_string(Rules))
-                           ]
-                          )
-               end, AccessRules) ++
-             [?XE("tr",
-                  [?X("td"),
-                   ?XE("td", [?INPUT("text", "namenew", "")]),
-                   ?XE("td", [?INPUT("submit", "addnew", "Add New")])
-                  ]
-                 )]
-            )]).
-
-access_parse_query(Query) ->
-    AccessRules =
-       ets:select(config,
-                  [{{config, {access, '$1'}, '$2'},
-                    [],
-                    [{{access, '$1', '$2'}}]}]),
-    case lists:keysearch("addnew", 1, Query) of
-       {value, _} ->
-           access_parse_addnew(AccessRules, Query);
-       _ ->
-           case lists:keysearch("delete", 1, Query) of
-               {value, _} ->
-                   access_parse_delete(AccessRules, Query)
-           end
-    end.
-
-access_parse_addnew(AccessRules, Query) ->
-    case lists:keysearch("namenew", 1, Query) of
-       {value, {_, String}} when String /= "" ->
-           Name = list_to_atom(String),
-           ejabberd_config:add_global_option({access, Name}, []),
-           ok
-    end.
-
-access_parse_delete(AccessRules, Query) ->
-    lists:foreach(
-      fun({access, Name, _Rules} = AccessRule) ->
-             ID = term_to_id(AccessRule),
-             case lists:member({"selected", ID}, Query) of
-                 true ->
-                     mnesia:transaction(
-                       fun() ->
-                               mnesia:delete({config, {access, Name}})
-                       end);
-                 _ ->
-                     ok
-             end
-      end, AccessRules),
-    ok.
-
-
-
-
-access_rule_to_xhtml(Rules) ->
-    Text = lists:flatmap(
-            fun({Access, ACL} = Rule) ->
-                    SAccess = atom_to_list(Access),
-                    SACL = atom_to_list(ACL),
-                    SAccess ++ "\t" ++ SACL ++ "\n"
-            end, Rules),
-    ?XAC("textarea", [{"name", "rules"},
-                     {"rows", "16"},
-                     {"cols", "80"}],
-        Text).
-
-parse_access_rule(Text) ->
-    Strings = string:tokens(Text, "\r\n"),
-    case catch lists:flatmap(
-                fun(String) ->
-                        case string:tokens(String, "\s\t") of
-                            [Access, ACL] ->
-                                [{list_to_atom(Access), list_to_atom(ACL)}];
-                            [] ->
-                                []
-                        end
-                end, Strings) of
-       {'EXIT', _Reason} ->
-           error;
-       Rs ->
-           {ok, Rs}
-    end.
-
-
-
-
-list_users() ->
-    Users = ejabberd_auth:dirty_get_registered_users(),
-    SUsers = lists:sort(Users),
-    case length(SUsers) of
-       N when N =< 100 ->
-           lists:flatmap(
-             fun(U) ->
-                     [?AC("../user/" ++ U ++ "/", U), ?BR]
-             end, SUsers);
-       N ->
-           NParts = trunc(math:sqrt(N * 0.618)) + 1,
-           M = trunc(N / NParts) + 1,
-           lists:flatmap(
-             fun(K) ->
-                     L = K + M - 1,
-                     Node = integer_to_list(K) ++ "-" ++ integer_to_list(L),
-                     Last = if L < N -> lists:nth(L, SUsers);
-                               true -> lists:last(SUsers)
-                            end,
-                     Name = 
-                         lists:nth(K, SUsers) ++ [$\s, 226, 128, 148, $\s] ++
-                         Last,
-                     [?AC(Node ++ "/", Name), ?BR]
-             end, lists:seq(1, N, M))
-    end.
-
-list_users_in_diapason(Diap) ->
-    Users = ejabberd_auth:dirty_get_registered_users(),
-    SUsers = lists:sort(Users),
-    {ok, [S1, S2]} = regexp:split(Diap, "-"),
-    N1 = list_to_integer(S1),
-    N2 = list_to_integer(S2),
-    Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
-    lists:flatmap(
-      fun(U) ->
-             [?AC("../../user/" ++ U ++ "/", U), ?BR]
-      end, Sub).
-
-
-
-get_stats() ->
-    OnlineUsers = mnesia:table_info(presence, size),
-    AuthUsers = mnesia:table_info(session, size),
-    RegisteredUsers = mnesia:table_info(passwd, size),
-    S2SConns = ejabberd_s2s:dirty_get_connections(),
-    S2SConnections = length(S2SConns),
-    S2SServers = length(lists:usort([element(2, C) || C <- S2SConns])),
-    
-    [?XAE("table", [],
-         [?XE("tbody",
-              [?XE("tr", [?XC("td", "Registered users"),
-                          ?XC("td", integer_to_list(RegisteredUsers))]),
-               ?XE("tr", [?XC("td", "Authentificated users"),
-                          ?XC("td", integer_to_list(AuthUsers))]),
-               ?XE("tr", [?XC("td", "Online users"),
-                          ?XC("td", integer_to_list(OnlineUsers))]),
-               ?XE("tr", [?XC("td", "Outgoing S2S connections"),
-                          ?XC("td", integer_to_list(S2SConnections))]),
-               ?XE("tr", [?XC("td", "Outgoing S2S servers"),
-                          ?XC("td", integer_to_list(S2SServers))])
-              ])
-         ])].
diff --git a/src/web/ejabberd_web_admin.erl b/src/web/ejabberd_web_admin.erl
new file mode 100644 (file)
index 0000000..6a12d29
--- /dev/null
@@ -0,0 +1,977 @@
+%%%----------------------------------------------------------------------
+%%% File    : ejabberd_web_admin.erl
+%%% Author  : Alexey Shchepin <alexey@sevcom.net>
+%%% Purpose : 
+%%% Created :  9 Apr 2004 by Alexey Shchepin <alexey@sevcom.net>
+%%% Id      : $Id$
+%%%----------------------------------------------------------------------
+
+-module(ejabberd_web_admin).
+-author('alexey@sevcom.net').
+-vsn('$Revision$ ').
+
+%% External exports
+-export([process_admin/1]).
+
+-include("ejabberd.hrl").
+-include("jlib.hrl").
+-include("ejabberd_http.hrl").
+
+-define(X(Name), {xmlelement, Name, [], []}).
+-define(XA(Name, Attrs), {xmlelement, Name, Attrs, []}).
+-define(XE(Name, Els), {xmlelement, Name, [], Els}).
+-define(XAE(Name, Attrs, Els), {xmlelement, Name, Attrs, Els}).
+-define(C(Text), {xmlcdata, Text}).
+-define(XC(Name, Text), ?XE(Name, [?C(Text)])).
+-define(XAC(Name, Attrs, Text), ?XAE(Name, Attrs, [?C(Text)])).
+
+-define(LI(Els), ?XE("li", Els)).
+-define(A(URL, Els), ?XAE("a", [{"href", URL}], Els)).
+-define(AC(URL, Text), ?A(URL, [?C(Text)])).
+-define(P, ?X("p")).
+-define(BR, ?X("br")).
+-define(INPUT(Type, Name, Value),
+       ?XA("input", [{"type", Type},
+                     {"name", Name},
+                     {"value", Value}])).
+
+make_xhtml(Els) ->
+    {200, [html],
+     {xmlelement, "html", [{"xmlns", "http://www.w3.org/1999/xhtml"},
+                          {"xml:lang", "en"},
+                          {"lang", "en"}],
+      [{xmlelement, "head", [],
+       [{xmlelement, "meta", [{"http-equiv", "Content-Type"},
+                              {"content", "text/html; charset=utf-8"}], []},
+        {xmlelement, "link", [{"href", "/admin/style.css"},
+                              {"type", "text/css"},
+                              {"rel", "StyleSheet"}], []}]},
+       {xmlelement, "body",
+       [{"topmargin", "0"},
+        {"marginheight", "0"},
+        {"leftmargin", "0"},
+        {"marginwidth", "0"},
+        {"rightmargin", "0"}],
+       [?XAE("table",
+             [{"cellpadding", "0"},
+              {"cellspacing", "0"},
+              {"border", "0"},
+              {"bgcolor", "#fe8a00"},
+              {"width", "100%"}],
+             [?XE("tr",
+                  [?XAE("td", [{"height", "2"}],
+                        [?XA("img", [{"src", "/admin/1x1tr.gif"},
+                                     {"width", "1"},
+                                     {"height", "2"},
+                                     {"alt", ""},
+                                     {"border", "0"}])]),
+                   ?XAE("td", [{"height", "2"}],
+                        [?XA("img", [{"src", "/admin/1x1tr.gif"},
+                                     {"width", "1"},
+                                     {"height", "2"},
+                                     {"alt", ""},
+                                     {"border", "0"}])])]),
+              ?XE("tr",
+                  [?XE("td",
+                       [?XA("img", [{"src", "/admin/logo.png"},
+                                    {"width", "343"},
+                                    {"height", "55"},
+                                    {"alt", "ejabberd"},
+                                    {"border", "0"}])]),
+                   ?XAE("td", [{"width", "100%"},
+                               {"background", "/admin/logo-fill.png"}],
+                        [?XA("img", [{"src", "/admin/1x1tr.gif"},
+                                     {"width", "100%"},
+                                     {"height", "55"},
+                                     {"alt", ""},
+                                     {"border", "0"}])])])
+             ]),
+        ?XAE("table",
+             [{"cellpadding", "0"},
+              {"cellspacing", "0"},
+              {"border", "0"},
+              {"width", "100%"},
+              {"height", "100%"}],
+             [?XE("tr",
+                  [?XAE("td",
+                        [{"width", "1"},
+                         {"bgcolor", "#d47911"}],
+                        [?C(" ")]),
+                   ?XAE("td",
+                        [{"height", "100%"},
+                                               %{"width", "100%"},
+                         {"bgcolor", "#ffffff"},
+                         {"valign", "top"}],
+                        [?XAE("ul",
+                              [{"id", "navlist"}],
+                              [?LI([?AC("/admin/acls/", "Access Control Lists")]),
+                               ?LI([?AC("/admin/access/", "Access Rules")]),
+                               ?LI([?AC("/admin/users/", "Users")]),
+                               ?LI([?AC("/admin/nodes/", "Nodes")]),
+                               ?LI([?AC("/admin/stats/", "Statistics")])
+                              ])]),
+                   ?XAE("td",
+                        [{"height", "100%"},
+                         {"width", "100%"},
+                         {"bgcolor", "#ffffff"},
+                         {"valign", "top"}],
+                        [?XAE("span", [{"id", "content"}], Els)])])
+             ]),
+        ?XAE("table",
+             [{"cellpadding", "0"},
+              {"cellspacing", "0"},
+              {"border", "0"},
+              {"width", "100%"}],
+             [?XE("tr",
+                  [?XA("td",
+                       [{"height", "1"},
+                        {"bgcolor", "#d47911"}])
+                  ])
+             ])]}
+      ]}}.
+
+css() -> "
+    /*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;
+    }
+
+    #navlist
+    {
+    padding: 0 1px 1px;
+    margin-left: 0;
+    font: bold 10px Verdana, sans-serif;
+    background: #d47911;
+    width: 13em;
+    }
+
+    #navlist li
+    {
+    list-style: none;
+    margin: 0;
+    text-align: left;
+    display: inline;
+    }
+
+    #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;
+    }
+
+    #navlist li a:link { color: #844; }
+    #navlist li a:visited { color: #766; }
+
+    #navlist li a:hover
+    {
+    border-color: #fc8800;
+    color: #FFF;
+    background: #332;
+    }
+
+input {
+    border: 1px solid #93a6c7;  
+    color: #556655;
+    background-color: #ffffff;
+    vertical-align: middle;
+    margin-bottom: 0px;
+    padding: 0.1em;
+}
+
+input.button {
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 7pt;
+  font-weight: bold;
+}
+
+textarea {
+    border: 1px solid #93a6c7;  
+    color: #556655;
+    background-color: #ffffff;
+    vertical-align: middle;
+    margin-top: 7px;
+    margin-left: 7px;
+    margin-right: 7px;
+    margin-bottom: 5px;
+    padding: 0.1em;
+}
+
+select {
+    border: 1px solid #93a6c7;  
+    color: #556655;
+    background-color: #ffffff;
+    vertical-align: middle;
+    margin-bottom: 0px; 
+    padding: 0.1em;
+}
+
+
+tr.head{
+  color: #ffffff;
+  background-color: #3b547a;
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 9pt;
+  font-weight: bold;
+  text-align: center;
+}
+
+tr.oddraw{
+  color: #412c75;
+  background-color: #ccd4df;
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 9pt;
+  font-weight: normal;
+  text-align: center;
+}
+
+tr.evenraw{
+  color: #412c75;
+  background-color: #dbe0e8;
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 9pt;
+  font-weight: normal;
+  text-align: center;
+}
+
+td.leftheader{
+  color: #412c75;
+  background-color: #ccccc1;
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 9pt;
+  font-weight: bold;
+  padding-left: 5px;
+  padding-top: 2px;
+  padding-bottom: 2px;
+  margin-top: 0px;
+  margin-bottom: 0px;
+}
+
+td.leftcontent{
+  color: #000044;
+  background-color: #e6e6df;
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 7pt;
+  font-weight: normal;
+  padding-left: 5px;
+  padding-right: 5px;
+  padding-top: 2px;
+  padding-bottom: 2px;
+  margin-top: 0px;
+  margin-bottom: 0px;
+}
+
+td.rightcontent{
+  color: #000044;
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 10pt;
+  font-weight: normal;
+  text-align: justify;
+  padding-left: 10px;
+  padding-right: 10px;
+  padding-bottom: 5px;
+}
+
+
+h1{
+  color: #000044;
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 14pt;
+  font-weight: bold;
+  text-align: center;
+  padding-left: 5px;
+  padding-top: 2px;
+  padding-bottom: 2px;
+  margin-top: 0px;
+  margin-bottom: 0px;
+}
+
+h2{
+  color: #000044;
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 12pt;
+  font-weight: bold;
+  text-align: center;
+  padding-left: 5px;
+  padding-top: 2px;
+  padding-bottom: 2px;
+  margin-top: 0px;
+  margin-bottom: 0px;
+}
+
+h3{
+  color: #000044;
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 10pt;
+  font-weight: bold;
+  text-align: left;
+  padding-left: 5px;
+  padding-top: 20px;
+  padding-bottom: 2px;
+  margin-top: 0px;
+  margin-bottom: 0px;
+}
+
+*#content a:link {
+  color: #444466; 
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 10pt;
+  font-weight: bold;
+  text-decoration: underlined;
+}
+*#content a:visited {
+  color: #444466;  
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 10pt;
+  font-weight: bold;
+  text-decoration: underlined;
+}
+*#content a:hover {
+  color: #222266;  
+  font-family: Verdana, Arial, Helvetica, sans-serif; 
+  font-size: 10pt;
+  font-weight: bold;
+  text-decoration: underlined;
+}
+
+
+*#content li{
+  list-style-type: dot;
+  font-size: 7pt;
+  padding-left: 10px;
+}
+
+*#content li.big{
+  font-size: 10pt;
+}
+
+
+
+".
+
+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=").
+
+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=").
+
+process_admin(#request{user = User,
+                       path = [],
+                       q = Query,
+                       lang = Lang} = Request) ->
+    make_xhtml([?XC("h1", "ejabberd administration"),
+               ?XE("ul",
+                   [?LI([?AC("acls/", "Access Control Lists"), ?C(" "),
+                         ?AC("acls-raw/", "(raw)")]),
+                    ?LI([?AC("access/", "Access Rules"), ?C(" "),
+                         ?AC("access-raw/", "(raw)")]),
+                    ?LI([?AC("users/", "Users")]),
+                    ?LI([?AC("nodes/", "Nodes")]),
+                    ?LI([?AC("stats/", "Statistics")])
+                   ])
+              ]);
+
+process_admin(#request{user = User,
+                      path = ["style.css"],
+                      q = Query,
+                      lang = Lang} = Request) ->
+    {200, [{"Content-Type", "text/css"}], css()};
+
+process_admin(#request{user = User,
+                      path = ["logo.png"],
+                      q = Query,
+                      lang = Lang} = Request) ->
+    {200, [{"Content-Type", "image/png"}], logo()};
+
+process_admin(#request{user = User,
+                      path = ["logo-fill.png"],
+                      q = Query,
+                      lang = Lang} = Request) ->
+    {200, [{"Content-Type", "image/png"}], logo_fill()};
+
+process_admin(#request{user = User,
+                      path = ["1x1tr.gif"],
+                      q = Query,
+                      lang = Lang} = Request) ->
+    {200, [{"Content-Type", "image/gif"}], empty()};
+
+process_admin(#request{user = User,
+                      path = ["acls-raw"],
+                      q = Query,
+                      lang = Lang} = Request) ->
+    Res = case lists:keysearch("acls", 1, Query) of
+             {value, {_, String}} ->
+                 case erl_scan:string(String) of
+                     {ok, Tokens, _} ->
+                         case erl_parse:parse_term(Tokens) of
+                             {ok, NewACLs} ->
+                                 case acl:add_list(NewACLs, true) of
+                                     ok ->
+                                         ok;
+                                     _ ->
+                                         error
+                                 end;
+                             _ ->
+                                 error
+                         end;
+                     _ ->
+                         error
+                 end;
+             _ ->
+                 nothing
+         end,
+    ACLs = lists:flatten(io_lib:format("~p.", [ets:tab2list(acl)])),
+    make_xhtml([?XC("h1", "ejabberd ACLs configuration")] ++
+              case Res of
+                  ok -> [?C("submited"), ?P];
+                  error -> [?C("bad format"), ?P];
+                  nothing -> []
+              end ++
+              [?XAE("form", [{"method", "post"}],
+                    [?XAC("textarea", [{"name", "acls"},
+                                       {"rows", "16"},
+                                       {"cols", "80"}],
+                          ACLs),
+                     ?BR,
+                     ?INPUT("submit", "", "")
+                    ])
+              ]);
+
+process_admin(#request{method = Method,
+                       user = User,
+                       path = ["acls"],
+                       q = Query,
+                       lang = Lang} = Request) ->
+    ?INFO_MSG("query: ~p", [Query]),
+    Res = case Method of
+             'POST' ->
+                 case catch acl_parse_query(Query) of
+                     {'EXIT', _} ->
+                         error;
+                     NewACLs ->
+                         ?INFO_MSG("NewACLs: ~p", [NewACLs]),
+                         case acl:add_list(NewACLs, true) of
+                             ok ->
+                                 ?INFO_MSG("NewACLs: ok", []),
+                                 ok;
+                             _ ->
+                                 error
+                         end
+                 end;
+             _ ->
+                 nothing
+         end,
+    ACLs = lists:keysort(2, ets:tab2list(acl)),
+    make_xhtml([?XC("h1", "ejabberd ACLs configuration")] ++
+              case Res of
+                  ok -> [?C("submited"), ?P];
+                  error -> [?C("bad format"), ?P];
+                  nothing -> []
+              end ++
+              [?XAE("form", [{"method", "post"}],
+                    [acls_to_xhtml(ACLs),
+                     ?BR,
+                     ?INPUT("submit", "delete", "Delete Selected"),
+                     ?C(" "),
+                     ?INPUT("submit", "submit", "Submit")
+                    ])
+              ]);
+
+process_admin(#request{user = User,
+                       path = ["access-raw"],
+                       q = Query,
+                       lang = Lang} = Request) ->
+    SetAccess =
+       fun(Rs) ->
+               mnesia:transaction(
+                 fun() ->
+                         Os = mnesia:select(config,
+                                            [{{config, {access, '$1'}, '$2'},
+                                              [],
+                                              ['$_']}]),
+                         lists:foreach(fun(O) ->
+                                               mnesia:delete_object(O)
+                                       end, Os),
+                         lists:foreach(
+                           fun({access, Name, Rules}) ->
+                                   mnesia:write({config,
+                                                 {access, Name},
+                                                 Rules})
+                           end, Rs)
+                 end)
+       end,
+    Res = case lists:keysearch("access", 1, Query) of
+             {value, {_, String}} ->
+                 case erl_scan:string(String) of
+                     {ok, Tokens, _} ->
+                         case erl_parse:parse_term(Tokens) of
+                             {ok, Rs} ->
+                                 case SetAccess(Rs) of
+                                     {atomic, _} ->
+                                         ok;
+                                     _ ->
+                                         error
+                                 end;
+                             _ ->
+                                 error
+                         end;
+                     _ ->
+                         error
+                 end;
+             _ ->
+                 nothing
+         end,
+    Access =
+       lists:flatten(
+         io_lib:format(
+           "~p.", [ets:select(config,
+                              [{{config, {access, '$1'}, '$2'},
+                                [],
+                                [{{access, '$1', '$2'}}]}])])),
+    make_xhtml([?XC("h1", "ejabberd access rules configuration")] ++
+              case Res of
+                  ok -> [?C("submited"), ?P];
+                  error -> [?C("bad format"), ?P];
+                  nothing -> []
+              end ++
+              [?XAE("form", [{"method", "post"}],
+                    [?XAC("textarea", [{"name", "access"},
+                                       {"rows", "16"},
+                                       {"cols", "80"}],
+                          Access),
+                     ?BR,
+                     ?INPUT("submit", "", "")
+                    ])
+              ]);
+
+process_admin(#request{method = Method,
+                      user = User,
+                      path = ["access"],
+                      q = Query,
+                      lang = Lang} = Request) ->
+    ?INFO_MSG("query: ~p", [Query]),
+    Res = case Method of
+             'POST' ->
+                 case catch access_parse_query(Query) of
+                     {'EXIT', _} ->
+                         error;
+                     ok ->
+                         ok
+                 end;
+             _ ->
+                 nothing
+         end,
+    AccessRules =
+       ets:select(config,
+                  [{{config, {access, '$1'}, '$2'},
+                    [],
+                    [{{access, '$1', '$2'}}]}]),
+    make_xhtml([?XC("h1", "ejabberd access rules configuration")] ++
+              case Res of
+                  ok -> [?C("submited"), ?P];
+                  error -> [?C("bad format"), ?P];
+                  nothing -> []
+              end ++
+              [?XAE("form", [{"method", "post"}],
+                    [access_rules_to_xhtml(AccessRules),
+                     ?BR,
+                     ?INPUT("submit", "delete", "Delete Selected")
+                    ])
+              ]);
+
+process_admin(#request{method = Method,
+                      user = User,
+                      path = ["access", SName],
+                      q = Query,
+                      lang = Lang} = Request) ->
+    ?INFO_MSG("query: ~p", [Query]),
+    Name = list_to_atom(SName),
+    Res = case lists:keysearch("rules", 1, Query) of
+             {value, {_, String}} ->
+                 case parse_access_rule(String) of
+                     {ok, Rs} ->
+                         ejabberd_config:add_global_option(
+                           {access, Name}, Rs),
+                         ok;
+                     _ ->
+                         error
+                 end;
+             _ ->
+                 nothing
+         end,
+    Rules = case ejabberd_config:get_global_option({access, Name}) of
+               undefined ->
+                   [];
+               Rs1 ->
+                   Rs1
+           end,
+    make_xhtml([?XC("h1",
+                   "ejabberd access rule '" ++ SName ++ "' configuration")] ++
+              case Res of
+                  ok -> [?C("submited"), ?P];
+                  error -> [?C("bad format"), ?P];
+                  nothing -> []
+              end ++
+              [?XAE("form", [{"method", "post"}],
+                    [access_rule_to_xhtml(Rules),
+                     ?BR,
+                     ?INPUT("submit", "submit", "")
+                    ])
+              ]);
+
+process_admin(#request{user = User,
+                       path = ["users"],
+                       q = Query,
+                       lang = Lang} = Request) ->
+    Res = list_users(),
+    make_xhtml([?XC("h1", "ejabberd users")] ++ Res);
+
+process_admin(#request{user = User,
+                       path = ["users", Diap],
+                       q = Query,
+                       lang = Lang} = Request) ->
+    Res = list_users_in_diapason(Diap),
+    make_xhtml([?XC("h1", "ejabberd users")] ++ Res);
+
+process_admin(#request{user = User,
+                       path = ["stats"],
+                       q = Query,
+                       lang = Lang} = Request) ->
+    Res = get_stats(),
+    make_xhtml([?XC("h1", "ejabberd stats")] ++ Res);
+
+process_admin(_Request) ->
+    setelement(1, make_xhtml([?XC("h1", "Not found")]), 404).
+
+
+
+acls_to_xhtml(ACLs) ->
+    ?XAE("table", [],
+        [?XE("tbody",
+             lists:map(
+               fun({acl, Name, Spec} = ACL) ->
+                       SName = atom_to_list(Name),
+                       ID = term_to_id(ACL),
+                       ?XE("tr",
+                           [?XE("td", [?INPUT("checkbox", "selected", ID)]),
+                            ?XC("td", SName)] ++
+                           acl_spec_to_xhtml(ID, Spec)
+                          )
+               end, ACLs) ++
+             [?XE("tr",
+                  [?X("td"),
+                   ?XE("td", [?INPUT("text", "namenew", "")])
+                  ] ++
+                  acl_spec_to_xhtml("new", {user, ""})
+                 )]
+            )]).
+
+-define(ACLINPUT(Text), ?XE("td", [?INPUT("text", "value" ++ ID, Text)])).
+
+acl_spec_to_text({user, U}) ->
+    {user, U};
+
+acl_spec_to_text({server, S}) ->
+    {server, S};
+
+acl_spec_to_text({user, U, S}) ->
+    {user, U ++ "@" ++ S};
+
+acl_spec_to_text(Spec) ->
+    {raw, term_to_string(Spec)}.
+
+acl_spec_to_xhtml(ID, Spec) ->
+    {Type, Str} = acl_spec_to_text(Spec),
+    [acl_spec_select(ID, Type), ?ACLINPUT(Str)].
+
+acl_spec_select(ID, Opt) ->
+    ?XE("td",
+       [?XAE("select", [{"name", "type" ++ ID}],
+             lists:map(
+               fun(O) ->
+                       Sel = if
+                                 O == Opt -> [{"selected", "selected"}];
+                                 true -> []
+                             end,
+                       ?XAC("option",
+                            Sel ++ [{"value", atom_to_list(O)}],
+                            atom_to_list(O))
+               end, [user, server, user_server, raw]))]).
+
+
+term_to_string(T) ->
+    lists:flatten(io_lib:format("~1000000p", [T])).
+
+term_to_id(T) ->
+    jlib:encode_base64(binary_to_list(term_to_binary(T))).
+
+
+acl_parse_query(Query) ->
+    ACLs = ets:tab2list(acl),
+    case lists:keysearch("submit", 1, Query) of
+       {value, _} ->
+           acl_parse_submit(ACLs, Query);
+       _ ->
+           case lists:keysearch("delete", 1, Query) of
+               {value, _} ->
+                   acl_parse_delete(ACLs, Query)
+           end
+    end.
+
+acl_parse_submit(ACLs, Query) ->
+    NewACLs =
+       lists:map(
+         fun({acl, Name, Spec} = ACL) ->
+                 SName = atom_to_list(Name),
+                 ID = term_to_id(ACL),
+                 case {lists:keysearch("type" ++ ID, 1, Query),
+                       lists:keysearch("value" ++ ID, 1, Query)} of
+                     {{value, {_, T}}, {value, {_, V}}} ->
+                         {Type, Str} = acl_spec_to_text(Spec),
+                         case {atom_to_list(Type), Str} of
+                             {T, V} ->
+                                 ACL;
+                             _ ->
+                                 NewSpec = string_to_spec(T, V),
+                                 {acl, Name, NewSpec}
+                         end;
+                     _ ->
+                         ACL
+                 end
+         end, ACLs),
+    NewACL = case {lists:keysearch("namenew", 1, Query),
+                  lists:keysearch("typenew", 1, Query),
+                  lists:keysearch("valuenew", 1, Query)} of
+                {{value, {_, ""}}, _, _} ->
+                    [];
+                {{value, {_, N}}, {value, {_, T}}, {value, {_, V}}} ->
+                    NewName = list_to_atom(N),
+                    NewSpec = string_to_spec(T, V),
+                    [{acl, NewName, NewSpec}];
+                _ ->
+                    []
+            end,
+    NewACLs ++ NewACL.
+
+string_to_spec("user", Val) ->
+    {user, Val};
+string_to_spec("server", Val) ->
+    {server, Val};
+string_to_spec("user_server", Val) ->
+    #jid{luser = U, lserver = S, resource = ""} = jlib:string_to_jid(Val),
+    {user_server, U, S};
+string_to_spec("raw", Val) ->
+    {ok, Tokens, _} = erl_scan:string(Val ++ "."),
+    {ok, NewSpec} = erl_parse:parse_term(Tokens),
+    NewSpec.
+
+
+acl_parse_delete(ACLs, Query) ->
+    NewACLs =
+       lists:filter(
+         fun({acl, Name, Spec} = ACL) ->
+                 ID = term_to_id(ACL),
+                 not lists:member({"selected", ID}, Query)
+         end, ACLs),
+    NewACLs.
+
+
+access_rules_to_xhtml(AccessRules) ->
+    ?XAE("table", [],
+        [?XE("tbody",
+             lists:map(
+               fun({access, Name, Rules} = Access) ->
+                       SName = atom_to_list(Name),
+                       ID = term_to_id(Access),
+                       ?XE("tr",
+                           [?XE("td", [?INPUT("checkbox", "selected", ID)]),
+                            ?XE("td", [?AC(SName ++ "/", SName)]),
+                            ?XC("td", term_to_string(Rules))
+                           ]
+                          )
+               end, AccessRules) ++
+             [?XE("tr",
+                  [?X("td"),
+                   ?XE("td", [?INPUT("text", "namenew", "")]),
+                   ?XE("td", [?INPUT("submit", "addnew", "Add New")])
+                  ]
+                 )]
+            )]).
+
+access_parse_query(Query) ->
+    AccessRules =
+       ets:select(config,
+                  [{{config, {access, '$1'}, '$2'},
+                    [],
+                    [{{access, '$1', '$2'}}]}]),
+    case lists:keysearch("addnew", 1, Query) of
+       {value, _} ->
+           access_parse_addnew(AccessRules, Query);
+       _ ->
+           case lists:keysearch("delete", 1, Query) of
+               {value, _} ->
+                   access_parse_delete(AccessRules, Query)
+           end
+    end.
+
+access_parse_addnew(AccessRules, Query) ->
+    case lists:keysearch("namenew", 1, Query) of
+       {value, {_, String}} when String /= "" ->
+           Name = list_to_atom(String),
+           ejabberd_config:add_global_option({access, Name}, []),
+           ok
+    end.
+
+access_parse_delete(AccessRules, Query) ->
+    lists:foreach(
+      fun({access, Name, _Rules} = AccessRule) ->
+             ID = term_to_id(AccessRule),
+             case lists:member({"selected", ID}, Query) of
+                 true ->
+                     mnesia:transaction(
+                       fun() ->
+                               mnesia:delete({config, {access, Name}})
+                       end);
+                 _ ->
+                     ok
+             end
+      end, AccessRules),
+    ok.
+
+
+
+
+access_rule_to_xhtml(Rules) ->
+    Text = lists:flatmap(
+            fun({Access, ACL} = Rule) ->
+                    SAccess = atom_to_list(Access),
+                    SACL = atom_to_list(ACL),
+                    SAccess ++ "\t" ++ SACL ++ "\n"
+            end, Rules),
+    ?XAC("textarea", [{"name", "rules"},
+                     {"rows", "16"},
+                     {"cols", "80"}],
+        Text).
+
+parse_access_rule(Text) ->
+    Strings = string:tokens(Text, "\r\n"),
+    case catch lists:flatmap(
+                fun(String) ->
+                        case string:tokens(String, "\s\t") of
+                            [Access, ACL] ->
+                                [{list_to_atom(Access), list_to_atom(ACL)}];
+                            [] ->
+                                []
+                        end
+                end, Strings) of
+       {'EXIT', _Reason} ->
+           error;
+       Rs ->
+           {ok, Rs}
+    end.
+
+
+
+
+list_users() ->
+    Users = ejabberd_auth:dirty_get_registered_users(),
+    SUsers = lists:sort(Users),
+    case length(SUsers) of
+       N when N =< 100 ->
+           lists:flatmap(
+             fun(U) ->
+                     [?AC("../user/" ++ U ++ "/", U), ?BR]
+             end, SUsers);
+       N ->
+           NParts = trunc(math:sqrt(N * 0.618)) + 1,
+           M = trunc(N / NParts) + 1,
+           lists:flatmap(
+             fun(K) ->
+                     L = K + M - 1,
+                     Node = integer_to_list(K) ++ "-" ++ integer_to_list(L),
+                     Last = if L < N -> lists:nth(L, SUsers);
+                               true -> lists:last(SUsers)
+                            end,
+                     Name = 
+                         lists:nth(K, SUsers) ++ [$\s, 226, 128, 148, $\s] ++
+                         Last,
+                     [?AC(Node ++ "/", Name), ?BR]
+             end, lists:seq(1, N, M))
+    end.
+
+list_users_in_diapason(Diap) ->
+    Users = ejabberd_auth:dirty_get_registered_users(),
+    SUsers = lists:sort(Users),
+    {ok, [S1, S2]} = regexp:split(Diap, "-"),
+    N1 = list_to_integer(S1),
+    N2 = list_to_integer(S2),
+    Sub = lists:sublist(SUsers, N1, N2 - N1 + 1),
+    lists:flatmap(
+      fun(U) ->
+             [?AC("../../user/" ++ U ++ "/", U), ?BR]
+      end, Sub).
+
+
+
+get_stats() ->
+    OnlineUsers = mnesia:table_info(presence, size),
+    AuthUsers = mnesia:table_info(session, size),
+    RegisteredUsers = mnesia:table_info(passwd, size),
+    S2SConns = ejabberd_s2s:dirty_get_connections(),
+    S2SConnections = length(S2SConns),
+    S2SServers = length(lists:usort([element(2, C) || C <- S2SConns])),
+    
+    [?XAE("table", [],
+         [?XE("tbody",
+              [?XE("tr", [?XC("td", "Registered users"),
+                          ?XC("td", integer_to_list(RegisteredUsers))]),
+               ?XE("tr", [?XC("td", "Authentificated users"),
+                          ?XC("td", integer_to_list(AuthUsers))]),
+               ?XE("tr", [?XC("td", "Online users"),
+                          ?XC("td", integer_to_list(OnlineUsers))]),
+               ?XE("tr", [?XC("td", "Outgoing S2S connections"),
+                          ?XC("td", integer_to_list(S2SConnections))]),
+               ?XE("tr", [?XC("td", "Outgoing S2S servers"),
+                          ?XC("td", integer_to_list(S2SServers))])
+              ])
+         ])].