10/22/2003 [Stefan Vogel]
I just wanted to get familar with metakits. So I build up a very simply but still useful (at least for me) ''Webadministration-Interface'' for [MetaKit].
This webadministration '''Metakit@Web''' works with [Tclhttpd].
With ''Metakit@Web'' you can:
* create metakit-database-files
* create, modify, delete views
* create, edit, delete rows
* view binaries
* test queries ...
Installation is quite simple (as always with Tclhttpd):
* Extract the following script or download it from http://www.vogel-nest.de/tcl
* Copy the script "mkweb.tcl" to your tclhttpd-custom-directory (may be you have to patch your Tclhttpd so that custom-files are read: http://sourceforge.net/tracker/index.php?func=detail&aid=635083&group_id=12884&atid=112884 )
* Create a directory where you place your metakits (let's say: /metakits or c:/metakits)
* Adapt the directory in mkweb.tcl (e.g. set the array aConfig(databaseDir) to /metakits or c:/metakits)
* restart Tclhttpd
* Go to http:// This document is designed to be viewed using the frames feature. If you see this message, you are using a non-frame-capable web client. Just to get myself familar with metakit I tried to locate a good "viewer".
But unfortunately all those metakit-viewers around either don't work or they don't do what I want. That was the time I decided to write a little web-frontend (nowadays it's popular to have a web-frontend
for almost anything, e.g. MySql). But because the web-frontend should be as easy as possible
I chosed Tcl and the Tcl-Webserver (Tclhttpd) for this purpose. And what should I say? Tclhttpd is the coolest webserver I've ever seen :-) As you seem to be able to read this, you have successfully installed Metakit@Web. You should create a directory where you place your metakit-files which should be editable from
Metakit@Web. This directory has to be configured in the Furthermore you can set the You can also configure some values via the configuration-frame below. Simply enter a different value
and click "Go". Afterwards you should "reload" the pages in which you expect the changes. Configuration-options (see below in configuration-frame) are:
The frames of Metakit@Web looks like this
Frame Alert
}]
if {![file isdirectory $aConfig(databaseDir)]} {
set result "Filename Size New Error
Database-directory: '$aConfig(databaseDir)' does not exist. Please create it or modify mkweb.tcl metakit::web::execute::aConfig(databaseDir)
."
} else {
foreach db [set lDb [glob -nocomplain -- $aConfig(databaseDir)/*]] {
set _db [file tail $db]
append dbList [subst { }]
}
set result [subst {[::html::h1 "Files"]
$_db [format "%.2f kB" [expr [file size $db]/1024.0]]
Delete $dbList
}]
}
}
default {
error "Unknown mode '$_args(mode)' for listdb"
}
}
ReturnResult $result $redirect
}
proc metakit::web::execute/listviews {args} {
variable aConfig
array set _args $args
if {![info exists _args(mode)]} {
set _args(mode) select
}
if {[info exists _args(db)]} {
set db $_args(db)
} else {
return [generateHtml ""]
}
set result ""
set redirect ""
# set result "$args
"
OpenDb $db
switch -- [string tolower $_args(mode)] {
select {
set viewList [subst { }]
foreach view [set lView [mk::file views db]] {
append viewList [subst {View Columns Datatype New View }]
foreach col [mk::view layout db.$view] {
foreach {colName datatype} [split $col ":"] {break}
append viewList [subst {$view
Modify
Delete
}]
}
append viewList {$colName
[expr {$datatype != "" ? $datatype : "S"}] }
}
mk::file close db
append result [subst {[::html::h1 "Views of metakit: '$db'"]
$viewList
}]
}
newview {
append result [subst {
Structure: e.g. id:I name:S street:S salary:F
:S string
:I integer
:L long
:F float
:D double
:B binary
}]
}
create {
mk::view layout db.$_args(viewname) $_args(structure)
mk::file commit db
set redirect "listviews?db=$db"
}
delete {
mk::view delete db.$_args(view)
mk::file commit db
set redirect "listviews?db=$db"
}
modifyview {
append result [subst {
Modifying a structure may delete the whole content!
Structure: e.g. id:I name:S street:S salary:F
:S string
:I integer
:L long
:F float
:D double
:B binary
}]
}
modify {
mk::view layout db.$_args(view) $_args(structure)
mk::file commit db
set redirect "listviews?db=$db"
}
default {
error "No such mode '$_args(mode)' in listviews"
}
}
CloseDb
ReturnResult $result $redirect
}
# needs db, view-parameter
proc metakit::web::execute/listmain {args} {
variable aConfig
array set _args $args
if {[info exists _args(db)]} {
OpenDb [set db $_args(db)]
} else {
return [generateHtml ""]
}
if {![info exists _args(mode)]} {
set _args(mode) browse
}
if {![info exists _args(query)]} {
set query ""
} else {
set query $_args(query)
}
set lStdLink [list db=$db view=$_args(view)]
set result ""
set redirect ""
switch -- [set mode [string tolower $_args(mode)]] {
query -
browse {
# the following parameters are used to browse through content
# index - startindex
# count - number of rows to show
# sort - either sort or rsort
# column - sort - column
set view [mk::view open db.$_args(view)]
foreach col [mk::view layout db.$_args(view)] {
if {[llength [set _col [split $col :]]] > 1} {
set aDatatype([lindex $_col 0]) [lindex $_col 1]
} else {
set aDatatype([lindex $_col 0]) S
}
}
set lAddLink {}
# attention, e.g for wikit-database we need to escape some html-chars
set cmd "mk::select db.$_args(view)"
if {[info exists _args(sort)]} {
append cmd " -$_args(sort) $_args(column)"
eval lappend lAddLink [list sort=$_args(sort) column=$_args(column)]
}
if {[info exists _args(first)]} {
append cmd " -first $_args(first)"
lappend lAddLink "first=$_args(first)"
} else {
set _args(first) 0
}
set cursor ""
if {[info exists aConfig(maxRows)] && $aConfig(maxRows) != ""} {
append cmd " -count $aConfig(maxRows)"
set cursor [AddCursor $_args(first) $aConfig(maxRows) [$view size] "listmain?[join $lStdLink &]&[join $lAddLink &]"]
}
set sContent [subst { }]
append cmd " $query"
set i 0
foreach r [eval $cmd] {
append sContent [subst {un/ mark all index ↑ }]
foreach col [mk::view layout db.$_args(view)] {
set queryLink "listmain?[join $lStdLink &]&[join $lAddLink &]&column=[lindex [split $col :] 0]&sort="
append sContent [subst {$col ↑ ↓ }]
}
append sContent [subst {New row \n}]
}
foreach {key value} $args {
lappend queryParam "$key=$value"
}
set result [subst {[::html::h1 "Content of metakit: '$db' view: '$_args(view)'"]
Total view-size: [$view size] rows$r }]
foreach {key value} [mk::get db.$_args(view)!$r] {
append sContent "[prepareData $value $aDatatype($key) download.bin?db=$db&view=$_args(view)&index=$r&column=$key] "
}
append sContent [subst {Delete
Query-Command: $cmd
$cursor
$cursor}]
}
newrow -
edit {
if {$mode == "newrow"} {
set msg [subst {New row in view '$_args(view)' in metakit '$db'
}]
set index ""
} else {
# mode == edit
set msg [subst {
Edit row $_args(index) in view '$_args(view)' in metakit '$db'
}
} else {
# mode == edit
set lRow [mk::get db.$_args(view)!$_args(index)]
foreach {col value} $lRow {
append result [subst { \nindex }]
set index "$_args(index) "
}
set layout [mk::view layout db.$_args(view)]
append result [subst {$index
if {$mode == "newrow"} {
foreach col $layout {
set col [lindex [split $col :] 0]
append result [subst { [prepareInput $col $aDatatype($col)] }]
}
append result {[prepareInput $col $aDatatype($col) $value] }]
}
append result [subst {
}]
}
}
{delete marked} -
delete {
# action from delete
if {[info exists _args(index)]} {
# delete a row (given by index from view)
mk::row delete db.$_args(view)!$_args(index)
} else {
# marked multiple elements to delete (via checkboxes)
set lResult {}
foreach elem [array names _args delete_col_*] {
set i [string range $elem 11 end]
mk::row delete db.$_args(view)!$i
}
}
set redirect "listmain?[join $lStdLink &]"
}
default {
error "No such mode '$_args(mode)' in listmain"
}
}
CloseDb
ReturnResult $result $redirect
}
# issued from edit/safe or new row/create
proc metakit::web::execute/saferow {args} {
set result ""
set redirect ""
foreach {n v} [ncgi::nvlist] {
set _args($n) [lindex $v 1]
}
OpenDb [set db $_args(db)]
set lStdLink [list db=$db view=$_args(view)]
switch -- [string tolower $_args(mode)] {
create {
# action from newrow
foreach var [array names _args colvalue_*] {
lappend lNewRow [string range $var 9 end] [set _args($var)]
}
append result "args: $args lNewRow: $lNewRow ncgi::value colvalue_data: [ncgi::value colvalue_data]"
set vw [mk::view open db.$_args(view)]
eval mk::row append db.$_args(view) $lNewRow
set redirect "listmain?[join $lStdLink &]"
}
safe {
# action from clicking "safe" in edit-form
foreach var [array names _args colvalue_*] {
set index [string range $var 9 end]
append result "index
info exists _args(bool_colvalue_$index): [info exists _args(bool_colvalue_$index)]
"
if {[info exists _args(bool_colvalue_$index)]} {
append result "_args(bool_colvalue_$index): $_args(bool_colvalue_$index)
"
}
if {![info exists _args(bool_colvalue_$index)] || $_args(bool_colvalue_$index) != 1} {
lappend lRow $index [set _args($var)]
}
}
append result "lRow: $lRow"
eval mk::set db.$_args(view)!$_args(index) $lRow
set redirect "listmain?[join $lStdLink &]"
}
}
CloseDb
ReturnResult $result $redirect
}
proc metakit::web::execute/download.bin {args} {
array set _args $args
set ::metakit::web::execute/download.bin application/octet-stream
OpenDb $_args(db)
set result [mk::get db.$_args(view)!$_args(index) $_args(column)]
CloseDb
return $result
}
proc metakit::web::execute/listconfig {args} {
variable aConfig
array set _args $args
set result ""
set redirect ""
if {[info exists _args(mode)] && $_args(mode) == "safe"} {
# set retrieved values into config-array and go back
foreach var {maxRows maxChars displayBinaries} {
if {[info exists _args($var)]} {
set aConfig($var) $_args($var)
} else {
unset -nocomplain aConfig($var)
}
}
set aConfig(rowColsTexts) [list $_args(rowsText) $_args(colsText)]
set redirect listconfig
} else {
# browse-mode
set result [subst {
}]
}
ReturnResult $result $redirect
}
################################################################################
# Help
proc metakit::web::execute/help {} {
variable aConfig
set result [subst {Configuration:
Welcome to Metakit@Web
Missing features are (resp. pay ATTENTION!!):
Installation/Configuration
Just to mention a few things:mkweb.tcl
-file
(Variable: aConfig(databaseDir)
currently set to: $aConfig(databaseDir)
).aConfig(prefix)
(currently: $aConfig(prefix)
).
This variable sets the url-prefix under which you can access Metakit@Web, e.g. now it is:
http://$::env(HTTP_HOST)/$aConfig(prefix).
Rows/Cols for edit-textareas The size of the textarea when editing (:S - string)-values.
If you have stored large strings it may make sense to enlarge the size. Max. rows of view How many rows of a view should be displayed? If you set this to "" all
rows will be shown (possibly only a good idea for small views). Max. characters of view-content When viewing the content of a view, all string-values will
be truncated to the size given here. If you want to see the full length, set this value to "". General remarks
DB-frame
This lists all the files from aConfig(databaseDir)
. Be careful that you are using metakits here. Deleting a database will delete the file physically!Main-frame
Not "mainframe" :-) This frame displays the content of the views (and the help).View-frame
If you select a file from the DB-frame, the views and the structure of the views will be shown here. You can modify the views.configuration-frame
Common configuration-options to be changed globally.
In general the navigation (back, refresh, ..) is left to you and your browser, so you won't find any
"back"-buttons in Metakit@Web.
Normally the names of database-files, view-names, row-indices link to the content of the item.
The last column of the table (database, views or rows) contains the "New ..."-link for each row an editing link.
Usually you go to the "File/database"-frame, select a file, go to the "View"-frame and select a view (the content is displayed in the main-area).
To Jean-Claude Wippler for his cool Metakit and the whole Tcl-community for their never-ending patience and helpfulness comments. You can find this script on the Tcl'ers Wiki or go to my Homepage.
This script is free software. But don't blame me if something get's wrong.
Suggestions or bug-fixes are always welcome, simply drop me an email or edit the script on the Wiki.
Stefan Vogel -- stefan at vogel-nest dot de
There are 3 alternatives here: