[ignore] smaller changes on admin panel

[ignore] smaller changes on admin panel

svn path=/trunk/eXist/; revision=13853

xquery version "1.0";
(: $Id$ :)
(:
    Main module of the database administration interface.
:)

declare namespace admin = "http://exist-db.org/xquery/admin-interface";

declare namespace request = "http://exist-db.org/xquery/request";
declare namespace session = "http://exist-db.org/xquery/session";
declare namespace util = "http://exist-db.org/xquery/util";
declare namespace xdb = "http://exist-db.org/xquery/xmldb";

import module namespace status = "http://exist-db.org/xquery/admin-interface/status" at "status.xqm";
import module namespace browse = "http://exist-db.org/xquery/admin-interface/browse" at "browse.xqm";
import module namespace users = "http://exist-db.org/xquery/admin-interface/users" at "users.xqm";
import module namespace xqueries = "http://exist-db.org/xquery/admin-interface/xqueries" at "xqueries.xqm";
import module namespace shut = "http://exist-db.org/xquery/admin-interface/shutdown" at "shutdown.xqm";
import module namespace setup = "http://exist-db.org/xquery/admin-interface/setup" at "setup.xqm";
import module namespace rev="http://exist-db.org/xquery/admin-interface/revisions" at "versions.xqm";
import module namespace backup="http://exist-db.org/xquery/admin-interface/backup" at "backup.xqm";
import module namespace prof="http://exist-db.org/xquery/profiling" at "trace.xqm";
import module namespace grammar="http://exist-db.org/xquery/admin-interface/grammar" at "grammar.xqm";
import module namespace install="http://exist-db.org/xquery/install-tools" at "install.xqm";
import module namespace fundocs="http://exist-db.org/xquery/admin/fundocs" at "fundocs.xqm";
import module namespace repomanager="http://exist-db.org/xquery/admin-interface/repo" at "repo.xqm";
import module namespace indexes="http://exist-db.org/xquery/admin-interface/indexes" at "indexes.xqm";

declare option exist:serialize "method=xhtml media-type=text/html";

(:
    Display the version, SVN revision and user info in the top right corner
:)
declare function admin:info-header() as element()
{

Version: { util:system-property( "product-version" ) }

SVN Revision: { util:system-property( "svn-revision" ) }

Build: {util:system-property("product-build")}

User: { xdb:get-current-user() }

};

(:
    Select the page to show. Every page is defined in its own module
:)
declare function admin:panel() as element()
{
    let $panel := request:get-parameter("panel", "status")[1] return
        if($panel eq "browse") then
        (
            browse:main()
        )
        else if($panel eq "users") then
        (
            users:main()
        )
         else if($panel eq "xqueries") then
        (
            xqueries:main()
        )
        else if($panel eq "shutdown") then
        (
            shut:main()
        )
        else if($panel eq "setup") then
        (
            setup:main()
        )
        else if ($panel eq "fundocs") then
        (
            fundocs:main()
        )
		else if ($panel eq "repo") then
		(
			repomanager:main()
		)
		else if ($panel eq "revisions") then
		(
			rev:main()
		)

		else if ($panel eq "backup") then
		(
		    backup:main()
		)
	    else if ($panel eq "trace") then
	    (
	        prof:main()
        )
        else if ($panel eq "grammar") then
	    (
	        grammar:main()
        )
        else if ($panel eq "install") then
        (
            install:main()
        )
        else if ($panel eq "indexes") then
        (
            indexes:main()
        )
        else
        (
            status:main()
        )
};

declare function admin:panel-header() {
    let $panel := request:get-parameter("panel", "status")[1]
    return
        if ($panel eq "install") then
            install:header()
        else
            <xf:model xmlns:xf="http://www.w3.org/2002/xforms">
            </xf:model>
};

