From dc53423183a0c459284ebd139022b707f01af006 Mon Sep 17 00:00:00 2001 From: Natanael Copa Date: Fri, 27 Jul 2007 12:53:38 +0000 Subject: moved core files to new dir structure git-svn-id: svn://svn.alpinelinux.org/acf/core/trunk@219 ab2d0c66-481e-0410-8bed-d214d4d58bed --- ChangeLog | 29 +++ Makefile | 53 ++++++ README | 11 ++ TODO | 8 + app/Makefile | 49 +++++ app/README | 48 +++++ app/acf-util/logon-controller.lua | 60 +++++++ app/acf-util/logon-html.lsp | 19 ++ app/acf-util/logon-model.lua | 61 +++++++ app/acf_www-controller.lua | 202 +++++++++++++++++++++ app/dummy/bar-controller.lua | 35 ++++ app/dummy/bar-html.lsp | 3 + app/dummy/bar.menu | 3 + app/foo-controller.lua | 36 ++++ app/foo-html.lsp | 3 + app/foo.menu | 2 + app/menuhints.menu | 8 + app/template-html.lsp | 99 ++++++++++ app/welcome-controller.lua | 27 +++ app/welcome-html.lsp | 3 + app/welcome.menu | 1 + config.mk | 10 ++ lib/Makefile | 47 +++++ lib/README | 3 + lib/ed.lua | 60 +++++++ lib/fs.lua | 89 +++++++++ lib/html.lua | 245 +++++++++++++++++++++++++ lib/join.lua | 25 +++ lib/log_view.lua | 56 ++++++ lib/menubuilder.lua | 150 ++++++++++++++++ lib/service_controller.lua | 189 ++++++++++++++++++++ lib/service_model.lua | 115 ++++++++++++ lib/session.lua | 143 +++++++++++++++ lib/split.lua | 31 ++++ lib/validator.lua | 156 ++++++++++++++++ lib/web_elements.lua | 156 ++++++++++++++++ www/Makefile | 50 ++++++ www/cgi-bin/acf | 19 ++ www/cgi-bin/mvc.lua | 278 +++++++++++++++++++++++++++++ www/index.html | 12 ++ www/sample.html | 102 +++++++++++ www/static/alpine.gif | Bin 0 -> 17876 bytes www/static/alpine.png | Bin 0 -> 43938 bytes www/static/arctic-back.png | Bin 0 -> 213 bytes www/static/arctic-upleft.png | Bin 0 -> 300 bytes www/static/greypx.gif | Bin 0 -> 38 bytes www/static/selected.gif | Bin 0 -> 1095 bytes www/static/submenu.css | 52 ++++++ www/static/unselected.gif | Bin 0 -> 945 bytes www/static/webconf-purple.css | 313 ++++++++++++++++++++++++++++++++ www/static/webconf.css | 367 ++++++++++++++++++++++++++++++++++++++ 51 files changed, 3428 insertions(+) create mode 100644 ChangeLog create mode 100644 Makefile create mode 100644 README create mode 100644 TODO create mode 100644 app/Makefile create mode 100644 app/README create mode 100644 app/acf-util/logon-controller.lua create mode 100644 app/acf-util/logon-html.lsp create mode 100644 app/acf-util/logon-model.lua create mode 100644 app/acf_www-controller.lua create mode 100644 app/dummy/bar-controller.lua create mode 100644 app/dummy/bar-html.lsp create mode 100644 app/dummy/bar.menu create mode 100644 app/foo-controller.lua create mode 100644 app/foo-html.lsp create mode 100644 app/foo.menu create mode 100644 app/menuhints.menu create mode 100644 app/template-html.lsp create mode 100644 app/welcome-controller.lua create mode 100644 app/welcome-html.lsp create mode 100644 app/welcome.menu create mode 100644 config.mk create mode 100644 lib/Makefile create mode 100644 lib/README create mode 100644 lib/ed.lua create mode 100644 lib/fs.lua create mode 100644 lib/html.lua create mode 100644 lib/join.lua create mode 100644 lib/log_view.lua create mode 100644 lib/menubuilder.lua create mode 100644 lib/service_controller.lua create mode 100644 lib/service_model.lua create mode 100644 lib/session.lua create mode 100644 lib/split.lua create mode 100755 lib/validator.lua create mode 100644 lib/web_elements.lua create mode 100644 www/Makefile create mode 100755 www/cgi-bin/acf create mode 100755 www/cgi-bin/mvc.lua create mode 100644 www/index.html create mode 100644 www/sample.html create mode 100644 www/static/alpine.gif create mode 100644 www/static/alpine.png create mode 100644 www/static/arctic-back.png create mode 100644 www/static/arctic-upleft.png create mode 100644 www/static/greypx.gif create mode 100644 www/static/selected.gif create mode 100644 www/static/submenu.css create mode 100644 www/static/unselected.gif create mode 100644 www/static/webconf-purple.css create mode 100644 www/static/webconf.css diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..87b06b7 --- /dev/null +++ b/ChangeLog @@ -0,0 +1,29 @@ +2006-09-25 + 0.1.3 + Renamed to acf (Alpine Configuration Framework), made new config directories + wc table renamed to cf (wc means watercloset, and although this code stinks, + it doesn't stink THAT bad) +2006-09-20 + 0.1.2 + The lua portions are integrated now. Other developers should be able to build + code from c/hostname.lua and m/hostname.lua +2006-09-14 + 0.1.1 + Clearsilver removed, haserll is used - Webconf on LUA! +2006-09-03 + 0.1.0 + Another attempt at simplifying... this one uses clearsilver. +2006-08-22 + 0.0.7 + Moved the preabmle/postamble stuff into an array that's executed, so that each + view doesn't have to do it. (DRY) + + Changed css to do dt/dd instead of label; This makes error messages for fields easier to + tag. + 0.0.5 + Added DESTDIR to the APPROOT root (N.Copa requested) +2006-08-21 + 0.0.3 version + create APPROOT and WEBROOT targets; moved www/app to www/cgi-bin (works with bb httpd) +2006-08-15 + 0.0.1 version released - webconf now has a makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..73e35f4 --- /dev/null +++ b/Makefile @@ -0,0 +1,53 @@ +APP_NAME=core +PACKAGE=acf-$(APP_NAME) +VERSION=2.0_alpha1 + +P=$(PACKAGE)-$(VERSION) +DISTDIR:=$(PWD)/$(P) +DISTPKG=$(P).tar.bz2 + +SUBDIRS=app lib www +EXTRA_DIST=ChangeLog Makefile README TODO + +DISTFILES=$(EXTRA_DIST) + +CP=cp +TAR=tar + +RECURSIVE_TARGETS=all-recursive install-recursive distdir-recursive \ + clean-recursive +phony+=$(RECURSIVE_TARGETS) + +export DISTDIR DESTDIR +$(RECURSIVE_TARGETS): + target=`echo $@ | sed 's/-recursive//'`;\ + for dir in $(SUBDIRS); do\ + ( cd $$dir && $(MAKE) $$target ) || exit 1;\ + done + +phony += all +all: all-recursive + +phony += clean +clean: clean-recursive + rm -rf $(DISTDIR) $(DISTPKG) + +phony += distdir +distdir: distdir-recursive $(DISTFILES) + for i in $(DISTFILES) ; do\ + dest="$(DISTDIR)/$$i";\ + mkdir -p `dirname $$dest` &&\ + $(CP) "$$i" "$$dest" || exit 1;\ + done + +phony += dist +dist: $(DISTPKG) + +$(DISTPKG): distdir $(DISTFILES) + $(TAR) -chjf $@ $(P) + rm -r $(DISTDIR) + +phony+=install +install: install-recursive + +.PHONY: $(phony) diff --git a/README b/README new file mode 100644 index 0000000..b5d8df5 --- /dev/null +++ b/README @@ -0,0 +1,11 @@ +Alpine Configuration Framework +(A work in progress.) + +You can checout from svn to another location and run "make checkpkg DESTDIR=/" +to make sure that all files get included in dist package. + +The config file should be in /etc/acf/acf.conf and contain: + +appdir=/var/lib/acf/app/ + +(or wherever your app dir is) diff --git a/TODO b/TODO new file mode 100644 index 0000000..0162583 --- /dev/null +++ b/TODO @@ -0,0 +1,8 @@ +Todo items + +* Menu controller +* Role Based Access Control +* Clean up the code - is m/c/v as simple as it can be? +* Work on a non-trivial webconf (openssl CA or interfaces file) to see how hard this is going to be +* Work at a slightly faster than glacial pace +* fix Makefiles so ncopa can create packages again diff --git a/app/Makefile b/app/Makefile new file mode 100644 index 0000000..4c12bea --- /dev/null +++ b/app/Makefile @@ -0,0 +1,49 @@ +include ../config.mk + +APP_DIST= acf-util/logon-controller.lua\ + acf-util/logon-html.lsp\ + acf-util/logon-model.lua\ + dummy/bar-controller.lua\ + dummy/bar-html.lsp\ + dummy/bar.menu\ + acf_www-controller.lua\ + foo-controller.lua\ + foo-html.lsp\ + foo.menu\ + menuhints.menu\ + template-html.lsp\ + welcome-controller.lua\ + welcome-html.lsp\ + welcome.menu\ + +EXTRA_DIST=README Makefile +DISTFILES=$(APP_DIST) $(EXTRA_DIST) + +install_dir=$(DESTDIR)/$(appdir) + +phony+=all +all: + +phony+=clean +clean: + +phony+=distdir +distdir: $(DISTFILES) + mkdir -p "$(DISTDIR)/app" + for i in $(DISTFILES); do\ + dest=`dirname "$(DISTDIR)/app/$$i"`;\ + mkdir -p "$$dest";\ + cp "$$i" "$$dest";\ + done + +phony+=install +install: + mkdir -p $(install_dir) + for i in $(APP_DIST); do\ + dest=`dirname "$(install_dir)/$$i"`;\ + mkdir -p "$$dest";\ + cp "$$i" "$$dest";\ + done + + +.PHONY: $(phony) diff --git a/app/README b/app/README new file mode 100644 index 0000000..83e69af --- /dev/null +++ b/app/README @@ -0,0 +1,48 @@ +Each directory has a collection of controllers, models and views +for a specific package. + + +How to make a new package +------------------------- + +Create a directory for your package with the package name. +In this dir you put your controller, model, menu files. + +Create a Makefile that describes the the following: + + * package name + + * version of package + + * what files should be installed on the running system + + * what additional files should be included in the distribution package + (tar.gz source package). The Makefile and acf-pkg.mk wil automatically be + added so only extra files like README and ChangeLog needs to be specified + here. + + * just include the acf-pkg.mk makefile which contains the rules to install + the package and build the dist package- + +If you have an acf-foo package it should look like this: + +PACAKGE = foo +VERSION = 0.2.0 +INSTALL_FILES = foo.controller.lua foo.model.lua +EXTRA_DIST = ChangeLog README +include acf-pkg.mk + + +Then you just need to create a link to ../acf-pkg.mk + + ln -s ../acf-pkg.mk acf-pkg.mk + +To build the distribution package you then run: + + make dist + +To install the files in the running system: + + make install + + diff --git a/app/acf-util/logon-controller.lua b/app/acf-util/logon-controller.lua new file mode 100644 index 0000000..8359c18 --- /dev/null +++ b/app/acf-util/logon-controller.lua @@ -0,0 +1,60 @@ +-- Logon / Logoff functions + +module (..., package.seeall) + +require ("session") + +mvc.on_load = function(self, parent) + -- If they specify an invalid action or try to run init, then redirect + -- to the read function. + if ( self.conf.action == nil or self.conf.action == "init" ) then + -- do what? + end + +end + + +logon = function(self) + + local username=cfe({ name="username" }) + local password=cfe({ name="password" }) + local logon=cfe({ name="Logon", type="submit"}) + local s = "" + + if self.clientdata.username and self.clientdata.password then + if self.model.logon(self, self.clientdata.username, self.clientdata.password) == false then + username.value = self.clientdata.username + if self.session.id then + username.errtxt = "You are already logged in. Logout first." + else + username.errtxt = "There was a problem logging in" + end + else + self.conf.controller = "" + self.conf.action = "" + self.conf.prefix = "" + self.conf.type = "redir" + error(self.conf) + end + end + -- If we reach this point, just give them the login page + return ( cfe ({type="form", + option={ script=ENV["SCRIPT_NAME"], + prefix=self.conf.prefix, + controller = self.conf.controller, + action = "logon" }, + value = { username, password, logon } })) +end + + +logout = function(self) + self.model.logout(self, session.id) + + + -- and raise an error to go to the homepage + self.conf.action = "" + self.conf.prefix = "" + self.conf.controller = "" + self.conf.type = "redir" + error(self.conf) +end diff --git a/app/acf-util/logon-html.lsp b/app/acf-util/logon-html.lsp new file mode 100644 index 0000000..cdac2bf --- /dev/null +++ b/app/acf-util/logon-html.lsp @@ -0,0 +1,19 @@ + +

Logon

+ +
" method="POST"> + + + + +
+ + + + + + +
+
diff --git a/app/acf-util/logon-model.lua b/app/acf-util/logon-model.lua new file mode 100644 index 0000000..dbd8522 --- /dev/null +++ b/app/acf-util/logon-model.lua @@ -0,0 +1,61 @@ +-- Logon / Logoff model functions + +module (..., package.seeall) + +local sess = require ("session") + +local pvt = {} + + +-- return a sessionid if username / password is valid, false +-- /etc/acf/passwd should be lines of userid:passwd:user name:role1[,role2[,role3]] +pvt.logon = function (self, id, passwd ) + -- if we already have sessionid... then you are already logged in + if self.session.id then + return false + end + + id = id or "" + passwd = passwd or "" + + -- open our hokey password file, + local f = io.open(self.conf.confdir .. "/passwd" ) + if f then + m = f:read("*all") .. "\n" + f:close() + + for l in string.gmatch(m, "(%C*)\n") do + local userid, password, username, roles = + string.match(l, "([^:]*):([^:]*):([^:]*):(.*)") + if userid == id and password == passwd then + self.session.id = sess.random_hash(512) + self.session.name = username + self.session.roles = roles + break + end + end + end + if self.session.id then + local x = require("session") + x.save_session(self.conf.sessiondir, self.session.id, self.session) + x=nil + return self.session.id + else + return false + end +end + +-- invalidate the session, or return false if the session wasn't valid +pvt.logout = function (self, sessionid) + + sess.invalidate_session ( self.conf.sessiondir, sessionid) + self.session = {} + +end + +------------------------------------------------------------------------- +-- Public Methods +------------------------------------------------------------------------- + +logon = pvt.logon +logout = pvt.logout diff --git a/app/acf_www-controller.lua b/app/acf_www-controller.lua new file mode 100644 index 0000000..742533a --- /dev/null +++ b/app/acf_www-controller.lua @@ -0,0 +1,202 @@ +--[[ Code for the Alpine Configuration WEB framework + Written for Alpine Configuration Framework (ACF) -- see www.alpinelinux.org + Copyright (C) 2007 Nathan Angelacos + Licensed under the terms of GPL2 + ]]-- +module(..., package.seeall) + +-- We use the parent exception handler in a last-case situation +local parent_exception_handler + +mvc = {} +mvc.on_load = function (self, parent) + + -- Make sure we have some kind of sane defaults for libdir and sessiondir + self.conf.libdir = self.conf.libdir or ( self.conf.appdir .. "/lib/" ) + self.conf.sessiondir = self.conf.sessiondir or "/tmp/" + self.conf.appuri = "http://" .. ENV.HTTP_HOST .. ENV.SCRIPT_NAME + self.conf.default_controller = "welcome" + self.clientdata = FORM + + + parent_exception_handler = parent.exception_handler + + -- this sets the package path for us and our children + package.path= self.conf.libdir .. "?.lua;" .. package.path + + self.session = {} + local x=require("session") + if FORM.sessionid then + local timestamp + timestamp , self.session = x.load_session(self.conf.sessiondir , FORM.sessionid) + self.session.id = FORM.sessionid + else + self.session.id = nil + end + logit ("acf_www-controller mvc.on_load activated" ) +end + +mvc.pre_exec = function () + logit ("acf_www-controller mvc.pre_exec activated") +end + +mvc.post_exec = function () + logit ("acf_www-controller mvc.post_exec activated") +end + + +-- look for a template +-- ctlr-action-view, then ctlr-view, then action-view, then view + +find_template = function ( appdir, prefix, controller, action, viewtype ) + local targets = { + appdir .. prefix .. "template-" .. controller .. "-" .. + action .. "-" .. viewtype .. ".lsp", + appdir .. prefix .. "template-" .. controller .. "-" .. + viewtype .. ".lsp", + appdir .. prefix .. "template-" .. action .. "-" .. + viewtype .. ".lsp", + appdir .. prefix .. "template-" .. viewtype .. ".lsp" + } + local file + for k,v in pairs(targets) do + file = io.open (v) + if file then + io.close (file) + return v + end + end + -- not found, so try one level higher + if prefix == "" then -- already at the top level - fail + return false + end + prefix = dirname (prefix) + return find_template ( appdir, prefix, controller, action, viewtype ) +end + + + +-- Overload the MVC's view resolver with our own + +view_resolver = function(self) + local file + local viewname + local viewtype = self.conf.viewtype or "html" + local names = { self.conf.appdir .. self.conf.prefix .. self.conf.controller .. + "-" .. self.conf.action .. "-" .. viewtype .. ".lsp", + self.conf.appdir .. self.conf.prefix .. self.conf.controller .. + "-" .. viewtype .. ".lsp" } + + + -- FIXME: MVC doesn't have a way to call a function after the controller is run + -- so we serialize the session in the view resolver. MVC probably should have + -- a postinit or postrun method... + if self.session.id then + local x = require("session") + x.save_session(self.conf.sessiondir, self.session.id, self.session) + x=nil + end + + -- search for template + local template = find_template ( self.conf.appdir, self.conf.prefix, + self.conf.controller, self.conf.action, "html") + + -- search for view + for i,filename in ipairs (names) do + file = io.open(filename) + if file then + file:close() + viewname = filename + break + end + end + + -- We have a template + if template then + -- *************************************************** + -- This is how to call another controller (APP or self + -- can be used... m will contain worker and model, + -- with conf, and other "missing" parts pointing back + -- to APP or self + -- *************************************************** + local m = self:new("alpine-baselayout/hostname") + local h = m.worker.read(m) + + local pageinfo = { viewfile = viewname, + controller = m.conf.controller, + -- ^^^ see.. m.conf doesnt exist - but it works + action = self.conf.action, + hostname = h.hostname.value, + prefix = self.conf.prefix, + script = self.conf.appuri + } + + -- Build the menu table + m=require("menubuilder") + local menu = m.get_menuitems(self.conf.appdir) + + -- A quick hack + submenu = {} + for k,v in pairs ( self.worker ) do + if type ( self.worker[k] ) == "function" then + table.insert (submenu, k) + end + end + + return function (viewtable) + local template = haserl.loadfile (template) + return template ( pageinfo, menu, submenu, viewtable, self.session ) + end + end + + -- No template, but have a view + if viewname then + return haserl.loadfile (viewname) + else + return function() end + end +end + +exception_handler = function (self, message ) + local html = require ("html") + if type(message) == "table" then + if message.type == "redir" then + io.write ("Status: 302 Moved\n") + io.write ("Location: " .. ENV["SCRIPT_NAME"] .. + message.prefix .. message.controller .. + "/" .. message.action .. + (message.extra or "" ) .. "\n") + if self.session.id then + io.write (html.cookie.set("sessionid", self.session.id)) + else + io.write (html.cookie.unset("sessionid")) + end + io.write ( "Content-Type: text/html\n\n" ) + elseif message.type == "dispatch" then + parent_exception_handler(self, message) + end + else + parent_exception_handler( self, message) + end +end + +-- create a Configuration Framework Entity (cfe) +-- returns a table with at least "value", "type", "option" and "errtxt" +cfe = function ( optiontable ) + optiontable = optiontable or {} + me = { value="", + type="text", + option="", + errtxt="", + name="" } + for key,value in pairs(optiontable) do + me[key] = value + end + return me +end + + +-- syslog something +logit = function ( ... ) + os.execute ( "logger \"" .. ... .. "\"" ) +end diff --git a/app/dummy/bar-controller.lua b/app/dummy/bar-controller.lua new file mode 100644 index 0000000..e0b9999 --- /dev/null +++ b/app/dummy/bar-controller.lua @@ -0,0 +1,35 @@ +-- A standin controller for testing + +module (..., package.seeall) + +-- Cause an http redirect to our "read" action +-- We use the self.conf table because it already has prefix,controller,etc +-- The redir code is defined in the application error handler (acf-controller) +local list_redir = function (self) + self.conf.action = "read" + self.conf.type = "redir" + error (self.conf) +end + + +--init ( init by definition is a public method) +init = function(self, parent) + -- If they specify an invalid action or try to run init, then redirect + -- to the read function. + if ( self.conf.action == nil or self.conf.action == "init" ) then + list_redir(self) + end + -- Make a function by the action name + self.worker[self.conf.action] = function () + return cfe ( {name=self.conf.action, value=self.conf.action} ) + + end + +end + + +read = function (self ) + return ( { } ) +end + + diff --git a/app/dummy/bar-html.lsp b/app/dummy/bar-html.lsp new file mode 100644 index 0000000..708e1ad --- /dev/null +++ b/app/dummy/bar-html.lsp @@ -0,0 +1,3 @@ + +

Action

+

This is a null controller for menu and authorization testing.