(:~
    Display the login form.
:)
declare function admin:display-login-form() as element()
{

Login

This is a protected resource. Only registered database users can log
        in. If you have not set up any users, login as "admin" and leave the
        password field empty. Note that the "guest" user is not permitted access.

        <form action="{session:encode-url(request:get-uri())}" method="post">

                    Please Login

                    Username:
                    <input name="user" type="text" size="20"/>

                    Password:
                    <input name="pass" type="password" size="20"/>

                    <input type="submit" value="Login"/>

            {
                for $param in request:get-parameter-names()
                return
                    <input type="hidden" name="{$param}"
                        value="{request:get-parameter($param, ())}"/>
            }
        </form>

};

(: main entry point :)
let $userParam := request:get-parameter("user", ())[1]
let $passwdParam := request:get-parameter("pass", ())[1]
let $isLoggedIn :=  if(xdb:get-current-user() eq "guest")then
    (
        (: is this a login attempt? :)
        if($userParam and not(empty($passwdParam)))then
        (
            if($userParam = ("", "guest") )then
            (
                (: prevent the guest user from accessing the admin webapp :)
                false()
            )
            else
            (
                (: try and log the user in :)
                xdb:login( "/db", $userParam, $passwdParam, true() )
            )
        )
        else
        (
            (: prevent the guest user from accessing the admin webapp :)
            false()
        )
    )
    else
    (
        (: if we are already logged in, are we logging out - i.e. set permissions back to guest :)
        if(request:get-parameter("logout",()))then
        (
        	let $null  := xdb:login("/db", "guest", "guest")
            let $inval := session:invalidate()

            return false()
        )
        else
        (
             (: we are already logged in and we are not the guest user :)
            true()
        )
    )
return (
    <?css-conversion no?>,

            <title>eXist Database Administration</title>
            <link type="text/css" href="admin.css" rel="stylesheet"/>
			<link type="text/css" href="styles/prettify.css" rel="stylesheet"/>
			<link type="text/css" href="../scripts/yui/yui-skin.css" rel="stylesheet"/>
            <link rel="shortcut icon" href="../resources/exist_icon_16x16.ico"/>
			<link rel="icon" href="../resources/exist_icon_16x16.png" type="image/png"/>

			{ admin:panel-header() }

                {admin:info-header()}

Select a Page

                    {
                        let $link := session:encode-url(request:get-uri())
                        return

Browse

Collections

Indexes

Install

Documentation

Examples

System

Shutdown

Status

Tooling

Backups

Package Repository

Query Profiling

User Management

View

Grammar cache

Running Jobs

Home

Logout

                    }

                        Logged in as: {xdb:get-current-user()}

                {
                    if($isLoggedIn)then
                    (
                        admin:panel()
                    )
                    else
                    (
                        admin:display-login-form()
                    )
                }

)

xquery version "1.0";
(: $Id$ :)
(:
    Main module of the database administration interface.
:)

declare namespace admin = "http://exist-db.org/xquery/admin-interface";

declare namespace request = "http://exist-db.org/xquery/request";
declare namespace session = "http://exist-db.org/xquery/session";
declare namespace util = "http://exist-db.org/xquery/util";
declare namespace xdb = "http://exist-db.org/xquery/xmldb";

import module namespace status = "http://exist-db.org/xquery/admin-interface/status" at "status.xqm";
import module namespace browse = "http://exist-db.org/xquery/admin-interface/browse" at "browse.xqm";
import module namespace users = "http://exist-db.org/xquery/admin-interface/users" at "users.xqm";
import module namespace xqueries = "http://exist-db.org/xquery/admin-interface/xqueries" at "xqueries.xqm";
import module namespace shut = "http://exist-db.org/xquery/admin-interface/shutdown" at "shutdown.xqm";
import module namespace setup = "http://exist-db.org/xquery/admin-interface/setup" at "setup.xqm";
import module namespace rev="http://exist-db.org/xquery/admin-interface/revisions" at "versions.xqm";
import module namespace backup="http://exist-db.org/xquery/admin-interface/backup" at "backup.xqm";
import module namespace prof="http://exist-db.org/xquery/profiling" at "trace.xqm";
import module namespace grammar="http://exist-db.org/xquery/admin-interface/grammar" at "grammar.xqm";
import module namespace install="http://exist-db.org/xquery/install-tools" at "install.xqm";
import module namespace fundocs="http://exist-db.org/xquery/admin/fundocs" at "fundocs.xqm";
import module namespace repomanager="http://exist-db.org/xquery/admin-interface/repo" at "repo.xqm";
import module namespace indexes="http://exist-db.org/xquery/admin-interface/indexes" at "indexes.xqm";

declare option exist:serialize "method=xhtml media-type=text/html";

(:
    Display the version, SVN revision and user info in the top right corner
:)
declare function admin:info-header() as element()
{

Version: { util:system-property( "product-version" ) }

SVN Revision: { util:system-property( "svn-revision" ) }

Build: {util:system-property("product-build")}

User: { xdb:get-current-user() }

};

(:
    Select the page to show. Every page is defined in its own module
:)
declare function admin:panel() as element()
{
    let $panel := request:get-parameter("panel", "status")[1] return
        if($panel eq "browse") then
        (
            browse:main()
        )
        else if($panel eq "users") then
        (
            users:main()
        )
         else if($panel eq "xqueries") then
        (
            xqueries:main()
        )
        else if($panel eq "shutdown") then
        (
            shut:main()
        )
        else if($panel eq "setup") then
        (
            setup:main()
        )
        else if ($panel eq "fundocs") then
        (
            fundocs:main()
        )
		else if ($panel eq "repo") then
		(
			repomanager:main()
		)
		else if ($panel eq "revisions") then
		(
			rev:main()
		)

		else if ($panel eq "backup") then
		(
		    backup:main()
		)
	    else if ($panel eq "trace") then
	    (
	        prof:main()
        )
        else if ($panel eq "grammar") then
	    (
	        grammar:main()
        )
        else if ($panel eq "install") then
        (
            install:main()
        )
        else if ($panel eq "indexes") then
        (
            indexes:main()
        )
        else
        (
            status:main()
        )
};

declare function admin:panel-header() {
    let $panel := request:get-parameter("panel", "status")[1]
    return
        if ($panel eq "install") then
            install:header()
        else
            <xf:model xmlns:xf="http://www.w3.org/2002/xforms">
            </xf:model>
};

(:~
    Display the login form.
:)
declare function admin:display-login-form() as element()
{

Login

This is a protected resource. Only registered database users can log
        in. If you have not set up any users, login as "admin" and leave the
        password field empty. Note that the "guest" user is not permitted access.

        <form action="{session:encode-url(request:get-uri())}" method="post">

                    Please Login

                    Username:
                    <input name="user" type="text" size="20"/>

                    Password:
                    <input name="pass" type="password" size="20"/>

                    <input type="submit" value="Login"/>

            {
                for $param in request:get-parameter-names()
                return
                    <input type="hidden" name="{$param}"
                        value="{request:get-parameter($param, ())}"/>
            }
        </form>

};

(: main entry point :)
let $userParam := request:get-parameter("user", ())[1]
let $passwdParam := request:get-parameter("pass", ())[1]
let $isLoggedIn :=  if(xdb:get-current-user() eq "guest")then
    (
        (: is this a login attempt? :)
        if($userParam and not(empty($passwdParam)))then
        (
            if($userParam = ("", "guest") )then
            (
                (: prevent the guest user from accessing the admin webapp :)
                false()
            )
            else
            (
                (: try and log the user in :)
                xdb:login( "/db", $userParam, $passwdParam, true() )
            )
        )
        else
        (
            (: prevent the guest user from accessing the admin webapp :)
            false()
        )
    )
    else
    (
        (: if we are already logged in, are we logging out - i.e. set permissions back to guest :)
        if(request:get-parameter("logout",()))then
        (
        	let $null  := xdb:login("/db", "guest", "guest")
            let $inval := session:invalidate()

            return false()
        )
        else
        (
             (: we are already logged in and we are not the guest user :)
            true()
        )
    )