diff --git a/app/dummy/bar.menu b/app/dummy/bar.menu new file mode 100644 index 0000000..1fb42b8 --- /dev/null +++ b/app/dummy/bar.menu @@ -0,0 +1,3 @@ +# Prefix and controller are already known at this point +# Cat Group Tab Action +Test Bar_controller bar read diff --git a/app/foo-controller.lua b/app/foo-controller.lua new file mode 100644 index 0000000..f25cbaf --- /dev/null +++ b/app/foo-controller.lua @@ -0,0 +1,36 @@ +-- A standin controller for testing + +module (..., package.seeall) + +x = require ("session") + +-- Cause an http redirect to our "read" action +-- We use the self.conf table because it already has prefix,controller,etc +-- The redir code is defined in the application error handler (acf-controller) +local list_redir = function (self) + self.conf.action = "read" + self.conf.type = "redir" + error (self.conf) +end + + +mvc.on_load = function(self, parent) + -- If they specify an invalid action or try to run init, then redirect + -- to the read function. + if ( self.conf.action == nil or self.conf.action == "init" ) then + list_redir(self) + end + -- Make a function by the action name + self.worker[self.conf.action] = function () + return cfe ( {name=self.conf.action, value=self.conf.action} ) + + end + +end + + +read = function (self ) + return ( { } ) +end + + diff --git a/app/foo-html.lsp b/app/foo-html.lsp new file mode 100644 index 0000000..708e1ad --- /dev/null +++ b/app/foo-html.lsp @@ -0,0 +1,3 @@ + +

Action

+

This is a null controller for menu and authorization testing.

diff --git a/app/foo.menu b/app/foo.menu new file mode 100644 index 0000000..e9244f0 --- /dev/null +++ b/app/foo.menu @@ -0,0 +1,2 @@ +#cat Group tab action +Test foo_controller foo read diff --git a/app/menuhints.menu b/app/menuhints.menu new file mode 100644 index 0000000..5d36abb --- /dev/null +++ b/app/menuhints.menu @@ -0,0 +1,8 @@ +# This gives basic priorities to the groups in the menu +# That way, individual menu files don't have to say "80Setup" everywhere +# Note, however, that if ANY individual menu sets Setup to less than 80, then +# That number will override the defaults set here. +# (i.e. Lowest specifically set priority wins) +10Home +80Setup +100Test diff --git a/app/template-html.lsp b/app/template-html.lsp new file mode 100644 index 0000000..784bb4e --- /dev/null +++ b/app/template-html.lsp @@ -0,0 +1,99 @@ + +Status: 200 OK +Content-Type: text/html + + + + + + +<?= pageinfo.hostname .. " - " .. pageinfo.controller .. "->" .. pageinfo.action ?> + + + + + + + + + + + + + +
+ +
+ + + + + diff --git a/app/welcome-controller.lua b/app/welcome-controller.lua new file mode 100644 index 0000000..450952c --- /dev/null +++ b/app/welcome-controller.lua @@ -0,0 +1,27 @@ +-- A standin controller for testing + +module (..., package.seeall) + +-- Cause an http redirect to our "read" action +-- We use the self.conf table because it already has prefix,controller,etc +-- The redir code is defined in the application error handler (acf-controller) +local list_redir = function (self) + self.conf.action = "read" + self.conf.type = "redir" + error (self.conf) +end + +mvc = {} +mvc.on_load = function(self, parent) + -- It doesn't matter what action they choose - we only support read + if ( self.conf.action ~= "read") then + list_redir(self) + end +end + + +read = function (self ) + return ( { } ) +end + + diff --git a/app/welcome-html.lsp b/app/welcome-html.lsp new file mode 100644 index 0000000..7f205fe --- /dev/null +++ b/app/welcome-html.lsp @@ -0,0 +1,3 @@ + +

Alpine Configuration Framework

+

Welcome.

diff --git a/app/welcome.menu b/app/welcome.menu new file mode 100644 index 0000000..51c25f1 --- /dev/null +++ b/app/welcome.menu @@ -0,0 +1 @@ +10Home Welcome Welcome Read diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..40c9722 --- /dev/null +++ b/config.mk @@ -0,0 +1,10 @@ +prefix=/usr +datadir=${prefix}/share +sysconfdir=${prefix}/etc +localstatedir=${prefix}/var +acfdir=${datadir}/acf +wwwdir=${acfdir}/www +cgibindir=${wwwdir}/cgi-bin +appdir=${acfdir}/app +acflibdir=${acfdir}/lib +sessionsdir=${localstatedir}/lib/acf/sessions diff --git a/lib/Makefile b/lib/Makefile new file mode 100644 index 0000000..0c65ca1 --- /dev/null +++ b/lib/Makefile @@ -0,0 +1,47 @@ +include ../config.mk + +LIB_DIST=ed.lua\ + fs.lua\ + html.lua\ + join.lua\ + log_view.lua\ + menubuilder.lua\ + service_controller.lua\ + service_model.lua\ + session.lua\ + split.lua\ + validator.lua\ + web_elements.lua + +EXTRA_DIST=README Makefile +DISTFILES=$(LIB_DIST) $(EXTRA_DIST) + +install_dir=$(DESTDIR)/$(acflibdir) +dist_dir=$(DISTDIR)/$(notdir $(PWD)) + +phony+=all +all: + +phony+=clean +clean: + +phony+=distdir +distdir: $(DISTFILES) + mkdir -p "$(dist_dir)" + for i in $(DISTFILES); do\ + dest=`dirname "$(dist_dir)/$$i"`;\ + mkdir -p "$$dest";\ + cp "$$i" "$$dest";\ + done + +phony+=install +install: + mkdir -p $(install_dir) + for i in $(LIB_DIST); do\ + dest=`dirname "$(install_dir)/$$i"`;\ + mkdir -p "$$dest";\ + cp "$$i" "$$dest";\ + done + + +.PHONY: $(phony) diff --git a/lib/README b/lib/README new file mode 100644 index 0000000..cbc9798 --- /dev/null +++ b/lib/README @@ -0,0 +1,3 @@ +In the process of cleaning up... +shell libs are no longer used, just keeping them for historical reference at this point +lua libs are being worked on. diff --git a/lib/ed.lua b/lib/ed.lua new file mode 100644 index 0000000..2e86f08 --- /dev/null +++ b/lib/ed.lua @@ -0,0 +1,60 @@ +#!/usr/bin/lua + +require "object" + +-- ed object +Ed = Object:new{ + filename = nil, + lines = {} +} + + +-- openfile and read it to table +function Ed:open( filename, mode ) + local f = io.open( filename, mode ) + -- check that open was success + if f == nil then + return nil + end + + -- read the lines + for line in f:lines() do + table.insert( self.lines, line ) + end + f:close() + self.filename = filename + return self.lines +end + + +-- search and replace on lines that matches linematch +function Ed:find_gsub( linematch, search, replace, limit ) + local i, line + for i, line in ipairs( self.lines ) do + if string.find( line, linematch ) then + self.lines[i] = string.gsub( line, search, replace, limit ) + end + end +end + + +-- Write the table to file again +function Ed:flush( filename ) + local f = io.open( filename, "w" ) + if f == nil then + return false + end + + -- write each line to file + for i, line in ipairs( self.lines ) do + f:write(line .. "\n") -- test this! + end + f:close() + return true +end + +function Ed:insert( line ) + self.lines:insert( line ) +end + + diff --git a/lib/fs.lua b/lib/fs.lua new file mode 100644 index 0000000..d171074 --- /dev/null +++ b/lib/fs.lua @@ -0,0 +1,89 @@ +--[[ + module for generic filesystem funcs + + Copyright (c) Natanael Copa 2006 + +]]-- + +module (..., package.seeall) + +require ("lfs") + +-- generic wrapper funcs +function is_dir ( pathstr ) + return lfs.attributes ( pathstr, "mode" ) == "directory" +end + +function is_file ( pathstr ) + return lfs.attributes ( pathstr, "mode" ) == "file" +end + +-- Returns the contents of a file as a string +function read_file ( path ) + local file = io.open(path) + if ( file ) then + local f = file:read("*a") + file:close() + return f + else + return nil + end +end + +-- Returns an array with the contents of a file, +-- or nil and the error message +function read_file_as_array ( path ) + local file, error = io.open(path) + if ( file == nil ) then + return nil, error + end + local f = {} + for line in file:lines() do + table.insert ( f , line ) + end + file:close() + return f +end + + + + +-- write a string to a file +function write_file ( path, str ) + local file = io.open(path, "w") + if ( file ) then + file:write(str) + file:close() + end +end + + +-- iterator function for finding dir entries matching filespec (what) +-- starting at where, or currentdir if not specified. +-- Finds regexes, not fileglobs +function find ( what, where ) + -- returns an array of files under "where" that match what "f" + local function find_files_as_array ( f, where, t ) + where = where or lfs.currentdir() + f = f or ".*" + t = t or {} + for d in lfs.dir ( where ) do + if fs.is_dir ( where .. "/" .. d ) and (d ~= ".") and ( d ~= "..") then + find_files_as_array (f, where .. "/" .. d, t ) + end + if (string.match (d, "^" .. f .. "$" )) then + table.insert (t, ( string.gsub ( where .. "/" .. d, "/+", "/" ) ) ) + end + end + return (t) + end + + -- This is the iterator + local t = find_files_as_array ( what, where ) + local idx = 0 + return function () + idx = idx + 1 + return t[idx] + end +end + diff --git a/lib/html.lua b/lib/html.lua new file mode 100644 index 0000000..7e60c2c --- /dev/null +++ b/lib/html.lua @@ -0,0 +1,245 @@ +--[[ lowlevel html functions + Written for Alpine Configuration Framework (ACF) -- see www.alpinelinux.org + Copyright (C) 2007 Nathan Angelacos + Licensed under the terms of GPL2 +]]-- +module (..., package.seeall) + +--[[ Cookie functions ]]------------------------------------------------------ +cookie={} + +-- Set a cookie - returns a string suitable for setting a cookie +-- if the value is the boolean "false", then set the cookie to expire +cookie.set = function ( name, value, path ) + local expires = "" + if name == nil then + return ("") + end + if value == false then + expires = 'expires=Thu Jan 1 00:00:00 EST 1970' + value = "" + end + if path == nil then + path = "/" + end + return (string.format('Set-Cookie: %s=%s; path=%s; %s\n', tostring(name), + tostring(value), path, expires)) +end + + +-- wrapper function to clear a cookie +cookie.unset = function ( name, path) + return cookie.set (name, false, path) +end + + + +-- escape unsafe html characters +function html_escape (text ) + text = text or "" + local str = string.gsub (text, "&", "&" ) + str = string.gsub (str, "<", "<" ) + return string.gsub (str, ">", ">" ) +end + +-- return a name,value pair as a string. +nv_pair = function ( name, value) + if ( name == nil ) then + return ( value or "" ) + end + + if ( type(value) == "boolean" ) then + value = tostring(value) + end + + if ( value == nil ) then + return ( "" ) + else + return (string.format (' %s="%s" ', name , ( value or "" ) )) + end +end + + +--[[ + each of these functions take a table that has an associative array of + the values we might care about: + + value -- this is the value in the form element, or the selected element + name -- this is the name of the element + cols, rows + class + id + etc. +]]-- + +local generic_input = function ( field_type, v ) + if type(v.value) == "table" then + ret = {} + local vals = v.value + for n, val in ipairs(vals) do + v.value = val + table.insert(ret, generic_input(field_type, v)) + end + v.value = vals + return table.concat(ret) + end + if ( field_type == nil ) then + return nil + end + + local str = string.format ( '" ) +end + + +--[[ Form functions ]]------------------------------------------------------ +-- These expect something like a cfe to work (see acf_www-controller.lua) + +form = {} +form.text = function ( v ) + return generic_input ( "text", v ) +end + + +form.longtext = function ( v ) + local str = "" .. (v.value or "" ) .. "" ) +end + + +function form.passwd ( v ) + return generic_input ( "password", v ) +end + +function form.hidden ( v ) + return generic_input ( "hidden", v ) +end + + +function form.submit ( v ) + return generic_input ( "submit", v ) +end + + +function form.action (v) + return generic_input ("submit", v) +end + +function form.file ( v ) + return generic_input ( "file", v ) +end + +function form.image ( v ) + return generic_input ( "image", v ) +end + + +-- v.value is the selected item +-- v.option is an array of valid options +-- NOTE use of value and values (plural) +function form.select ( v ) + if ( v.name == nil ) then + return nil + end + local str = "" + -- now the options + for i, k in ipairs ( v.option ) do + local val = k + local txt = nil + if type(val) == "table" then + txt=val[1] + val=val[0] + end + str = str .. "" + end + str = str .. "" + return (str) +end + +function form.checkbox ( v ) + return generic_input ( "checkbox", v ) +end + + +-- NOTE: VALUE of a form is a table containing the form elements ... +function form.start ( v) + if ( v.action == nil ) then + return nil + end + + local method = v.method or "get" + return ( string.format ( + '
', + nv_pair ( "class", v.class ), + nv_pair ( "method", v.method), + nv_pair ( "action", v.action ) + ) ) +end + +function form.stop ( ) + return ("
") +end + +-- For "h1, h2, p," etc +-- WARNING - Text is printed verbatim - you may want to +-- wrap the text in html_escape +function entity (tag, text, class, id) + return ( string.format ( + "<%s%s%s>%s", + tag, + nv_pair ("class", class), + nv_pair("id", id), text , tag) + ) +end + + +function link ( v ) + if ( v.value == nil ) then + return nil + end + local str = nv_pair ( "href", v.value ) + for i,k in ipairs( { "class", "id" }) do + str = str .. nv_pair ( k, v[k] ) + end + + return ( "" .. (v.label or "" ) .. "" ) +end + diff --git a/lib/join.lua b/lib/join.lua new file mode 100644 index 0000000..e3621de --- /dev/null +++ b/lib/join.lua @@ -0,0 +1,25 @@ +--[[ + module for joining an array to a string +]]-- + +module (..., package.seeall) + + +-- This code comes from http://lua-users.org/wiki/SplitJoin +-- +-- Concat the contents of the parameter list, +-- -- separated by the string delimiter (just like in perl) +-- -- example: strjoin(", ", {"Anna", "Bob", "Charlie", "Dolores"}) +return function (delimiter, list) + local len = getn(list) + if len == 0 then + return "" + end + local string = list[1] + for i = 2, len do + string = string .. delimiter .. list[i] + end + return string +end + + diff --git a/lib/log_view.lua b/lib/log_view.lua new file mode 100644 index 0000000..f32400e --- /dev/null +++ b/lib/log_view.lua @@ -0,0 +1,56 @@ +require ("web_elements") + +local function fwrite(fmt, ...) + return io.write(string.format(fmt, ...)) +end + +local function footer(time) + fwrite("
\n