return (
    <?css-conversion no?>,

            <title>eXist Database Administration</title>
            <link type="text/css" href="admin.css" rel="stylesheet"/>
			<link type="text/css" href="styles/prettify.css" rel="stylesheet"/>
			<link type="text/css" href="../scripts/yui/yui-skin.css" rel="stylesheet"/>
            <link rel="shortcut icon" href="../resources/exist_icon_16x16.ico"/>
			<link rel="icon" href="../resources/exist_icon_16x16.png" type="image/png"/>

			{ admin:panel-header() }

                {admin:info-header()}

Select a Page

                    {
                        let $link := session:encode-url(request:get-uri())
                        return

Home

System Status

Browse Collections

Browse Indexes

User Management

View Grammar cache

View Running Jobs

Install Documentation

Install Examples

Install Tools

Backups

Query Profiling

Package Repository

Shutdown

Logout

                    }

                        Logged in as: {xdb:get-current-user()}

                {
                    if($isLoggedIn)then
                    (
                        admin:panel()
                    )
                    else
                    (
                        admin:display-login-form()
                    )
                }

)


tweaking voyant affiliations regex

tweaking voyant affiliations regex


delete

delete

This line, and those below, will be ignored--

D    repository/saveRepositoryIDs.xql

svn path=/trunk/eXist/; revision=12071

import module namespace http = "http://www.expath.org/mod/http-client" at "/db/scripts/http-client/http-client.xq";
declare namespace rb="http://kjc-fs1.kjc.uni-heidelberg.de/refbase";

(: credentials :)
declare variable $scope  := doc('/db/scripts/refbase/conf.xml');
declare variable $repository-name as xs:string := $scope/repositroy/name/string();
declare variable $database as xs:string := $scope/repositroy/driver/database/string();
declare variable $repository := concat($repository-name,"?repository=",$database,"&");
declare variable $username as xs:string := $scope/repositroy/driver/user/string();
declare variable $password as xs:string := $scope/repositroy/driver/password/string();
declare variable $collection := 'xmldb:exist:///db/mods/refbase/';
declare variable $file-name := 'refbase-metadata.xml';

(: the transform rule :)
declare function local:transform-serials($doc as document-node()) as document-node() {
 document {
   <modsCollection>
   {
     for $serial in $doc//rb:repository/sql:result/sql:row/sql:serial/string()
       return  local:transform-entries(local:get-in($serial))
   }
   </modsCollection>
 }
};

(:author (; nachnanem,vorname) title<titleInfo:title> year<originInfo:dateIssued> publisher<originInfo:c> place (;) <place:placeTerm>  isbn  <identifier type="isbn"> cluster_publication(note type="cp") :)
declare function local:transform-entries($doc as document-node()) as document-node() {
 document {

     for $serial in $doc//rb:repository/rb:id/sql:result/sql:row
      return
        <mods xmlns="http://www.loc.gov/mods/v3" version="3.4" ID="{$serial/sql:serial/string()}">
        <typeOfResource>text</typeOfResource>
        <titleInfo><title>{$serial/sql:title/string()}</title></titleInfo>

        {
        let $names := fn:tokenize($serial/sql:author/string(),';')
        for $name in $names
            return
             <name type="personal">
            <namePart type="given">{$name}</namePart>
        <role><roleTerm authority="marcrelator" type="text">author</roleTerm></role>
        </name>

        }
   (:use : retrieve mods functions , if avavailable:)
        <originInfo><dateIssued>{$serial/sql:year/string()}</dateIssued></originInfo>
        <originInfo><publisher>{$serial/sql:publisher/string()}</publisher></originInfo>
        <place><placeTerm>{$serial/sql:place/string()}</placeTerm></place>
        <identifier type="isbn">{$serial/sql:isbn/string()}</identifier>

   </mods>
   }

};

(: retrieve the input doc :)
declare function local:get-in($serial) as document-node() {
let $serial_id := concat('serial=',$serial)
 let $req := <http:request href="{$repository}{$serial_id}"
                           method="get"
                           username="{ $username }"
                           password="{ $password }"
                           auth-method="basic"
                           send-authorization="true"/>
   return
     (: error checking is left as an exercise :)
     http:send-request($req)[2]
};

(: save the document to database:)
declare function local:save-file($doc as document-node()) {
  let $login := xmldb:login($collection, $username, $password)
  let $store-return-status := xmldb:store($collection, $file-name, $doc)
  return 1
};

(: retrieve a doc, transform it and upload the result :)

let $serial  :=  xs:string(request:get-parameter('serial', ''))
return
<modsCollection>
{
if (not($serial))
 then (
  (:local:transform-serials(local:get-in($serial)) :)
  system:as-user('admin','SP1WS22-cb',local:save-file(local:transform-serials(local:get-in($serial))))
 )
 else (
  local:transform-entries(local:get-in($serial))
 )

}
</modsCollection>


[MOD] DBA logs: faster search

[MOD] DBA logs: faster search

(:~
 : Logging page.
 :
 : @author Christian Grün, BaseX Team 2005-19, BSD License
 :)
module namespace dba = 'dba/logs';

import module namespace html = 'dba/html' at '../lib/html.xqm';
import module namespace options = 'dba/options' at '../lib/options.xqm';

(:~ Top category :)
declare variable $dba:CAT := 'logs';

(:~
 : Redirects to the URL that creates a log entry table for the specified timestamp.
 : @param  $name  name (date) of log file
 : @param  $time  timestamp to be found
 : @return redirection
 :)
declare
  %rest:GET
  %rest:path('/dba/logs-jump')
  %rest:query-param('name', '{$name}')
  %rest:query-param('time', '{$time}')
function dba:logs-jump(
  $name  as xs:string,
  $time  as xs:string
) as element(rest:response) {
  let $page := head((
    let $ignore-logs := options:get($options:IGNORE-LOGS)
    let $max := options:get($options:MAXROWS)
    for $log at $pos in reverse(
      admin:logs($name, true())[not($ignore-logs and matches(., $ignore-logs, 'i'))]
    )
    where $log/@time = $time
    return ($pos - 1) idiv $max + 1,
    1
  ))
  return web:redirect('/dba/logs', map { 'name': $name, 'page': $page, 'time': $time }) update {
    .//*:header/@value ! (replace value of node . with . || '#' || $time)
  }
};

(:~
 : Logging page.
 : @param  $input  search input
 : @param  $name   name (date) of log file
 : @param  $sort   table sort key
 : @param  $error  error string
 : @param  $info   info string
 : @param  $page   current page
 : @param  $time   timestamp to highlight
 : @return page
 :)
declare
  %rest:GET
  %rest:path('/dba/logs')
  %rest:query-param('input', '{$input}')
  %rest:query-param('name',  '{$name}')
  %rest:query-param('sort',  '{$sort}', '')
  %rest:query-param('error', '{$error}')
  %rest:query-param('info',  '{$info}')
  %rest:query-param('page',  '{$page}', '1')
  %rest:query-param('time',  '{$time}')
  %output:method('html')
function dba:logs(
  $input  as xs:string?,
  $name   as xs:string?,
  $sort   as xs:string,
  $error  as xs:string?,
  $info   as xs:string?,
  $page   as xs:string,
  $time   as xs:string?
) as element(html) {
  let $files := (
    for $file in admin:logs()
    order by $file descending
    return $file
  )
  let $name := if($name) then $name else string(head($files))
  return html:wrap(map { 'header': $dba:CAT, 'info': $info, 'error': $error },

{ html:link('Logs', $dba:CAT) }

        <form action='{ $dba:CAT }' method='post' class='update'>
          <input type='hidden' name='name' id='name' value='{ $name }'/>
          <input type='hidden' name='sort' id='sort' value='{ $sort }'/>
          <input type='hidden' name='page' id='page' value='{ $page }'/>
          <input type='hidden' name='time' id='time' value='{ $time }'/>

{
            let $buttons := html:button('log-delete', 'Delete', true())
            let $headers := (
              map { 'key': 'name', 'label': 'Name', 'type': 'xml' },
              map { 'key': 'size', 'label': 'Size', 'type': 'bytes' }
            )
            let $entries :=
              for $entry in reverse(sort($files))
              return map {
                'name': function() {
                  let $link := html:link(
                    $entry, $dba:CAT, (map { 'sort': $sort }, map { 'name': $entry })
                  ) update {
                    (: enrich link targets with current search string :)
                    insert node attribute onclick { 'addInput(this);' } into .
                  }
                  return if ($name = $entry) then element b { $link } else $link
                },
                'size': $entry/@size
              }
            return html:table($headers, $entries, $buttons, map { }, map { })
          }

        </form>

      {
        if($name) then (
          <form action='log-download' method='post' id='resources' autocomplete='off'>

{
              $name, ' ',
              <input type='hidden' name='name' value='{ $name }'/>,
              <input size='40' id='input' name='input' value='{ $input }'
                title='Enter regular expression'
                onkeydown='if(event.keyCode==13) {{logEntries(true,false);event.preventDefault();}}'
                onkeyup='logEntries(false, true);'/>,
              ' ',
              html:button('download', 'Download')
            }

          </form>,

,
          html:js('logEntries(true, false);')
        ) else (),
        html:focus('input')
      }

  )
};