This request was processed in approximately %d seconds

\n
",time) +end + +header = [[ +content-type: text/html + + + + + + +Alpine log view + + + +]] + +-- + +print(header) +print("\n
") + +fwrite("

%s

",cf.hostinfo.alpine_hostname) + +fwrite("

%s

",cf.hostinfo.alpine_release) + +print("
") + +print (' + + + +
+

]]) +-- get the wc and view tables +-- walk the tree +web_elements.render_table ( view ) +print("

\n
") + +print(footer(cf.time)) +print("") + +-- /* vim: set filetype=lua : */ diff --git a/lib/menubuilder.lua b/lib/menubuilder.lua new file mode 100644 index 0000000..6def1fe --- /dev/null +++ b/lib/menubuilder.lua @@ -0,0 +1,150 @@ +--[[ parse through the *.menu tables and return a "menu" table + Written for Alpine Configuration Framework (ACF) -- see www.alpinelinux.org + Copyright (C) 2007 Nathan Angelacos + Licensed under the terms of GPL2 + ]]-- +module(..., package.seeall) + +-- returns a table of the "*.menu" tables +-- uses the system "find" command +-- startdir should be the app dir. +local get_candidates = function (startdir) + local t = {} + startdir = startdir .. "/" + local fh = io.popen('find ' .. startdir .. ' -name "*.menu"') + + local start = string.gsub(startdir, "/$", "") + for x in fh:lines() do + table.insert (t, (string.gsub(x, start, ""))) + end + + return t +end + + +-- internal function for table.sort +local t_compare = function (x,y,f) + for k,v in pairs(f) do + local a = x[v] + local b = y[v] + if tonumber(a) and tonumber(b) then + a=tonumber(a) + b=tonumber(b) + end + if a < b then return true end + if a > b then return false end + end + return false + end + + +-- returns a table of all the menu items found, sorted by priority +-- Table format: prefix controller cat group tab action +get_menuitems = function (startdir) + local t = {} + for k,v in pairs(get_candidates(startdir)) do + local prefix, controller = mvc.dirname(v), mvc.basename(v, ".menu") + -- open the thing, and parse the contents + local fh = io.open(startdir .. "/" .. v) + local prio = 10 + for x in fh:lines() do + if not string.match(x, "^#") then + local item = {} + for i in string.gmatch(x, "%S+") do + table.insert(item, i) + end + table.insert(t, { prefix=prefix, + controller=controller, + catprio="nan", + cat=item[1] or "", + groupprio="nan", + group=item[2] or "", + tabprio=tostring(prio), + tab=item[3] or "", + action=item[4] or "" }) + prio=prio+5 + end + end + fh:close() + end + -- Ok, we now have the raw menu table + -- now try to parse out numbers in front of any cat, group or tabs + for x in ipairs(t) do + local f = t[x] + if (string.match(f.cat, "^%d")) then + f.catprio, f.cat = string.match(f.cat, "(%d+)(.*)") + end + if (string.match(f.group, "^%d")) then + f.groupprio, f.group = string.match(f.group, "(%d+)(.*)") + end + if (string.match(f.tab, "^%d")) then + f.tabprio, f.tab = string.match(f.tab, "(%d+)(.*)") + end + end + + -- Convert underscores to spaces + for x in ipairs(t) do + t[x].cat = string.gsub(t[x].cat, "_", " ") + t[x].group = string.gsub(t[x].group, "_", " ") + t[x].tab = string.gsub(t[x].tab, "_", " ") + end + + -- Now alpha sort + table.sort(t, function(x,y) + return t_compare (x,y,{"cat", "catprio", "group", "groupprio", "tab", "tabprio"} ) + end) + + -- Fill in the priorities + local fill_prio = function (t, start, stop, col) + local prio = t[start][col] + if prio == "nan" then prio = "0" end + while start <= stop do + t[start][col] = prio + start = start + 1 + end + end + + +-- Fill in the priorities +-- Warning - UGLY code ahead. +-- Basic rules, for each cat and group, if the prio is nan, then set it +-- to the lowest value for that group or cat. + local k = 1 + while ( k <= table.maxn(t) ) do + local c = k + while ( c <= table.maxn(t) and t[c].cat == t[k].cat ) do + c=c+1 + end + c=c-1 -- back up one - we only want whats the same + fill_prio(t,k,c,"catprio") + -- from k,c is a mini table, do the same for groupprio + local g = k + while ( g <= c ) do + local h = g + while ( h <= c and t[h].group == t[g].group ) do + h=h+1 + end + h=h-1 --- back up one (again) + fill_prio(t,g,h,"groupprio") + g=h+1 + end + k = c + 1 + end + + -- Now priority sort + table.sort(t, function(x,y) + return t_compare (x,y,{"catprio", "cat", "groupprio", "group", "tabprio", "tab"} ) + end) + + + -- drop the priorities - they were internal + for k,v in ipairs(t) do + v.catprio = nil + v.groupprio = nil + v.tabprio = nil + end + + return t +end + + diff --git a/lib/service_controller.lua b/lib/service_controller.lua new file mode 100644 index 0000000..09595a4 --- /dev/null +++ b/lib/service_controller.lua @@ -0,0 +1,189 @@ +local function build_status(model, name) + return { + type = "form", + method = "post", + action = cf.uri .. "/chrun", + value = { + { + type = "formtext", + name = "status", + label = "Status: " .. name .. "", + value = model.status(name) + }, + { + type = "hidden", + name = "service", + value = name + }, + { + type = "submit", + name = "cmd", + label = "Running controls", + value = { "Start", "Stop", "Restart" } + }, + } + } +end + +local function build_note(model, name) + return { + type = "form", + method = "post", + action = cf.uri .. "/note_set", + value = { + { + type = "textarea", + name = "note", + label = "Notes", + cols = 80, + rows = 8, + value = model.get_note(name) + }, + { + type = "submit", + label = "Command", + value = "Save" + } + } + } +end + +local function mkerr(str) + return { + { + type = "formtext", + class = "error", + value = str + } + } +end + +local function mkresult(str) + return { + { + type = "formtext", + value = str + } + } +end + +local function chrun(model, name, ucmd) + local cmd = string.lower(ucmd) + --TODO chect that name is in get_service_names() + if cmd ~= "start" and cmd ~= "stop" and cmd ~= "restart" then + return mkerr("unknown command") + end + return mkresult(model.initd(name, cmd)) +end + +local function build_log_file(model, name) + return { + type = "group", + label = name, + text = "FILE " .. model.get_log_names()[name].path, + value = { + { + type = "log", + lines = model.get_log_producer(name) + } + } + } +end + +local function build_edit_file(model, name) + return { + type = "form", + method = "post", + action = cf.uri .. "/cfg_set", + value = { + { + type = "textarea", + name = "cfg", + label = name, + cols = 80, + rows = 12, + value = model.get_cfg(name) + }, + { + type = "hidden", + name = "name", + value = name + }, + { + type = "submit", + label = "Command", + value = "Save" + } + } + } + +end + +local function build_cfg(model) + local ret = {} + for name, whatever in pairs(model.get_cfg_names()) do + table.insert(ret, build_edit_file(model, name)) + end + return ret +end + +local function set_cfg(model) + for name, whatever in pairs(model.get_cfg_names()) do + if name == FORM.name then + model.set_cfg(name, FORM.cfg) + return + end + end + return mkerr("unknown config") +end + +local function build_log(model) + local ret = {} + for name, whatever in pairs(model.get_log_names()) do + table.insert(ret, build_log_file(model, name)) + end + return ret +end + +local function build_service(model) + local ret = {} + for name, whatever in pairs(model.get_service_names()) do + table.insert(ret, build_status(model, name)) + end + table.insert(ret, build_note(model)) + return ret +end + +function create_service_controller() + + local me = {} + + me.service = build_service + me.default = me.service + me.cfg = build_cfg + me.log = build_log + + function me.chrun(model) + return chrun(model, FORM.service, FORM.cmd) + end + + function me.note_set(model) + local ret = model.set_note(FORM.note) + if ret then + return ret + end + return me.service(model) + end + + + function me.cfg_set(model) + set_cfg(model) + return me.cfg(model) + end + + return me + +end + +-- /* vim: set filetype=lua shiftwidth=4: */ + diff --git a/lib/service_model.lua b/lib/service_model.lua new file mode 100644 index 0000000..c619d61 --- /dev/null +++ b/lib/service_model.lua @@ -0,0 +1,115 @@ +require("fs") + +function create_service_model(cfglist, loglist, servlist, notepath) + + local me = {} + + local function get_any_pid(pname) + for e in lfs.dir("/proc") do + if e == string.match(e, "^%d*$") then + for line in io.lines("/proc/" .. e .. "/status") do + tag, val = string.match(line, "^([^:]*):%s*(%S*)$"); + if tag == "Name" then + if val == pname then return e end + break + end + end + end + end + end + + local function get_cfg_path(name) + if not cfglist or not cfglist[name] then + return + end + return cfglist[name].path + end + + local function get_log_path(name) + if not loglist or not loglist[name] then + return + end + return loglist[name].path + end + + --[[ Public Functions go here ]]-- + + function me.status(name) + if not servlist or not servlist[name] then + return "unknown service" + end + local cmdname = servlist[name].cmdname + if get_any_pid(cmdname) == nil then + return "seems to be stopped" + else + return "seems to be running" + end + end + + function me.initd(name, action) + if not servlist or not servlist[name] then + return "unknown service" + end + local p = io.popen("/etc/init.d/" + .. servlist[name].initdname .. " " .. action, "r") + local ret = p:read("*a") + p:close() + return ret + end + + function me.get_note() + if not notepath or not fs.is_file(notepath) then return "" end + return fs.read_file(notepath) + end + + function me.set_note(value) + if notepath == nil then return end + fs.write_file(notepath, value) + end + + function me.get_cfg_names() + return cfglist + end + + function me.get_log_names() + return loglist + end + + function me.get_service_names() + return servlist + end + + function me.get_cfg(name) + local path = get_cfg_path(name) + if not path or not fs.is_file(path) then + return "" + end + return fs.read_file(path) + end + + function me.set_cfg(name, value) + local path = get_cfg_path(name) + if path then + fs.write_file(path, value) + end + end + +-- local function wrap_single(x) +-- return function(state, prev) +-- if not prev then +-- return x +-- end +-- end +-- end + + function me.get_log_producer(name) + local path = get_log_path(name) + if not path or not fs.is_file(path) then + return "cannot access " .. path + end + return io.lines(path) + end + + return me + +end diff --git a/lib/session.lua b/lib/session.lua new file mode 100644 index 0000000..f23f5a5 --- /dev/null +++ b/lib/session.lua @@ -0,0 +1,143 @@ +-- Session handling routines - written for acf +-- Copyright (C) 2007 N. Angelacos - GPL2 License + +module (..., package.seeall) + +local b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-" + +-- Return a sessionid of at least size bits length +random_hash = function (size) + local file = io.open("/dev/urandom") + local str = "" + if file == nil then return nil end + while (size > 0 ) do + local offset = (string.byte(file:read(1)) % 64) + 1 + str = str .. string.sub (b64, offset, offset) + size = size - 6 + end + return str +end + +hash_ip_addr = function (string) + local str = "" + for i in string.gmatch(string, "%d+") do + str = str .. string.format("%02x", i ) + end + return str +end + +ip_addr_from_hash = function (string) + local str = "" + for i in string.gmatch(string, "..") do + str = str .. string.format("%d", "0x" .. i) .. "." + end + return string.sub(str, 1, string.len(str)-1) +end + + +--[[ + These functions serialize a table, including nested tables. + The code based on code in PiL 2nd edition p113 +]]-- +local function basicSerialize (o) + if type(o) == "number" then + return tostring(o) + else + return string.format("%q", o) + end +end + + +function serialize (name, value, saved ) + local str = str or "" + saved = saved or {} + str = str .. name .. " = " + if type(value) == "number" or type(value) == "string" then + str = str .. basicSerialize (value) .. "\n" + elseif type(value) == "table" then + if saved[value] then + str = str .. saved[value] .. "\n" + else + saved[value] = name + str = str .. "{}\n" + for k,v in pairs(value) do + local fieldname = string.format("%s[%s]", name, basicSerialize(k)) + str = str .. serialize (fieldname, v, saved) + end + end + elseif type(value) == "boolean" then + str = str .. tostring(value) .. "\n" + else + str = str .. "nil\n" -- cannot save other types, so skip them + end + return str +end + +save_session = function( sessionpath, session, sessiontable) + local file = io.open(sessionpath .. "/" .. session , "w") + if file then + file:write ( "-- This is an ACF session table.\nlocal timestamp=" .. os.time() ) + file:write ( "\nlocal " ) + file:write ( serialize("s", sessiontable) ) + file:write ( "return timestamp, s\n") + file:close() + return true + else + return false + end +end + + +--- FIXME: This is really a generic "test if file exists" thing. + +-- Tests if a session is valid +-- Returns true if valid, false if not +session_is_valid = function (session) + local file = io.open(session) + if file then + file:close() + return true + else + return false + end +end + +-- Loads a session +-- Returns a timestamp (when the session data was saved) and the session table. +load_session = function ( sessionpath, session ) + -- session can only have b64 characters in it + session = string.gsub ( session, "[^" .. b64 .. "]", "") + if #session == 0 then + return nil, {} + end + session = sessionpath .. "/" .. session + if (session_is_valid(session)) then + local file = io.open(session) + return dofile(session) + else + return nil, {} + end +end + +-- unlinks a session +invalidate_session = function (sessionpath, session) + if type(session) ~= "string" then return nil end + local s = string.gsub (session, "[^" .. b64 .. "]", "") + if s ~= session then + return nil + end + session = sessionpath .. "/" .. s + os.remove (session) + return nil +end + + +expire_old_sessions = function ( sessiondir ) + file = io.popen("ls " .. sessiondir ) + x = file:read("*a") + file:close() + for a in string.gmatch(x, "[^%c]+") do + timestamp, foo = load_session ( sessiondir .. "/" .. a ) + print ( a .. " is " .. os.time() - timestamp .. " seconds old") + end +end diff --git a/lib/split.lua b/lib/split.lua new file mode 100644 index 0000000..f3e2e95 --- /dev/null +++ b/lib/split.lua @@ -0,0 +1,31 @@ +--[[ + module for splitting a string to an array +]]-- + +module (..., package.seeall) + +-- This code comes from http://lua-users.org/wiki/SplitJoin + +-- Split text into a list consisting of the strings in text, +-- separated by strings matching delimiter (which may be a pattern). +-- example: strsplit(",%s*", "Anna, Bob, Charlie,Dolores") +return function (delimiter, text) + local list = {} + local pos = 1 + -- this would result in endless loops + if string.find("", delimiter, 1) then + error("delimiter matches empty string!") + end + while 1 do + local first, last = string.find(text, delimiter, pos) + if first then -- found? + table.insert(list, string.sub(text, pos, first-1)) + pos = last+1 + else + table.insert(list, string.sub(text, pos)) + break + end + end + return list +end + diff --git a/lib/validator.lua b/lib/validator.lua new file mode 100755 index 0000000..951d716 --- /dev/null +++ b/lib/validator.lua @@ -0,0 +1,156 @@ +-------------------------------------------------- +-- Validation Functions for Alpine Linux' Webconf +-------------------------------------------------- + +-- setup the 'language' table +lang = {} +lang.English = 1 +lang.German = 2 +lang.French = 3 +lang.Current = lang.English + +-- setup the 'validator' tables +validator = {} +validator.msg = {} +validator.msg.err = {} + +-- setup error messages +validator.msg.err.Success = {} +validator.msg.err.Success[lang.English] = "Ok." +validator.msg.err.Success[lang.German] = "Ok." +validator.msg.err.InvalidChars = {} +validator.msg.err.InvalidChars[lang.English] = "Invalid characters!" +validator.msg.err.InvalidChars[lang.German] = "Ungültige Zeichen!" +validator.msg.err.InvalidLength = {} +validator.msg.err.InvalidLength[lang.English] = "Invalid length!" +validator.msg.err.InvalidLength[lang.German] = "Ungültige Länge!" +validator.msg.err.InvalidFormat = {} +validator.msg.err.InvalidFormat[lang.English] = "Invalid format!" +validator.msg.err.InvalidFormat[lang.German] = "Ungültiges Format!" +validator.msg.err.InvalidValue = {} +validator.msg.err.InvalidValue[lang.English] = "Invalid Value!" +validator.msg.err.InvalidValue[lang.German] = "Ungültiger Wert!" +validator.msg.err.OutOfRange = {} +validator.msg.err.OutOfRange[lang.English] = "Value out of range!" +validator.msg.err.OutOfRange[lang.German] = "Wert ausserhalb des Bereichs!" + +-- +-- This function validates an ipv4 address. +-- On success it returns 1 otherwise a negative value +-- +function validator.is_ipv4(ipv4) + local retval = false; + local nums = { "", "", "", ""}; + local iplen = string.len(ipv4); + + -- check the ipv4's length + if (iplen < 7 or iplen > 15) then + return false, validator.msg.err.InvalidLength[lang.Current] + end + + -- NC: Split the string into an array. separate with '.' (dots) + -- ^ beginning of string + -- () capture + -- \. litteral '.' The \ neutralizes the . character class. + -- %d+ one or more digits + -- $ end of string + nums = { ipv4:match ("^(%d+)\.(%d+)\.(%d+)\.(%d+)$" ) } + + -- check if all nums are filled + if ( nums[1] == nil or + nums[2] == nil or + nums[3] == nil or + nums[4] == nil) then + -- we have an empty number + return false, validator.msg.err.InvalidFormat[lang.Current] + end + + -- too big? + if (tonumber(nums[1]) > 255 or + tonumber(nums[2]) > 255 or + tonumber(nums[3]) > 255 or + tonumber(nums[4]) > 255) then + -- at least one number is too big + return false, validator.msg.err.InvalidValue[lang.Current] + end + + return true, validator.msg.err.Success[lang.Current] +end + +function validator.is_mac(mac) + + local tmpmac = string.upper(mac) + + if (string.len(tmpmac) ~= 17) then + return false, validator.msg.err.InvalidLength[lang.Current] + end + + -- check for valid characters + local step = 1; + while (step <= 17) do + if (string.sub(tmpmac, step, step) ~= ":") and + (string.sub(tmpmac, step, step) < "0" or string.sub(tmpmac, step, step) > "9") and + (string.sub(tmpmac, step, step) < "A" or string.sub(tmpmac, step, step) > "F") then + -- we have found an invalid character! + return false, validator.msg.err.InvalidChars[lang.Current] + end + step = step + 1; + end + + -- check for valid colon positions + if (string.sub(tmpmac, 3, 3) ~= ":" or + string.sub(tmpmac, 6, 6) ~= ":" or + string.sub(tmpmac, 9, 9) ~= ":" or + string.sub(tmpmac, 12, 12) ~= ":" or + string.sub(tmpmac, 15, 15) ~= ":") then + return false, validator.msg.err.InvalidFormat[lang.Current] + end + + -- check for valid non colon positions + step = 1; + while (step <= 17) do + if ((string.sub(tmpmac, step, step) == ":") and + ((step ~= 3) and (step ~= 6) and (step ~= 9) and (step ~= 12) and + (step ~= 15))) then + return false, validator.msg.err.InvalidValue[lang.Current] + end + step = step + 1; + end + + return true, validator.msg.err.Success[lang.Current] +end + +-- +-- This function checks if the given input +-- consists of number-chars between 0..9 only +-- and eventually a leading '-' +-- +function validator.is_integer(numstr) + -- ^ beginning of string + -- -? one or zero ot the char '-' + -- %d+ one or more digits + -- $ end of string + return string.find(numstr, "^-?%d+$") ~= nil +end + + +-- +-- This function checks if the given input +-- consists of number-chars between 0..9 only +-- and if it is within a given range. +-- +function validator.is_integer_in_range(numstr, min, max) + return validator.is_integer(numstr) + and numstr >= min + and numstr <= max + +end + +-- +-- This function checks if the given number is an integer +-- and wheter it is between 1 .. 65535 +-- +function validator.is_port(numstr) + return validator.is_integer_in_range(numstr, 1, 65535) +end + diff --git a/lib/web_elements.lua b/lib/web_elements.lua new file mode 100644 index 0000000..e676205 --- /dev/null +++ b/lib/web_elements.lua @@ -0,0 +1,156 @@ +--[[ + middle level functions for the webconf view templates. Part of + acf + + Copyright (C) 2006 N. Angelacos Licensed under terms of GPL2 +]]-- + +module ( ..., package.seeall ) + +require ("html") + +-- This is the main function that walks a table formatted for a template +-- (This is the magic in generic) +function render_table ( element, level) + local level = level or 1 + + if (type(element) ~= "table" ) then + return nil + end + + for k,v in pairs (element) do + if ( v.type ~= nil ) then + if ( v.type == "group" ) then + print ( html.entity ( ( "h" .. tostring(level) ), v.label, v.class, v.id ) ) + print ( html.entity ( "p" , v.text, v.class ) ) + render_table ( v.value, level + 1 ) + elseif ( v.type == "label" ) then + print ( html.entity ( "h" .. level , v.value, v.class, v.id ) ) + if ( v.text ~= nil ) then + print ( html.entity ( "p", v.text, v.class )) + end + elseif ( v.type == "html" ) then + print (v.value) + elseif ( v.type == "log" ) then + print("
")
+				if type(v.lines) == "function" then
+					for line in v.lines do
+						print(line)
+					end
+				elseif v.lines then
+					print(v.lines)
+				end
+				print("
") + elseif ( v.type == "link" ) then + print (html.link ( v ) ) + elseif ( v.type == "form" ) then + print ( html.form.start ( v ) ) + print ("
") + render_table ( v.value, level + 1 ) + print ("
") + print ( html.form.stop () ) + elseif type(html.form[v.type]) ~= "nil" then + if v.type == "hidden" then + v.noitem = true + end + if not v.noitem then + print ( string.format ("
%s
", ( v.label or "" ))) + end + print ( html.form[v.type] ( v ) ) + if not v.noitem then + print ("
") + end + end + end + end +end + + +-- This function prints the main menu, with the given prefix/controller "selected" +-- returns the group, category, subcat that is selected +function render_mainmenu ( menu, prefix, controller, action ) + -- prefix+controller defines which menu is "selected" + local megroup = nil + local mecat = nil + local mesubcat = nil + local liston = nil + local group = "" + local cat = "" + + -- find the current group/cat/subcat + for i=1,table.maxn(menu) do + if (menu[i].prefix == prefix) and ( menu[i].controller == controller ) then + megroup = menu[i].group + mecat = menu[i].cat + if ( menu[i].action == action ) then + mesubcat = menu[i].subcat + elseif ( menu[i].action == "*" ) and ( mesubcat == nil ) then + mesubcat = menu[i].subcat + end + end + end + + -- render the mainmenu + local thisgroup = "" + local thiscat = "" + for i=1,table.maxn(menu),1 do + if menu[i].group ~= thisgroup then + thisgroup = menu[i].group + if ( liston ) then io.write ("") end + print ( html.entity ( "h3", menu[i].group ) ) + io.write("
    ") + liston = true + thicat = nil + end + if menu[i].cat ~= thiscat then + thiscat = menu[i].cat + if (thiscat == mecat ) then + print ( html.entity ("li", html.html_escape(thiscat), nil, "selected")) + else + print (html.link ( { value= ENV.SCRIPT_NAME .. menu[i].uri , + label = html.entity ("li", html.html_escape(thiscat)) } ) ) + end + end + end + io.write ("
") + return megroup, mecat, mesubcat +end + + + +-- This function prints the tabs for the submenu, with the given submenu "selected" +function render_submenu ( menu, group, cat, subcat ) + cat = cat or "" + group = group or "" + local this_subcat = nil + local foo = group .. " > " .. cat + if (foo ~= " > " ) then + print ( html.entity ( "h2", html.html_escape( group .. " > " .. cat ) )) + end + + + -- print (string.format ("%s - %s - %s", group, cat , (subcat or ""))) + + io.write ("
    ") + for i=1, table.maxn(menu),1 do + if ( group == menu[i].group ) and ( cat == menu[i].cat ) then + -- If a subcat was not selected, make the first one the default + if ( subcat == nil ) then + subcat = menu[i].subcat + end + + if ( menu[i].subcat ~= this_subcat ) then + this_subcat = menu[i].subcat + if ( menu[i].subcat == subcat ) then + print ( html.entity ("li", menu[i].subcat, nil, "selected")) + else + io.write ("
  • ") + io.write ( html.link ( { value= ENV.SCRIPT_NAME .. menu[i].uri , + label =menu[i].subcat } ) ) + print ("
  • ") + end + end + end + end + io.write ("
") +end diff --git a/www/Makefile b/www/Makefile new file mode 100644 index 0000000..e208bdf --- /dev/null +++ b/www/Makefile @@ -0,0 +1,50 @@ +include ../config.mk + +WWW_DIST=cgi-bin/acf\ + cgi-bin/mvc.lua\ + static/alpine.gif\ + static/alpine.png\ + static/arctic-back.png\ + static/arctic-upleft.png\ + static/greypx.gif\ + static/selected.gif\ + static/submenu.css\ + static/unselected.gif\ + static/webconf-purple.css\ + static/webconf.css\ + index.html\ + sample.html + +EXTRA_DIST=Makefile +DISTFILES=$(WWW_DIST) $(EXTRA_DIST) + +install_dir=$(DESTDIR)/$(wwwdir) +dist_dir=$(DISTDIR)/$(notdir $(PWD)) + +phony+=all +all: + +phony+=clean +clean: + +phony+=distdir +distdir: $(DISTFILES) + mkdir -p "$(dist_dir)" + for i in $(DISTFILES); do\ + dest=`dirname "$(dist_dir)/$$i"`;\ + mkdir -p "$$dest";\ + cp "$$i" "$$dest";\ + done + +phony+=install +install: + mkdir -p $(install_dir) + for i in $(WWW_DIST); do\ + dest=`dirname "$(install_dir)/$$i"`;\ + mkdir -p "$$dest";\ + cp "$$i" "$$dest";\ + done + chmod +x $(install_dir)/cgi-bin/acf + + +.PHONY: $(phony) diff --git a/www/cgi-bin/acf b/www/cgi-bin/acf new file mode 100755 index 0000000..b52f9e7 --- /dev/null +++ b/www/cgi-bin/acf @@ -0,0 +1,19 @@ +#!/usr/bin/haserl --shell=lua -a + diff --git a/www/cgi-bin/mvc.lua b/www/cgi-bin/mvc.lua new file mode 100755 index 0000000..b677d3b --- /dev/null +++ b/www/cgi-bin/mvc.lua @@ -0,0 +1,278 @@ +--[[ Basic MVC framework + Written for Alpine Configuration Framework (ACF) -- see www.alpinelinux.org + Copyright (C) 2007 Nathan Angelacos + Licensed under the terms of GPL2 + ]]-- +module(..., package.seeall) + +-- the constructor +--[[ Builds a new MVC object. If "module" is given, then tries to load + self.conf.appdir .. module "-controller.lua" in c.worker and + self.conf.appdir .. module "-model.lua" in c.model + + The returned .conf table is guaranteed to have the following + appdir - where the application lives + confdir - where the configuration file is + sessiondir - where session data and other temporary stuff goes + appname - the name of the application + ]] + +new = function (self, modname) + local c = {} + c.worker = {} + c.model = {} + + -- make defaults if the parent doesn't have them + if self.conf == nil then + c.conf = { appdir = "", confdir = "", tempdir = "", appname = "" } + end + + -- If no clientdata, then clientdata is a null table + if self.clientdata == nil then + c.clientdata = {} + end + + -- If we don't have an application name, use the modname + if (self.conf == nil ) or (self.conf.appname == nil) then + c.conf.appname = modname + end + + -- load the module code here + if (modname) then + c.worker = self:soft_require( modname .. "-controller") or {} + c.model = self:soft_require( modname .. "-model" ) or {} + end + + -- The magic that makes all the metatables point in the correct + -- direction. c.model -> c.worker -> parent -> parent.worker -> + -- grandparent -> grandparent -> worker (and so on) + + -- The model looks in worker for missing + setmetatable (c.model, c.model ) + c.model.__index = c.worker + + -- the worker looks in the main table for missing + setmetatable (c.worker, c.worker) + c.worker.__index = c + + -- the table looks in the parent worker for missing + setmetatable (c, c) + + -- ensure an "mvc" table exists, even if empty + if (type(rawget(c.worker, "mvc")) ~= "table") then + c.worker.mvc = {} + end + + setmetatable (c.worker.mvc, c.worker.mvc) + -- If creating a new parent container, then + -- we are the top of the chain. + if (modname) then + c.__index = self.worker + c.worker.mvc.__index = self.worker.mvc + else + c.__index = self + c.worker.mvc.__index = self.mvc + end + + + -- run the worker on_load code + if type(rawget(c.worker.mvc, "on_load")) == "function" then + c.worker.mvc.on_load(c, self) + c.worker.mvc.on_load = nil + end + + return c +end + +-- This is a sample front controller/dispatch. +dispatch = function (self) + local controller + local success, err = xpcall ( function () + + self:parse_path_info(ENV["PATH_INFO"]) + + -- If they didn't provide a controller, and a default was specified + -- use it + if self.conf.controller == "" and self.conf.default_controller then + self.conf.controller = self.conf.default_controller + end + + controller = self:new(self.conf.prefix .. self.conf.controller) + + local action = controller.conf.action + + -- Because of the inheritance, normally the + -- controller.worker.action will flow up, so that EVERY + -- worker has an "exception_handler" action. We use rawget to + -- make sure that only controller defined actions are used. + -- If the controller or action are missing, raise an error + if ( type(rawget(controller.worker, action)) ~= "function") then + self.conf.type = "dispatch" + error (self.conf) + end + + -- run the pre_exec code +--- if type(rawget(controller.worker.mvc, "pre_exec")) == "function" then + if type(controller.worker.mvc.pre_exec) == "function" then + controller.worker.mvc.pre_exec ( controller ) + end + + -- run the action + local viewtable = controller.worker[action](controller) + + + -- run the post_exec code + if type(controller.worker.mvc.post_exec) == "function" then + -- if type(controller.worker.mvc, "post_exec") == "function" then + controller.worker.mvc.post_exec ( controller ) + end + + + local viewfunc = controller:view_resolver(viewtable) + + viewfunc (viewtable) + end, + self:soft_traceback(message) + ) + + if not success then + local handler + if controller then + handler = controller.worker or controller + end + handler = handler or self.worker or mvc + handler:exception_handler(err) + end +end + + +-- Tries to see if name exists in the self.conf.appdir, and if so, it loads it. +-- otherwise, returns nil, but no error +soft_require = function (self, name ) + local filename, file + filename = self.conf.appdir .. name .. ".lua" + file = io.open(filename) + if file then + file:close() + local PATH=package.path + -- FIXME - this should really try to open the lua file, and if it doesnt exist- + -- then silently fail -- This version allows things from /usr/local/lua/5.1 to + -- be loaded - but that's because a module might require another module + package.path = self.conf.appdir .. "?" .. ".lua;" .. package.path + local t = require(name) + package.path = PATH + return t + end + return nil +end + +-- see man basename.1 +basename = function (string, suffix) + string = string or "" + local basename = string.gsub (string, "[^/]*/", "") + if suffix then + basename = string.gsub ( basename, suffix, "" ) + end + return basename +end + +-- see man dirname.1 +dirname = function ( string) + string = string or "" + -- strip trailing / first + string = string.gsub (string, "/$", "") + local basename = basename ( string) + string = string.sub(string, 1, #string - #basename - 1) + return(string) +end + +-- look in various places for a config file, and store it in self.conf +read_config = function( self, appname ) + appname = appname or self.conf.appname + self.conf.appname = self.conf.appname or appname + + local confs = { (ENV["HOME"] or ENV["PWD"] or "") .. "/." .. appname .. "/" .. appname .. ".conf", + ( ENV["HOME"] or ENV["PWD"] or "") .. "/" .. appname .. ".conf", + -- dirname(ENV["SCRIPT_FILENAME"] or "") .. appname .. ".conf", + ENV["ROOT"] or "" .. "/etc/" .. appname .. "/" .. appname .. ".conf", + ENV["ROOT"] or "" .. "/etc/" .. appname .. ".conf" + } + for i, filename in ipairs (confs) do + local file = io.open (filename) + if (file) then + self.conf.confdir = dirname(filename) .. "/" + for line in file:lines() do + key, value = string.match(line, "([^[=]*)=[ \t]*(.*)") + if key then -- ugly way of finding blank spots between key and = + repeat + local space = string.find ( key, "%s", -1) + if space then key=string.sub(key,1,space-1) end + until space == nil + self.conf[key] = value + end + end + file:close() + break + end + end +end + +-- parse a "URI" like string into a prefix, controller and action +-- store them (or blanks) in self.conf +parse_path_info = function( self, string ) + string = string or "" + -- If it ends in a /, then add another to force + -- a blank action (the user gave a controller without action) + if string.match (string, "[^/]/$" ) then + string = string .. "/" + end + self.conf.action = basename(string) + local temp = dirname(string) + self.conf.controller = basename(temp) + self.conf.prefix = dirname(temp) .. "/" + return self.conf.prefix, self.conf.controller, self.conf.action + +end + +-- The View resolver of last resort. +view_resolver = function(self) + return function() + if ENV["PATH_INFO"] then + io.write ("Content-type: text/plain\n\n") + end + io.write ("Your controller and application did not specify a view resolver.\n") + io.write ("The MVC framework has no view available. sorry.\n") + return + end +end + +-- Generates a debug.traceback if called with no arguments +soft_traceback = function (self, message ) + if message then + return message + else + return debug.traceback + end +end + +-- The exception hander of last resort +exception_handler = function (self, message ) + if ENV["PATH_INFO"] then + print ("Content-Type: text/html\n\n
")
+	end
+	print ("The following unhandled application error occured:\n\n")
+
+	if (type(message) == "table" ) then
+		if (message.type == "dispatch") then
+		print ('controller: "' .. message.controller .. '" does not have a "' .. 
+			message.action .. '" action.')
+		else
+		print ("An error of type: '" .. (tostring(message.type) or "nil") .. "' was raised." )
+		end
+	else
+		print (tostring(message))
+	end
+	if ENV["PATH_INFO"] then
+		print ("
") + end +end diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..8db9a9d --- /dev/null +++ b/www/index.html @@ -0,0 +1,12 @@ + + + + + + + + + + + diff --git a/www/sample.html b/www/sample.html new file mode 100644 index 0000000..665334d --- /dev/null +++ b/www/sample.html @@ -0,0 +1,102 @@ + + + + +Alpine + + + + + + + + + + + + + + + + + + +
+

Errors

+

This is error text. As if you didn't know that. Just check the errors below.

+ +

This is a page with a link in it.

+ +

Networking - Status

+

This is the networking page. This is a very long paragraph in that page, talking about +all the good things on this page. This is not the real page, its just a standin page for +the page you want. Evenutally, you'll see the page you want. But not quite yet.

+ +

Status

+

The service is running

+
+
+
bozo
+
This is error text
+ +
foo
+ +
+ +
Command
+
+ + + +
+ +
Textbox
+
+
+ + +
A Tag with no form element
+
This is just some text
+ +
+
+ +

Configure

+

Bring Service Down

+

Bring Service Up

+
+ + + + diff --git a/www/static/alpine.gif b/www/static/alpine.gif new file mode 100644 index 0000000..2605afe Binary files /dev/null and b/www/static/alpine.gif differ diff --git a/www/static/alpine.png b/www/static/alpine.png new file mode 100644 index 0000000..11802e0 Binary files /dev/null and b/www/static/alpine.png differ diff --git a/www/static/arctic-back.png b/www/static/arctic-back.png new file mode 100644 index 0000000..0719f28 Binary files /dev/null and b/www/static/arctic-back.png differ diff --git a/www/static/arctic-upleft.png b/www/static/arctic-upleft.png new file mode 100644 index 0000000..9b6c926 Binary files /dev/null and b/www/static/arctic-upleft.png differ diff --git a/www/static/greypx.gif b/www/static/greypx.gif new file mode 100644 index 0000000..b13cb90 Binary files /dev/null and b/www/static/greypx.gif differ diff --git a/www/static/selected.gif b/www/static/selected.gif new file mode 100644 index 0000000..b2c2951 Binary files /dev/null and b/www/static/selected.gif differ diff --git a/www/static/submenu.css b/www/static/submenu.css new file mode 100644 index 0000000..b759020 --- /dev/null +++ b/www/static/submenu.css @@ -0,0 +1,52 @@ +/*---------------------------------------------------------------------------------- + * Submenu ( Horizontal Tabs) + *---------------------------------------------------------------------------------- + */ +#submenu { + width: 75%; + font-size: 90%; +// font-weight: Bold; +// display: inline; + float: right; + margin: 0em 2em 0em 0em; + background: #fff url(/static/greypx.gif) repeat-x bottom left; + } + +#submenu p { + display: inline; + color: #22c; + font-size: 161%; + font-weight: light; + margin: 2em 0em -2em 0em; + } + +#submenu ul { + list-style: none; + display: inline; + margin: 0em 1em 0em 1em; + padding: 0em 3em .6em 2em; + float: right; + } + +#submenu li { + display: inline; + border: 1px solid #ccc; + padding: .5em 1em .5em 1em; + margin: 0em 0em 0em 0em; + border-bottom: none; + background: #f8f8f8; + background: transparent url("/static/unselected.gif") repeat-x top left; + } + +#submenu #selected { + // background: #fff url("/static/selected.gif") repeat-x top left; + background: #fff; + } + +#submenu a { + } + +#submenu a:hover { + color: #c22; + } + diff --git a/www/static/unselected.gif b/www/static/unselected.gif new file mode 100644 index 0000000..f26d430 Binary files /dev/null and b/www/static/unselected.gif differ diff --git a/www/static/webconf-purple.css b/www/static/webconf-purple.css new file mode 100644 index 0000000..57538cd --- /dev/null +++ b/www/static/webconf-purple.css @@ -0,0 +1,313 @@ +/* webconf.css - For Alpine webconf + */ + +/* Default definitions */ +body { + background: url(arctic-back.png) repeat-y 20% 0; + // background-color: #DAE0EE; + font-family: Geneva, Arial, Helvetica, San-Serif; + margin: 0em; + padding: 0em; + // padding: 1em 0em 0em 1em; + font-size: 80%; + } + +p { + margin: 0em 0em 0em 0em; + } + +a { + text-decoration: none; + color: #333; + } + +a:link { + } + +a:visited { + } + +a:hover { + // font-weight: bold; + // text-decoration: underline; + } + +a:active { + } +/*---------------------------------------------------------------------------------- + * Header + *---------------------------------------------------------------------------------- + */ +#head { + padding: 1em 1em 1em 0em; + background: #dde url(alpine.png) no-repeat 50% 0; + color: #333; + // margin: -1em 0em 0em 0em; + padding: 1em; + } + +#head h1 { + font-size: 300%; + color: #226; + font-family: Palatino, Times-Roman, Serif; + font-weight: bold; + margin: 0em 0em 0em 0em; + //float: left; + width: 49%; + } + +#head h2 { + margin: 0em 0em 0em 0em; + font-size: 100%; + font-weight: light; + float: right; + width: 30%; + } + +#head h3 { + float: right; + font-size: 90%; + color: #222; + } + +#head p { + // border: 1px inset #ccc; + // border-top: 1px solid #ccc; + // border-bottom: 1px solid #ccc; + } + +/*---------------------------------------------------------------------------------- + * Left (Main) Menu + *---------------------------------------------------------------------------------- + */ +#mainmenu { + display: block; + background-color: #dde; + width: 20%; + float: left; + margin: 2em 0em 0em 0em; + padding: 0em -1em 0em 1em; + } + +#mainmenu p { + background-color: #448; + color: #fc4; + padding: 0em 0em 0em 1em; + font-size: 110%; + font-weight: bold; + // margin: 0em 0em 0em 0em; + } + +#mainmenu ul { + padding: 0em 0em 0em 2em; + margin: 0; + list-style-type: none; + } +#mainmenu li { + background-color: inherit; + } + +#mainmenu a:hover { + display: block; + background-color: #eec; + } + +#mainmenu #selected { + // border: 1px solid #666; + //border-right: 1px solid #ffd; + background-color: #ffe; + } + + +/*---------------------------------------------------------------------------------- + * Submenu ( Horizontal Tabs) + *---------------------------------------------------------------------------------- + */ +#submenu { + // display: none; + width: 80%; + // clear: both; + display: inline; + float: right; + margin: 0em 0em 0em 0em; + background-color: #dde; + } + +#submenu p { + display: none; + } + +#submenu ul { + float: left; + list-style: none; + display: inline; + margin: 0 1em 0 1em; + padding: 0 1em 0 1em; + } + +#submenu li { + display: inline; + background-color: #ccd; + border: 1px solid #666; + border-bottom: none; + } + +#submenu #selected { + border: 1px solid #666; + background: #ffe; + border-bottom: none; + // 2px solid #ffd; + // font-weight: bold; + } + +#submenu a { + padding: .1em .2em; + } + +#submenu a:hover { + display: inline; + background: #eec; + padding: 0em .2em; + } + +/*---------------------------------------------------------------------------------- + * Content + *---------------------------------------------------------------------------------- + */ +#content { + float:left; + width: 70%; + color: #333; + // background-color: #DAE0EE; + background: url(arctic-upleft.png) no-repeat; + padding: 2em 0em 3em 3em; + } + +#content h1 { + font-size: 120%; + // width: 100%; + background-color: #eec; + border-bottom: 2px solid #aa9; + margin: 1em 0em 1em 0em; + clear: both; + } + +#content h2 { + font-size: 100%; + // width: 100%; + background-color: #eec; + border-bottom: 1px solid #aa9; + // margin: 1em 1em 0em 1em; + clear: both; + } + +#content p { + // margin: 1em 1em 0em 1em; + clear: both; + padding: .5em 0em .5em 0em; + } + +#content p.error { + // border: 2px solid #c00; + // margin: 1em 2em; + // padding: 1em 0em; + font-size: 120%; + font-weight: bold; + // background-color: #faa; + color: #c00; + } + +#content p.warn { + // border: 2px solid #cc0; + // margin: 1em 2em; + // padding: 1em; + background-color: #ffa; + } + +#content p.ok { + font-size: 120%; + font-weight: bold; + // border: 2px solid #0c0; + // margin: 1em 2em; + // padding: 1em 0em; + color: #090; + } + + +#content input, textarea { + float: left; + font-size: 90%; + // font-family: courier-new, courier, sans; + background-color: #eec; + margin-top: -0.3em; + } + + +#content input.error { + background-color: #faa; + // border: 2px solid #c00; + // margin: .5em 1em .5em 0em; + } + +#content input.button { + font-family: Geneva, Arial, Helvetica, Serif; + font-size: 100%; + font-weight: bold; + color: #228; + } + +#content dt { + font-size: 110%; + font-weight: bold; + color: #228; + margin: .7em 1em .5em 0em; + float: left; + border-top: 1px dotted #ccc; + border-bottom: 1px dotted #ccc; + width: 20%; + clear: both; + } + +#content a { + text-decoration: underline; + color: #449; + } + +#content form { + padding: 1em 0em; + } + +#content dl { + margin: 0em; + } + +#content dd { + color: #c00; + float: left; + margin: 1em 0em .5em 0em; + // font-size: 90%; + font-weight: light; + // margin: .5em 1em .5em 0em; + // border-top: 1px dotted #ccc; + // border-bottom: 1px dotted #ccc; + } + + + +/*---------------------------------------------------------------------------------- + * footer + *---------------------------------------------------------------------------------- + */ + +#footer { + float: right; + clear: both; + text-align: center; + font-size: 80%; + width: 70%; + color: #666; + margin: 1em 3em 1em 2em; + padding: 1em 0em 1em; + border-top: 1px dotted #ccc; + border-bottom: 1px dotted #ccc; + } diff --git a/www/static/webconf.css b/www/static/webconf.css new file mode 100644 index 0000000..2c09494 --- /dev/null +++ b/www/static/webconf.css @@ -0,0 +1,367 @@ +/* webconf.css - For Alpine webconf + */ + +/* Default definitions */ +body { + background: #fff url("/static/alpine.gif") no-repeat top center; + font-family: Geneva, Arial, Helvetica, San-Serif; + margin: 0em; + padding: 0em; + font-size: small; + } + +p { + margin: 0em 0em 0em 0em; + } + +a { + text-decoration: none; + color: #333; + } + +a:link { color: #226; + } + +a:visited { + color: #226; + } + +a:hover { + color: #44C; + } + +a:active { + } + +pre.code { + border: 1px solid #666; + background: #f8f8f8; + padding: .5em; +} +/*---------------------------------------------------------------------------------- + * Header + *---------------------------------------------------------------------------------- + */ +#head { + padding: 1em 1em 1em 0em; + color: #333; + // margin: -1em 0em 0em 0em; + padding: 1em; + } + +#head h1 { + font-size: 259%; + color: #33A; + // font-family: Palatino, Times-Roman, Serif; + font-weight: bold; + margin: 0em 0em 0em 0em; + //float: left; + // width: 49%; + } + +#head h2 { + margin: 0em 0em 0em 0em; + font-size: 100%; + font-weight: light; + float: right; + width: 30%; + } + +#head h3 { + float: right; + font-size: 90%; + color: #222; + } + +#head p { + // border: 1px inset #ccc; + // border-top: 1px solid #ccc; + // border-bottom: 1px solid #ccc; + } + +/*---------------------------------------------------------------------------------- + * Left (Main) Menu + *---------------------------------------------------------------------------------- + */ +#mainmenu { + display: block; + background: #eee; + // border: 1px #222 solid; + width: 19%; + float: left; + margin: 5em 0em 0em 1em; + padding: 0em 0em 0em .5em; + // border-right: 1px #ccc solid; + } + +#mainmenu h3 { + // background-color: #eee; + color: #448; + padding: .25em 0em 0em .25em; + font-weight: normal; + font-size: 138%; + margin: 0em 0em .1em 0em; + } + +#mainmenu ul { + padding: .2em 0em .6em 1em; + margin: 0; + list-style-type: none; + } +#mainmenu li { + background-color: inherit; + padding: .1em 0em .1em 1em; + } + +#mainmenu a:hover { + display: block; +// background: transparent url("/static/unselected.gif") repeat top left; +// background-color: #f8f8f8; +// color: #669; + text-decoration: underline; + } + +#mainmenu #selected { + background-color: #fff; + // border: 1px solid #666; + //border-right: 1px solid #ffd; + } + + +/*---------------------------------------------------------------------------------- + * Submenu ( Horizontal Tabs) + *---------------------------------------------------------------------------------- + */ +#submenu { + width: 75%; + margin: 0 1em 0 0; +// font-weight: Bold; + background: transparent url(/static/greypx.gif) repeat-x bottom left; + float: right; + } + +#submenu p { + display: none; + } + +#submenu h2 { + font-weight: normal; + font-size: 161%; + color: #228; + float: left; + margin: 0em; + margin-top: .2em; + } + +#submenu ul { + margin: 0em 2em 0em 0em; + list-style: none; + padding: 0em 2em 0em 2em; + float: right; + background: transparent; + } + +#submenu li { + // display: block; + // float: left; + // border: 1px solid #666; + // padding: .5em 1em .6em 1em; + // margin: 0em .5em 0em .5em; + // border-bottom: none; + color: #33A; + background: transparent url("/static/unselected.gif") repeat-x top left; + float: left; + margin: 0; + padding: 0; + } + +#submenu #selected { + // background: #fff url("/static/selected.gif") repeat-x top left; + background: #fff; + display: block; + float: left; + margin: 0em; + padding: .75em 1em; + border: 1px solid #ccc; + border-bottom: none; + } + + +#submenu a { + display: block; + float: left; + margin: 0em; + padding: .75em 1em; + border: 1px solid #ccc; + border-bottom: none; + // background: #eee; + } + +#submenu a:hover { + // color: #226; +// background: #eee; + text-decoration: underline; + } + +/*---------------------------------------------------------------------------------- + * Content + *---------------------------------------------------------------------------------- + */ +#content { + display: block; + float:left; + width: 70%; + color: #333; + padding: 2em 0em 3em 3em; + // border-left: #ccc 1px solid; + } + +#content h1 { + font-size: 120%; + // width: 100%; + background-color: #eee; + border-bottom: 2px solid #aaa; + margin: 1em 0em 1em 0em; + clear: both; + } + +#content h2 { + font-size: 100%; + // width: 100%; + background-color: #eee; + border-bottom: 1px solid #aaa; + // margin: 1em 1em 0em 1em; + clear: both; + } + +#content h3 { + font-size: 100%; + // width: 100%; + background-color: #fff; + border-bottom: 0px solid #eee; + margin: 0em 0em 0em 0em; + clear: both; + } + +#content p { + // margin: 1em 1em 0em 1em; + clear: both; + padding: .5em 0em .5em 0em; + } + +#content p.error { + // border: 2px solid #c00; + // margin: 1em 2em; + // padding: 1em 0em; + font-size: 120%; + font-weight: bold; + background-color: #eee; + color: #f44; + } + +#content p.warn { + // border: 2px solid #cc0; + // margin: 1em 2em; + // padding: 1em; + background-color: #cc4; + } + +#content p.ok { + font-size: 120%; + font-weight: bold; + // border: 2px solid #0c0; + // margin: 1em 2em; + // padding: 1em 0em; + color: #090; + } + + +#content input, textarea { + float: left; + background-color: #eee; + margin-top: -0.3em; + } + + +#content input.error { + /* yellow snow - bad */ + background-color: #ffa; + } + +#content input.button { + font-family: Geneva, Arial, Helvetica, Serif; + font-size: 100%; + font-weight: bold; + color: #228; + } + +#content dt { + font-size: 110%; + // font-weight: bold; + color: #228; + margin: .7em 1em .5em 0em; + float: left; + border-top: 1px dotted #ccf; + border-bottom: 1px dotted #ccf; + // background-color: #eef; + width: 20%; + clear: both; + } + +#content th { + font-weight: inherit; + color: #228; + background: #eef; + } + +#content a { + text-decoration: underline; + color: #449; + } + +#content form { + padding: 1em 0em; + } + +#content dl { + margin: 0em; + } + +#content dd { + color: #444; + float: left; + margin: 1em 0em .5em 0em; + // font-size: 90%; + font-weight: light; + // margin: .5em 1em .5em 0em; + // border-top: 1px dotted #ccc; + // border-bottom: 1px dotted #ccc; + } + +#content .grey { + background-color: #eee; + } + + +#content form { + display: inline; + } + + +/*---------------------------------------------------------------------------------- + * footer + *---------------------------------------------------------------------------------- + */ + +#footer { + float: right; + clear: both; + text-align: center; + font-size: 80%; + width: 70%; + color: #666; + margin: 1em 3em 1em 2em; + padding: 1em 0em 1em; + border-top: 1px dotted #ccc; + border-bottom: 1px dotted #ccc; + } -- cgit v1.2.3