(:~
 : Returns a log entry table.
 : @param  $input  search input
 : @param  $name   name of selected log files
 : @param  $sort   table sort key
 : @param  $page   current page
 : @param  $time   timestamp to highlight
 : @return html elements
 :)
declare
  %rest:POST('{$input}')
  %rest:path('/dba/log')
  %rest:query-param('name', '{$name}')
  %rest:query-param('sort', '{$sort}', 'time')
  %rest:query-param('page', '{$page}', '1')
  %rest:query-param('time', '{$time}')
  %output:method('html')
  %output:indent('no')
  %rest:single
function dba:log(
  $input  as xs:string?,
  $name   as xs:string,
  $sort   as xs:string,
  $page   as xs:string,
  $time   as xs:string?
) as element()+ {
  let $headers := (
    map { 'key': 'time', 'label': 'Time', 'type': 'xml', 'order': 'desc' },
    map { 'key': 'address', 'label': 'Address' },
    map { 'key': 'user', 'label': 'User', 'type': 'xml' },
    map { 'key': 'type', 'label': 'Type', 'type': 'xml' },
    map { 'key': 'ms', 'label': 'ms', 'type': 'decimal', 'order': 'desc' },
    map { 'key': 'text', 'label': 'Text', 'type': 'xml' }
  )
  let $entries := (
    let $ignore-logs := options:get($options:IGNORE-LOGS)
    let $search := boolean($input)
    let $highlight := function($string) {
      for $match in analyze-string($string, $input, 'i')/*
      let $value := string($match)
      return if(local-name($match) = 'match') then element b { $value } else $value
    }

    for $log in reverse(admin:logs($name, true()))
    let $user := string($log/@user)
    let $type := string($log/@type)
    let $text := string($log)
    let $user-hit := $search and matches($user, $input, 'iq')
    let $type-hit := $search and not($user-hit) and matches($type, $input, 'iq')
    let $text-hit := $search and not($user-hit or $type-hit) and matches($text, $input, 'i')
    where (not($search) or $user-hit or $type-hit or $text-hit) and (
      not($ignore-logs and matches($text, $ignore-logs, 'i'))
    )
    let $id := string($log/@time)
    let $time := if($input or $sort != 'time') then (
      function() { html:link($id, $dba:CAT || '-jump', map { 'name': $name, 'time': $id }) }
    ) else if($id = $time) then (
      element b { $id }
    ) else (
      $id
    )
    return map {
      'id': $id,
      'time': $time,
      'address': string($log/@address),
      'user': if($user-hit) then function() { $highlight($user) } else $user,
      'type': if($type-hit) then function() { $highlight($type) } else $type,
      'ms': xs:decimal($log/@ms),
      'text': if($text-hit) then function() { $highlight($text) } else $text
    }
  )
  let $params := map { 'name': $name, 'input': $input }
  let $options := map { 'sort': $sort, 'presort': 'time', 'page': xs:integer($page) }
  return html:table($headers, $entries, (), $params, $options)
};

(:~
 : Redirects to the specified action.
 : @param  $action  action to perform
 : @param  $names   names of selected log files
 : @return redirection
 :)
declare
  %rest:POST
  %rest:path('/dba/logs')
  %rest:query-param('action', '{$action}')
  %rest:query-param('name',   '{$names}')
function dba:logs-redirect(
  $action  as xs:string,
  $names   as xs:string*
) as element(rest:response) {
  web:redirect($action, map { 'name': $names[.], 'redirect': $dba:CAT })
};

(:~
 : Logging page.
 :
 : @author Christian Grün, BaseX Team 2005-19, BSD License
 :)
module namespace dba = 'dba/logs';

import module namespace html = 'dba/html' at '../lib/html.xqm';
import module namespace options = 'dba/options' at '../lib/options.xqm';

(:~ Top category :)
declare variable $dba:CAT := 'logs';

(:~
 : Redirects to the URL that creates a log entry table for the specified timestamp.
 : @param  $name  name (date) of log file
 : @param  $time  timestamp to be found
 : @return redirection
 :)
declare
  %rest:GET
  %rest:path('/dba/logs-jump')
  %rest:query-param('name', '{$name}')
  %rest:query-param('time', '{$time}')
function dba:logs-jump(
  $name  as xs:string,
  $time  as xs:string
) as element(rest:response) {
  let $page := head((
    let $ignore-logs := options:get($options:IGNORE-LOGS)
    let $max := options:get($options:MAXROWS)
    for $log at $pos in reverse(
      admin:logs($name, true())[not($ignore-logs and matches(., $ignore-logs, 'i'))]
    )
    where $log/@time = $time
    return ($pos - 1) idiv $max + 1,
    1
  ))
  return web:redirect('/dba/logs', map { 'name': $name, 'page': $page, 'time': $time }) update {
    .//*:header/@value ! (replace value of node . with . || '#' || $time)
  }
};

(:~
 : Logging page.
 : @param  $input  search input
 : @param  $name   name (date) of log file
 : @param  $sort   table sort key
 : @param  $error  error string
 : @param  $info   info string
 : @param  $page   current page
 : @param  $time   timestamp to highlight
 : @return page
 :)
declare
  %rest:GET
  %rest:path('/dba/logs')
  %rest:query-param('input', '{$input}')
  %rest:query-param('name',  '{$name}')
  %rest:query-param('sort',  '{$sort}', '')
  %rest:query-param('error', '{$error}')
  %rest:query-param('info',  '{$info}')
  %rest:query-param('page',  '{$page}', '1')
  %rest:query-param('time',  '{$time}')
  %output:method('html')
function dba:logs(
  $input  as xs:string?,
  $name   as xs:string?,
  $sort   as xs:string,
  $error  as xs:string?,
  $info   as xs:string?,
  $page   as xs:string,
  $time   as xs:string?
) as element(html) {
  let $files := (
    for $file in admin:logs()
    order by $file descending
    return $file
  )
  let $name := if($name) then $name else string(head($files))
  return html:wrap(map { 'header': $dba:CAT, 'info': $info, 'error': $error },

{ html:link('Logs', $dba:CAT) }

        <form action='{ $dba:CAT }' method='post' class='update'>
          <input type='hidden' name='name' id='name' value='{ $name }'/>
          <input type='hidden' name='sort' id='sort' value='{ $sort }'/>
          <input type='hidden' name='page' id='page' value='{ $page }'/>
          <input type='hidden' name='time' id='time' value='{ $time }'/>

{
            let $buttons := html:button('log-delete', 'Delete', true())
            let $headers := (
              map { 'key': 'name', 'label': 'Name', 'type': 'xml' },
              map { 'key': 'size', 'label': 'Size', 'type': 'bytes' }
            )
            let $entries :=
              for $entry in reverse(sort($files))
              return map {
                'name': function() {
                  let $link := html:link(
                    $entry, $dba:CAT, (map { 'sort': $sort }, map { 'name': $entry })
                  ) update {
                    (: enrich link targets with current search string :)
                    insert node attribute onclick { 'addInput(this);' } into .
                  }
                  return if ($name = $entry) then element b { $link } else $link
                },
                'size': $entry/@size
              }
            return html:table($headers, $entries, $buttons, map { }, map { })
          }

        </form>

      {
        if($name) then (
          <form action='log-download' method='post' id='resources' autocomplete='off'>

{
              $name, ' ',
              <input type='hidden' name='name' value='{ $name }'/>,
              <input size='40' id='input' name='input' value='{ $input }'
                title='Enter regular expression'
                onkeydown='if(event.keyCode==13) {{logEntries(true,false);event.preventDefault();}}'
                onkeyup='logEntries(false, true);'/>,
              ' ',
              html:button('download', 'Download')
            }

          </form>,

,
          html:js('logEntries(true, false);')
        ) else (),
        html:focus('input')
      }

  )
};

(:~
 : Returns a log entry table.
 : @param  $input  search input
 : @param  $name   name of selected log files
 : @param  $sort   table sort key
 : @param  $page   current page
 : @param  $time   timestamp to highlight
 : @return html elements
 :)
declare
  %rest:POST('{$input}')
  %rest:path('/dba/log')
  %rest:query-param('name', '{$name}')
  %rest:query-param('sort', '{$sort}', 'time')
  %rest:query-param('page', '{$page}', '1')
  %rest:query-param('time', '{$time}')
  %output:method('html')
  %output:indent('no')
  %rest:single
function dba:log(
  $input  as xs:string?,
  $name   as xs:string,
  $sort   as xs:string,
  $page   as xs:string,
  $time   as xs:string?
) as element()+ {
  let $headers := (
    map { 'key': 'time', 'label': 'Time', 'type': 'xml', 'order': 'desc' },
    map { 'key': 'address', 'label': 'Address' },
    map { 'key': 'user', 'label': 'User', 'type': 'xml' },
    map { 'key': 'type', 'label': 'Type', 'type': 'xml' },
    map { 'key': 'ms', 'label': 'ms', 'type': 'decimal', 'order': 'desc' },
    map { 'key': 'text', 'label': 'Text', 'type': 'xml' }
  )
  let $entries :=
    let $ignore-logs := options:get($options:IGNORE-LOGS)
    let $search := boolean($input)
    let $highlight := function($string, $found) {
      if($found) then (
        for $match in analyze-string($string, $input, 'i')/*
        let $value := string($match)
        return if(local-name($match) = 'match') then element b { $value } else $value
      ) else (
        $string
      )
    }
    for $log in reverse(admin:logs($name, true()))
    let $user := string($log/@user)
    let $type := string($log/@type)
    let $msg := string($log)
    let $user-hit := $search and matches($user, $input, 'iq')
    let $type-hit := $search and not($user-hit) and matches($type, $input, 'iq')
    let $text-hit := $search and not($user-hit or $type-hit) and matches($msg, $input, 'i')
    where (not($search) or $user-hit or $type-hit or $text-hit) and (
      not($ignore-logs and matches($msg, $ignore-logs, 'i'))
    )
    let $id := string($log/@time)
    return map {
      'id': $id,
      'time': function() {
        if($input or $sort != 'time') then (
          html:link($id, $dba:CAT || '-jump', map { 'name': $name, 'time': $id })
        ) else if($id = $time) then (
          { $id }
        ) else (
          $id
        )
      },
      'address': $log/@address,
      'user': function() { $highlight($user, $user-hit) },
      'type': function() { $highlight($type, $type-hit) },
      'ms': $log/@ms,
      'text': function() { $highlight($msg, $text-hit) }
    }
  let $params := map { 'name': $name, 'input': $input }
  let $options := map { 'sort': $sort, 'presort': 'time', 'page': xs:integer($page) }
  return html:table($headers, $entries, (), $params, $options)
};

(:~
 : Redirects to the specified action.
 : @param  $action  action to perform
 : @param  $names   names of selected log files
 : @return redirection
 :)
declare
  %rest:POST
  %rest:path('/dba/logs')
  %rest:query-param('action', '{$action}')
  %rest:query-param('name',   '{$names}')
function dba:logs-redirect(
  $action  as xs:string,
  $names   as xs:string*
) as element(rest:response) {
  web:redirect($action, map { 'name': $names[.], 'redirect': $dba:CAT })
};