summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorZach LeBar <zach@zachlebar.com>2012-08-18 20:02:36 +0000
committerZach LeBar <zach@zachlebar.com>2012-08-18 20:02:36 +0000
commitbb6fd68db726c6b4d1a12e82a741d4248b0c70de (patch)
tree10797e2010478358ff146afdb237cbc9266459bc
downloadacf-kamailio-bb6fd68db726c6b4d1a12e82a741d4248b0c70de.tar.bz2
acf-kamailio-bb6fd68db726c6b4d1a12e82a741d4248b0c70de.tar.xz
Further tweaks to support PostgreSQL 'schemas' and a hack to allow DISTributary Phone System Tool: 'ASHP' to submit NULL values into integer columns. The ability to add NULL values should be implemented in the near future.
-rw-r--r--Makefile44
-rw-r--r--README1
-rw-r--r--config.mk10
-rw-r--r--kamailio-controller.lua83
-rw-r--r--kamailio-createtableentry-html.lsp14
-rw-r--r--kamailio-createuser-html.lsp15
l---------kamailio-edit-html.lsp1
-rw-r--r--kamailio-listfiles-html.lsp24
-rw-r--r--kamailio-listtables-html.lsp27
-rw-r--r--kamailio-listusers-html.lsp43
-rw-r--r--kamailio-model.lua588
-rw-r--r--kamailio-searchdatabase-html.lsp28
l---------kamailio-startstop-html.lsp1
-rw-r--r--kamailio-status-html.lsp46
-rw-r--r--kamailio-updatetableentry-html.lsp14
-rw-r--r--kamailio-updateuser-html.lsp15
-rw-r--r--kamailio-viewtable-html.lsp54
-rw-r--r--kamailio.menu7
-rw-r--r--kamailio.roles4
19 files changed, 1019 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..80e34f1
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,44 @@
+APP_NAME=kamailio
+PACKAGE=acf-$(APP_NAME)
+VERSION=0.4.3
+
+APP_DIST=\
+ kamailio* \
+
+
+EXTRA_DIST=README Makefile config.mk
+
+DISTFILES=$(APP_DIST) $(EXTRA_DIST)
+
+TAR=tar
+
+P=$(PACKAGE)-$(VERSION)
+tarball=$(P).tar.bz2
+install_dir=$(DESTDIR)/$(appdir)/$(APP_NAME)
+
+all:
+clean:
+ rm -rf $(tarball) $(P)
+
+dist: $(tarball)
+
+install:
+ mkdir -p "$(install_dir)"
+ cp -a $(APP_DIST) "$(install_dir)"
+
+$(tarball): $(DISTFILES)
+ rm -rf $(P)
+ mkdir -p $(P)
+ cp -a $(DISTFILES) $(P)
+ $(TAR) -jcf $@ $(P)
+ rm -rf $(P)
+
+# target that creates a tar package, unpacks is and install from package
+dist-install: $(tarball)
+ $(TAR) -jxf $(tarball)
+ $(MAKE) -C $(P) install DESTDIR=$(DESTDIR)
+ rm -rf $(P)
+
+include config.mk
+
+.PHONY: all clean dist install dist-install
diff --git a/README b/README
new file mode 100644
index 0000000..ec521f5
--- /dev/null
+++ b/README
@@ -0,0 +1 @@
+ACF for kamailio
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..45f4d21
--- /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=${acfdir}/cgi-bin
+appdir=${acfdir}/app
+acflibdir=${acfdir}/lib
+sessionsdir=${localstatedir}/lib/acf/sessions
diff --git a/kamailio-controller.lua b/kamailio-controller.lua
new file mode 100644
index 0000000..7a98ae3
--- /dev/null
+++ b/kamailio-controller.lua
@@ -0,0 +1,83 @@
+module(..., package.seeall)
+
+-- Load libraries
+require("controllerfunctions")
+
+default_action = "status"
+
+function status(self)
+ return self.model.getstatus()
+end
+
+function startstop(self)
+ return controllerfunctions.handle_startstop(self, self.model.startstop_service, self.clientdata)
+end
+
+function listfiles(self)
+ return self.model.list_files()
+end
+
+function edit(self)
+ return controllerfunctions.handle_form(self, function() return self.model.get_filedetails(self.clientdata.filename) end, self.model.update_filedetails, self.clientdata, "Save", "Edit File", "File Saved")
+end
+
+function listusers(self)
+ return self.model.list_users()
+end
+
+function createuser(self)
+ return controllerfunctions.handle_form(self, self.model.get_new_user, self.model.create_new_user, self.clientdata, "Create", "Create New User")
+end
+
+function deleteuser(self)
+ return self:redirect_to_referrer(self.model.delete_user(self.clientdata.username))
+end
+
+function updateuser(self)
+ return controllerfunctions.handle_form(self, function() return self.model.get_user(self.clientdata.username) end, self.model.update_user, self.clientdata, "Update", "Update User")
+end
+
+function listtables(self)
+ return self.model.list_tables()
+end
+
+function viewtable(self)
+ local req_table
+ if self.clientdata.schema then
+ req_table = self.clientdata.schema.."."..self.clientdata.table
+ else
+ req_table = self.clientdata.table
+ end
+ return self.model.list_table_entries(req_table)
+end
+
+function deletetableentry(self)
+ return self:redirect_to_referrer(self.model.delete_table_entry(self.clientdata.table, self.clientdata.id))
+end
+
+function updatetableentry(self)
+ return controllerfunctions.handle_form(self, function() return self.model.get_table_entry(self.clientdata.table, self.clientdata.id) end, self.model.update_table_entry, self.clientdata, "Update", "Update Table Entry", "Entry updated")
+end
+
+function createtableentry(self)
+ local req_table
+ if self.clientdata.schema then
+ req_table = self.clientdata.schema.."."..self.clientdata.table
+ else
+ req_table = self.clientdata.table
+ end
+ return controllerfunctions.handle_form(self, function() return self.model.get_table_entry(req_table) end, self.model.create_table_entry, self.clientdata, "Create", "Create New Table Entry", "Entry created")
+end
+
+function createdatabase(self)
+ return self:redirect_to_referrer(self.model.create_database())
+end
+
+function reloadplan(self)
+ return self:redirect_to_referrer(self.model.reloadplan())
+end
+
+
+searchdatabase = function( self )
+ return self.model.search_database(self.clientdata.id, self.clientdata.value, self.clientdata.comparison)
+end
diff --git a/kamailio-createtableentry-html.lsp b/kamailio-createtableentry-html.lsp
new file mode 100644
index 0000000..14ffd0a
--- /dev/null
+++ b/kamailio-createtableentry-html.lsp
@@ -0,0 +1,14 @@
+<% local form, viewlibrary, page_info = ...
+require("viewfunctions")
+%>
+
+<H1><%= html.html_escape(form.label) %></H1>
+<%
+ form.value.table.readonly = true
+ if page_info.action == "updatetableentry" and form.value.id then
+ form.value.id.readonly = true
+ elseif form.value.id then
+ form.value.id.type = "hidden"
+ end
+ displayform(form, nil, nil, page_info, 2)
+%>
diff --git a/kamailio-createuser-html.lsp b/kamailio-createuser-html.lsp
new file mode 100644
index 0000000..fb05750
--- /dev/null
+++ b/kamailio-createuser-html.lsp
@@ -0,0 +1,15 @@
+<% local form, viewlibrary, page_info = ...
+require("viewfunctions")
+%>
+
+<H1><%= html.html_escape(form.label) %></H1>
+<%
+ form.action = page_info.script .. page_info.prefix .. page_info.controller .. "/" .. page_info.action
+ form.value.password.type = "password"
+ form.value.password_confirm.type = "password"
+ if page_info.action == "updateuser" then
+ form.value.username.readonly = true
+ end
+ local order = {"username", "password", "password_confirm", "email_address"}
+ displayform(form, order)
+%>
diff --git a/kamailio-edit-html.lsp b/kamailio-edit-html.lsp
new file mode 120000
index 0000000..15b1930
--- /dev/null
+++ b/kamailio-edit-html.lsp
@@ -0,0 +1 @@
+../filedetails-html.lsp \ No newline at end of file
diff --git a/kamailio-listfiles-html.lsp b/kamailio-listfiles-html.lsp
new file mode 100644
index 0000000..93a00fc
--- /dev/null
+++ b/kamailio-listfiles-html.lsp
@@ -0,0 +1,24 @@
+<% local view, viewlibrary, page_info, session = ...
+require("viewfunctions")
+%>
+
+<% displaycommandresults({"edit"}, session) %>
+
+<% if viewlibrary and viewlibrary.dispatch_component then
+ viewlibrary.dispatch_component("status")
+end %>
+
+<h1>Configuration</h1>
+<DL><TABLE>
+ <TR style="background:#eee;font-weight:bold;">
+ <TD style="padding-right:20px;white-space:nowrap;text-align:left;" class="header">File</TD>
+ <TD style="padding-right:20px;white-space:nowrap;text-align:left;" class="header">Size</TD>
+ <TD style="white-space:nowrap;text-align:left;" class="header">Last Modified</TD>
+ </TR>
+
+<%
+ for k,v in ipairs( view.value ) do
+ io.write( "<tr><td><a href=\"" .. html.html_escape(page_info.script .. page_info.prefix .. page_info.controller) .. "/edit?filename=" .. html.html_escape(v.filename) .. "&redir=" .. html.html_escape(page_info.orig_action) .. "\">" .. html.html_escape(v.filename) .. "</a></td><td>" .. html.html_escape(v.size) .."</td><td>" .. html.html_escape(v.mtime) .."</td></tr>\n" )
+ end
+%>
+</TABLE></DL>
diff --git a/kamailio-listtables-html.lsp b/kamailio-listtables-html.lsp
new file mode 100644
index 0000000..38dd4f0
--- /dev/null
+++ b/kamailio-listtables-html.lsp
@@ -0,0 +1,27 @@
+<% local form, viewlibrary, page_info, session = ... %>
+<% require("viewfunctions") %>
+
+<% displaycommandresults({"createdatabase"}, session) %>
+
+<H1><%= html.html_escape(form.label) %></H1>
+<DL>
+<% for i,v in ipairs(form.value) do %>
+ <li>
+ <% if viewlibrary.check_permission("viewtable") then %>
+ <% if v.schema ~= "public" then %>
+ <%= html.link{value = "viewtable?table=" .. v.table .. "&schema=" .. v.schema, label="("..v.schema..") "..v.table} %>
+ <% else %>
+ <%= html.link{value = "viewtable?table=" .. v.table, label=v.table} %>
+ <% end %>
+ <% else %>
+ <%= html.html_escape("("..v.schema..") "..v.table) %>
+ <% end %>
+<% end %>
+<% if #form.value == 0 and viewlibrary.check_permission("createdatabase") then %>
+<DT>Create Database</DT><DD>
+<form action="<%= html.html_escape(page_info.script .. page_info.prefix .. page_info.controller .. "/createdatabase") %>">
+<input class="submit" type="submit" value="Create"></DD>
+</form>
+</DD>
+<% end %>
+</DL>
diff --git a/kamailio-listusers-html.lsp b/kamailio-listusers-html.lsp
new file mode 100644
index 0000000..5994b79
--- /dev/null
+++ b/kamailio-listusers-html.lsp
@@ -0,0 +1,43 @@
+<% local form, viewlibrary, page_info, session = ... %>
+<% require("viewfunctions") %>
+
+<% displaycommandresults({"deleteuser", "updateuser"}, session) %>
+<% displaycommandresults({"createuser"}, session, true) %>
+
+<H1><%= html.html_escape(form.label) %></H1>
+<DL>
+<TABLE>
+ <TR style="background:#eee;font-weight:bold;">
+ <% if viewlibrary.check_permission("deleteuser") or viewlibrary.check_permission("updateuser") then %>
+ <TD style="padding-right:20px;white-space:nowrap;" class="header">Action</TD>
+ <% end %>
+ <TD style="padding-right:20px;white-space:nowrap;" class="header">User Name</TD>
+ <TD style="white-space:nowrap;" class="header">Password</TD>
+ </TR>
+
+<% for i,user in ipairs(form.value) do %>
+ <TR>
+ <% if viewlibrary.check_permission("deleteuser") or viewlibrary.check_permission("updateuser") then %>
+ <TD style="padding-right:20px;white-space:nowrap;">
+ <% if viewlibrary.check_permission("updateuser") then %>
+ <%= html.link{value = "updateuser?username=" .. user.username.."&redir="..page_info.orig_action, label="Update "} %>
+ <% end %>
+ <% if viewlibrary.check_permission("deleteuser") then %>
+ <%= html.link{value = "deleteuser?username=" .. user.username, label="Delete "} %>
+ <% end %>
+ </TD>
+ <% end %>
+ <TD><%= html.html_escape(user.username) %></TD>
+ <% if viewlibrary.check_permission("updateuser") then %>
+ <TD><%= html.html_escape(user.password) %></TD>
+ <% else %>
+ <TD>******</TD>
+ <% end %>
+ </TR>
+<% end %>
+</TABLE>
+</DL>
+
+<% if viewlibrary and viewlibrary.dispatch_component and viewlibrary.check_permission("createuser") then
+ viewlibrary.dispatch_component("createuser")
+end %>
diff --git a/kamailio-model.lua b/kamailio-model.lua
new file mode 100644
index 0000000..ed05f85
--- /dev/null
+++ b/kamailio-model.lua
@@ -0,0 +1,588 @@
+module(..., package.seeall)
+
+-- Load libraries
+require("posix")
+require("modelfunctions")
+require("fs")
+require("format")
+require("validator")
+
+-- Set variables
+local processname = "kamailio"
+local packagename = "kamailio"
+local baseurl = "/etc/kamailio"
+local kamctlrc_file = "/etc/kamailio/kamctlrc"
+
+local path = "PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin "
+local env
+local con
+local DBENGINE
+
+-- ################################################################################
+-- DATABASE FUNCTIONS
+
+local function assert (v, m)
+ if not v then
+ m = m or "Assertion failed!"
+ error(m, 0)
+ end
+ return v, m
+end
+
+-- Escape special characters in sql statements
+local escape = function(sql)
+ sql = sql or ""
+ return string.gsub(sql, "'", "''")
+end
+
+local databaseconnect = function()
+ if not con then
+ -- parse the kamctlrc file
+ local config = format.parse_ini_file(fs.read_file(kamctlrc_file), "") or {}
+ if not config.DBENGINE then
+ error("Database engine not specified, please setup one in the config script "..kamctlrc_file)
+ end
+
+ -- create environment object
+ if config.DBENGINE == "MYSQL" or config.DBENGINE == "mysql" or config.DBENGINE == "MySQL" then
+ error("MYSQL database not supported")
+ elseif config.DBENGINE == "PGSQL" or config.DBENGINE == "pgsql" or config.DBENGINE == "postgres" or config.DBENGINE == "postgresql" or config.DBENGINE == "POSTGRESQL" then
+ require("luasql.postgres")
+ env = assert (luasql.postgres())
+ DBENGINE = "PGSQL"
+ elseif config.DBENGINE == "ORACLE" or config.DBENGINE == "oracle" or config.DBENGINE == "Oracle" then
+ error("ORACLE database not supported")
+ elseif config.DBENGINE == "DBTEXT" or config.DBENGINE == "dbtext" or config.DBENGINE == "textdb" then
+ error("DBTEXT database not supported")
+ elseif config.DBENGINE == "DB_BERKELEY" or config.DBENGINE == "db_berkeley" or config.DBENGINE == "BERKELEY" or config.DBENGINE == "berkeley" then
+ error("BERKELEY database not supported")
+ else
+ error("Unknown database engine "..config.DBENGINE)
+ end
+
+ -- connect to data source
+ con = assert(env:connect(config.DBNAME or "", config.DBRWUSER or "", config.DBRWPW or "", config.DBHOST, config.DBPORT))
+ return true
+ end
+ return false
+end
+
+local databasedisconnect = function()
+ if env then
+ env:close()
+ env = nil
+ end
+ if con then
+ con:close()
+ con = nil
+ end
+end
+
+local runsqlcommand = function(sql)
+logevent(sql)
+ assert(con:execute(sql))
+end
+
+local getselectresponse = function(sql)
+ local retval = {}
+logevent(sql)
+ local cur = assert (con:execute(sql))
+ local row = cur:fetch ({}, "a")
+ while row do
+ local tmp = {}
+ for name,val in pairs(row) do
+ tmp[name] = val
+ end
+ retval[#retval + 1] = tmp
+ row = cur:fetch (row, "a")
+ end
+ cur:close()
+ return retval
+end
+
+local listtables = function()
+ local result = {}
+ if DBENGINE == "PGSQL" then
+ local tab = getselectresponse("SELECT tablename, schemaname FROM pg_tables WHERE tablename !~* 'pg_*' ORDER BY schemaname, tablename ASC")
+ for i,t in ipairs(tab) do
+ result[#result+1] = {schema=t.schemaname, table=t.tablename}
+ end
+ else
+ -- untested
+ result = con:tables()
+ end
+ return result
+end
+
+local listcolumns = function(table, schema)
+ local result = {}
+ if DBENGINE == "PGSQL" then
+ local col
+ if schema then
+ col = getselectresponse("SELECT column_name AS field FROM information_schema.columns WHERE table_schema='"..schema.."' AND table_name='"..table.."' ORDER BY ordinal_position")
+ else
+ col = getselectresponse("SELECT column_name AS field FROM information_schema.columns WHERE table_name='"..table.."' ORDER BY ordinal_position")
+ end
+
+ for i,c in ipairs(col) do
+ result[#result+1] = c.field
+ end
+ end
+ return result
+end
+
+-- ################################################################################
+-- LOCAL FUNCTIONS
+
+local is_valid_filename = function(filename)
+ local dirname = posix.dirname(filename)
+ return validator.is_valid_filename(filename) and string.match(dirname, baseurl) and not string.match(dirname, "%.%.")
+end
+
+local function validate_user(user)
+ local success = true
+ if user.value.username.value == "" then
+ user.value.username.errtxt = "Cannot be empty"
+ success = false
+ end
+ if user.value.password.value == "" then
+ user.value.password.errtxt = "Cannot be empty"
+ success = false
+ end
+ if user.value.password.value ~= user.value.password_confirm.value then
+ user.value.password_confirm.errtxt = "Must match password"
+ success = false
+ end
+ return success, user
+end
+
+-- ################################################################################
+-- PUBLIC FUNCTIONS
+
+function startstop_service(action)
+ return modelfunctions.startstop_service(processname, action)
+end
+
+function getstatus()
+ return modelfunctions.getstatus(processname, packagename, "Kamailio Status")
+end
+
+function get_filedetails(filename)
+ return modelfunctions.getfiledetails(filename, is_valid_filename)
+end
+
+function update_filedetails(filedetails)
+ return modelfunctions.setfiledetails(filedetails, is_valid_filename)
+end
+
+function reloadplan()
+ local cmd = path .. " sercmd mi_dg dp_reload"
+ local f = io.popen(cmd)
+ local result = f:read("*a")
+ f:close()
+ return cfe({value=result, label="Reloading Dial Plan Result"})
+end
+
+function list_files()
+ local retval = {}
+ for file in fs.find(null, baseurl) do
+ local details = fs.stat(file)
+ if details.type == "regular" then
+ details.filename = file
+ table.insert(retval, details)
+ end
+ end
+ table.sort(retval, function(a,b) return a.filename < b.filename end)
+ return cfe({ type="structure", value=retval, label="List of Kamailio files" })
+end
+
+local function parse_db_show(table)
+ local cmd = path .. "kamctl db show "..(table or "")
+ local f = io.popen(cmd)
+ -- These settings work for Postgres and DBTEXT database
+ local delimiter = "\'?%s*[,|]%s*\'?"
+ local results = {}
+ local errtxt
+ for line in f:lines() do
+ if #results == 0 and string.match(line, "^ERROR:") then
+ errtxt = line
+ results = nil
+ break
+ end
+ if string.match(line, "^[+-]+$") then
+ results = {}
+ else
+ local words = format.string_to_table(line, delimiter)
+ if words and #words > 0 then
+ results[#results+1] = words
+ end
+ end
+ end
+ f:close()
+ return results, errtxt
+end
+
+function list_users()
+ -- Database format: id | username | domain | password | email_address | ha1 | ha1b | rpid
+ local results = {}
+ local r, errtxt
+ r, errtxt = parse_db_show("subscriber")
+ for i,words in ipairs(r or {}) do
+ if #words > 1 then
+ local temp = {username = words[2],
+ --domain = words[3],
+ password = words[4],
+ --email_address = words[5]
+ }
+ results[#results+1] = temp
+ end
+ end
+ table.sort(results, function(a,b) return a.username < b.username end)
+ return cfe({type="list", value=results, label="Kamailio Users"})
+end
+
+function get_new_user()
+ local user = {}
+ user.username = cfe({label="User Name"})
+ user.password = cfe({label="Password"})
+ user.password_confirm = cfe({label="Password (confirm)"})
+ --user.email_address = cfe({label="E-mail Address"})
+ return cfe({type="group", value=user, label="Kamailio User"})
+end
+
+function create_new_user(user)
+ local success = validate_user(user)
+ if success then
+ local cmd = path .. "kamctl add "..format.escapespecialcharacters(user.value.username.value).." "..format.escapespecialcharacters(user.value.password.value)
+ --if user.value.email_address.value ~= "" then
+ -- cmd = cmd.." "..format.escapespecialcharacters(user.value.email_address.value)
+ --end
+ local f = io.popen(cmd)
+ user.descr = f:read("*a")
+ f:close()
+ else
+ user.errtxt = "Failed to create new user"
+ end
+
+ return user
+end
+
+function delete_user(username)
+ local cmd = path .. "kamctl rm "..format.escapespecialcharacters(username)
+ local f = io.popen(cmd)
+ local result = f:read("*a")
+ f:close()
+ return cfe({value=result, label="Delete User Result"})
+end
+
+function get_user(username)
+ local user = get_new_user()
+ user.value.username.value = username
+ user.value.username.errtxt = "Invalid user"
+ local users = list_users()
+ for i,u in ipairs(users.value) do
+ if u.username == username then
+ user.value.username.errtxt = nil
+ break
+ end
+ end
+ return user
+end
+
+function update_user(user)
+ local success = validate_user(user)
+ if success then
+ local cmd = path .. "kamctl passwd "..format.escapespecialcharacters(user.value.username.value).." "..format.escapespecialcharacters(user.value.password.value)
+ local f = io.popen(cmd)
+ user.descr = f:read("*a")
+ f:close()
+ else
+ user.errtxt = "Failed to update user"
+ end
+
+ return user
+end
+
+function list_tables()
+ local retval = {}
+ local errtxt
+ -- Get the devices from the DB
+ local res, err = pcall(function()
+ local connected = databaseconnect()
+ retval = listtables()
+ if connected then databasedisconnect() end
+ end)
+ if not res and err then
+ errtxt = err
+ end
+
+ return cfe({ type="list", value=retval, label="List of Database Tables", errtxt=errtxt })
+end
+
+function list_table_entries(req_table)
+ local retval = {}
+ -- split up the schema from the table
+ local schema, table
+ schema, table = string.match(req_table, "(.+)%.(.+)")
+ -- if there's no schema with it then grab the req_table value
+ if not schema then
+ table = req_table
+ end
+ retval.table = cfe({ value=req_table or "", label="Table" })
+ retval.fields = cfe({ type="list", value={}, label="List of Table Fields" })
+ retval.entries = cfe({ type="structure", value={}, label="List of Database Entries" })
+ local errtxt
+ -- Get the devices from the DB
+ local res, err = pcall(function()
+ local connected = databaseconnect()
+ local tables = listtables()
+ retval.table.errtxt = "Table does not exist"
+ errtxt = "Table does not exist"
+ for i,t in ipairs(tables) do
+ if t.table == table then
+ retval.table.errtxt = nil
+ errtxt = nil
+ -- if there's a schema, include it in the select statement
+ if schema then
+ retval.entries.value = getselectresponse("SELECT * FROM "..schema.."."..table) or {}
+ else
+ retval.entries.value = getselectresponse("SELECT * FROM "..table) or {}
+ end
+ retval.fields.value = listcolumns(table, schema) or {}
+ end
+ end
+ if connected then databasedisconnect() end
+ end)
+ if not res and err then
+ errtxt = err
+ end
+ return cfe({ type="group", value=retval, label="Database Table Entries", errtxt=errtxt })
+end
+
+function get_table_entry(req_table, id)
+ local retval = {}
+ -- split up the schema from the table
+ local schema, table
+ schema, table = string.match(req_table, "(.+)%.(.+)")
+ -- if there's no schema with it then grab the req_table value
+ if not schema then
+ table = req_table
+ end
+ retval.table = cfe({ value=req_table or "", label="Table", errtxt="Table does not exist", seq=0 })
+ local errtxt = "Table does not exist"
+ if req_table and req_table ~= "" then
+ local res, err = pcall(function()
+ local connected = databaseconnect()
+ local tables = listtables()
+ for i,t in ipairs(tables) do
+ if t.table == table then
+ retval.table.errtxt = nil
+ errtxt = nil
+ break
+ end
+ end
+ if not errtxt then
+ local fields = listcolumns(table, schema)
+ for i,f in ipairs(fields) do
+ retval[f] = cfe({ label=f, seq=i })
+ end
+ if id and id ~= "" then
+ local entry
+ if schema then
+ entry = getselectresponse("SELECT * FROM "..schema.."."..table.." WHERE id='"..escape(id).."'")
+ else
+ entry = getselectresponse("SELECT * FROM "..table.." WHERE id='"..escape(id).."'")
+ end
+ if entry and #entry > 0 then
+ for n,v in pairs(entry[1]) do
+ if retval[n] then retval[n].value = v end
+ end
+ end
+ end
+ end
+ if connected then databasedisconnect() end
+ end)
+ if not res and err then
+ errtxt = err
+ end
+ end
+
+ return cfe({ type="group", value=retval, label="Database Table Entry", errtxt=errtxt })
+end
+
+function create_table_entry(entry)
+ return update_table_entry(entry, true)
+end
+
+function update_table_entry(entry, create)
+ local success = true
+ local errtxt
+ -- Validate the settings
+ -- relying on get_table_entry to do the validation of table
+ if entry.value.table.value == "" or entry.value.table.errtxt then
+ success = false
+ entry.value.table.errtxt = "Table does not exist"
+ end
+ if not create then
+ if not entry.value.id then
+ success = false
+ elseif not entry.value.id.value or entry.value.id.value == "" then
+ success = false
+ entry.value.id.errtxt = "Invalid id"
+ end
+ end
+ if success then
+ local res, err = pcall(function()
+ local connected = databaseconnect()
+ local tables = listtables()
+ local schema, tablename
+ schema, tablename = string.match(entry.value.table.value, "(.+)%.(.+)")
+ success = false
+ if not schema then
+ tablename = entry.value.table.value
+ end
+ entry.value.table.errtxt = "Table does not exist"
+ for i,t in ipairs(tables) do
+ if t.table == tablename then
+ success = true
+ entry.value.table.errtxt = nil
+ break
+ end
+ end
+ if success and not create then
+ local sql = "SELECT * FROM "..entry.value.table.value.." WHERE id='"..escape(entry.value.id.value).."'"
+ local tmp = getselectresponse(sql)
+ if not tmp or #tmp == 0 then
+ success = false
+ errtxt = "Entry does not exist"
+ end
+ end
+ if success then
+ local names = {}
+ local values = {}
+ for n,v in pairs(entry.value) do
+ if n ~= "table" and n ~= "id" then
+ --- !!! HACK !!! ---
+ --- Fix for DISTributary Phone System Tool: ASHP
+ ---
+ --- Need to allow for insertion of NULL rather than
+ --- simply '' so that integers and booleans can be
+ --- added with empty values.
+ ---- ----------- ---
+ if n == "role_id" or n == "seq" then
+ if v.value == "" then
+ v.value = "0"
+ end
+ end
+ names[#names+1] = n
+ values[#values+1] = escape(v.value)
+ end
+ end
+ if create then
+ sql = "INSERT INTO "..entry.value.table.value.." ("..table.concat(names, ", ")..") VALUES('"..table.concat(values, "', '").."')"
+ else
+ sql = "UPDATE "..entry.value.table.value.." SET ("..table.concat(names, ", ")..") = ('"..table.concat(values, "', '").."') WHERE id='"..escape(entry.value.id.value).."'"
+ end
+ runsqlcommand(sql)
+ end
+ if connected then databasedisconnect() end
+ end)
+ if not res and err then
+ success = false
+ errtxt = err
+ end
+ end
+ if not success then
+ if create then
+ entry.errtxt = errtxt or "Failed to create entry"
+ else
+ entry.errtxt = errtxt or "Failed to save entry"
+ end
+ end
+ return entry
+end
+
+function delete_table_entry(req_table, id)
+ local result = ""
+ local errtxt
+ -- split up the schema from the table
+ local schema, table
+ schema, table = string.match(req_table, "(.+)%.(.+)")
+ -- if there's no schema with it then grab the req_table value
+ if not schema then
+ table = req_table
+ end
+ if not req_table or req_table == "" then
+ errtxt = "Invalid table"
+ elseif not id or id == "" then
+ errtxt = "Invalid entry"
+ else
+ local res, err = pcall(function()
+ local connected = databaseconnect()
+ errtxt = "Invalid table"
+ local tables = listtables()
+ for i,t in ipairs(tables) do
+ if t.table == table then
+ errtxt = nil
+ break
+ end
+ end
+ if not errtxt then
+ local sql
+ if schema then
+ sql = "DELETE FROM "..schema.."."..table.." WHERE id='"..escape(id).."'"
+ else
+ sql = "DELETE FROM "..table.." WHERE id='"..escape(id).."'"
+ end
+ runsqlcommand(sql)
+ result = "Entry Deleted"
+ end
+ if connected then databasedisconnect() end
+ end)
+ if not res and err then
+ errtxt = err
+ end
+ end
+
+ return cfe({ value=result, errtxt=errtxt, label="Delete Entry Result" })
+end
+
+function create_database()
+ local cmd = path.."echo -e 'y\ny\n' | "..path.."kamdbctl create 2>&1"
+ local f = io.popen(cmd)
+ local result = f:read("*a")
+ return cfe({ value=result, label="Create database result" })
+end
+
+function search_database(id, value, comparison)
+ local errtxt
+ retval = {}
+ retval.id = cfe({type="select", value=id or "", label="Table.Column", option={}, seq=1})
+ retval.comparison = cfe({type="select", value=comparison or "=", label="Comparison", option={"=", "!=", "~", "!~", "~*", "!*~"}, seq=2})
+ retval.value = cfe({label="Value", value=value or "", descr="Value or SQL regular expression", seq=3})
+ local res, err = pcall(function()
+ local connected = databaseconnect()
+ local tables = listtables() or {}
+ for i,t in ipairs(tables) do
+ local columns = listcolumns(t.table) or {}
+ for i,c in ipairs(columns) do
+ retval.id.option[#retval.id.option + 1] = t.table.."."..c
+ end
+ end
+ -- Get the rows from the DB
+ if id and modelfunctions.validateselect(retval.id) and modelfunctions.validateselect(retval.comparison) then
+ retval.result = cfe({type="structure", value={}, label="List of Rows", seq=4 })
+ local table, column = string.match(id, "^([^.]*)%.(.*)")
+ if table then
+ local sql = "SELECT * FROM "..table.." WHERE "..column..comparison.."'"..value.."'"
+ retval.result.value = getselectresponse(sql)
+ end
+ end
+ if connected then databasedisconnect() end
+ end)
+ if not res and err then
+ errtxt = err
+ end
+ return cfe({type="group", value=retval, label="Database Search", errtxt=errtxt})
+end
+
diff --git a/kamailio-searchdatabase-html.lsp b/kamailio-searchdatabase-html.lsp
new file mode 100644
index 0000000..87f0a9d
--- /dev/null
+++ b/kamailio-searchdatabase-html.lsp
@@ -0,0 +1,28 @@
+<% local form, viewlibrary, page_info = ...
+require("viewfunctions")
+%>
+
+<% if form.value.result then
+ local func = haserl.loadfile(page_info.viewfile:gsub("searchdatabase", "viewtable"))
+ local table, column = string.match(form.value.id.value, "^([^.]*)%.(.*)")
+ form.value.table = cfe({ value=table })
+ form.value.fields = cfe({ value={} })
+ for i,o in ipairs(form.value.id.option) do
+ local t,c = string.match(o, "^([^.]*)%.(.*)")
+ if t == table then
+ form.value.fields.value[#form.value.fields.value + 1] = c
+ end
+ end
+ form.value.entries = form.value.result
+ func(form, viewlibrary, page_info, session)
+ form.value.entries = nil
+ form.value.table = nil
+ form.value.fields = nil
+end %>
+
+<H1><%= html.html_escape(form.label) %></H1>
+<%
+ form.value.result = nil
+ form.option = "Search"
+ displayform(form, nil, nil, page_info, 2)
+%>
diff --git a/kamailio-startstop-html.lsp b/kamailio-startstop-html.lsp
new file mode 120000
index 0000000..0ea2627
--- /dev/null
+++ b/kamailio-startstop-html.lsp
@@ -0,0 +1 @@
+../startstop-html.lsp \ No newline at end of file
diff --git a/kamailio-status-html.lsp b/kamailio-status-html.lsp
new file mode 100644
index 0000000..9a8bce3
--- /dev/null
+++ b/kamailio-status-html.lsp
@@ -0,0 +1,46 @@
+<% local data, viewlibrary, page_info, session = ...
+require("viewfunctions")
+%>
+
+<% displaycommandresults({"install","edit"}, session) %>
+<% displaycommandresults({"startstop"}, session) %>
+<% displaycommandresults({"reloadplan"}, session) %>
+
+<H1>System Info</H1>
+<DL>
+<%
+displayitem(data.value.status)
+
+displayitem(data.value.version)
+if data.value.version and data.value.version.errtxt and viewlibrary.check_permission("apk-tools/apk/install") then
+%>
+ <DT>Install package</DT>
+ <DD><form action="<%= html.html_escape(page_info.script .. "/apk-tools/apk/install") %>" method="POST">
+ <input type='hidden' name='package' value='<%= html.html_escape(data.value.version.name) %>'>
+ <input class='submit' type='submit' value='Install'></form></DD>
+<%
+end
+
+displayitem(data.value.autostart)
+if not (data.value.version and data.value.version.errtxt) and data.value.autostart and data.value.autostart.errtxt and viewlibrary.check_permission("alpine-baselayout/rc/edit") then
+%>
+ <DT>Enable autostart</DT>
+ <DD><form action="<%= html.html_escape(page_info.script .. "/alpine-baselayout/rc/edit") %>" method="POST">
+ <input type='hidden' name='servicename' value='<%= html.html_escape(data.value.autostart.name) %>'>
+ <input type='hidden' name='redir' value='<%= html.html_escape(page_info.orig_action) %>'>
+ <input class='submit' type='submit' value='Enable'></form></DD>
+<% end %>
+</DL>
+
+<% if viewlibrary and viewlibrary.dispatch_component and viewlibrary.check_permission("startstop") then
+ viewlibrary.dispatch_component("startstop")
+end %>
+
+<% if viewlibrary.check_permission("reloadplan") then %>
+<H1>Reload Dial Plan</H1>
+<DL>
+ <DT>Reload dial plan into memory</DT>
+ <DD><form action="<%= html.html_escape(page_info.script .. page_info.prefix) %>kamailio/reloadplan" method="POST">
+ <input class="submit" type="submit" name="reloadplan" value="Reload"></form></DD>
+</DL>
+<% end %>
diff --git a/kamailio-updatetableentry-html.lsp b/kamailio-updatetableentry-html.lsp
new file mode 100644
index 0000000..14ffd0a
--- /dev/null
+++ b/kamailio-updatetableentry-html.lsp
@@ -0,0 +1,14 @@
+<% local form, viewlibrary, page_info = ...
+require("viewfunctions")
+%>
+
+<H1><%= html.html_escape(form.label) %></H1>
+<%
+ form.value.table.readonly = true
+ if page_info.action == "updatetableentry" and form.value.id then
+ form.value.id.readonly = true
+ elseif form.value.id then
+ form.value.id.type = "hidden"
+ end
+ displayform(form, nil, nil, page_info, 2)
+%>
diff --git a/kamailio-updateuser-html.lsp b/kamailio-updateuser-html.lsp
new file mode 100644
index 0000000..fb05750
--- /dev/null
+++ b/kamailio-updateuser-html.lsp
@@ -0,0 +1,15 @@
+<% local form, viewlibrary, page_info = ...
+require("viewfunctions")
+%>
+
+<H1><%= html.html_escape(form.label) %></H1>
+<%
+ form.action = page_info.script .. page_info.prefix .. page_info.controller .. "/" .. page_info.action
+ form.value.password.type = "password"
+ form.value.password_confirm.type = "password"
+ if page_info.action == "updateuser" then
+ form.value.username.readonly = true
+ end
+ local order = {"username", "password", "password_confirm", "email_address"}
+ displayform(form, order)
+%>
diff --git a/kamailio-viewtable-html.lsp b/kamailio-viewtable-html.lsp
new file mode 100644
index 0000000..28d4e13
--- /dev/null
+++ b/kamailio-viewtable-html.lsp
@@ -0,0 +1,54 @@
+<% local form, viewlibrary, page_info, session = ... %>
+<% require("viewfunctions") %>
+
+<% displaycommandresults({"deletetableentry", "updatetableentry"}, session) %>
+<% displaycommandresults({"createtableentry"}, session, true) %>
+
+<H1><%= html.html_escape(form.label) %> - <%= html.html_escape(form.value.table.value) %></H1>
+<DL>
+<TABLE>
+ <TR style="background:#eee;font-weight:bold;">
+ <% if viewlibrary.check_permission("deletetableentry") or viewlibrary.check_permission("updatetableentry") then %>
+ <TD style="padding-right:20px;white-space:nowrap;" class="header">Action</TD>
+ <% end %>
+ <% for i,f in ipairs(form.value.fields.value) do %>
+ <TD style="padding-right:20px;white-space:nowrap;" class="header"><%= html.html_escape(f) %></TD>
+ <% end %>
+ </TR>
+
+<% for i,tableentry in ipairs(form.value.entries.value) do %>
+ <TR>
+ <% if viewlibrary.check_permission("deletetableentry") or viewlibrary.check_permission("updatetableentry") then %>
+ <TD style="padding-right:20px;white-space:nowrap;">
+ <% if viewlibrary.check_permission("updatetableentry") then %>
+ <form action="updatetableentry" method="POST">
+ <input class="hidden" type="hidden" name="table" value="<%= html.html_escape(form.value.table.value) %>">
+ <input class="hidden" type="hidden" name="id" value="<%= html.html_escape(tableentry.id) %>">
+ <input class="hidden" type="hidden" name="redir" value="<%= html.html_escape(page_info.orig_action.."?table="..form.value.table.value) %>">
+ <input class="submit" type="submit" value="Update"></form>
+ <% end %>
+ <% if viewlibrary.check_permission("deletetableentry") then %>
+ <form action="deletetableentry" method="POST">
+ <input class="hidden" type="hidden" name="table" value="<%= html.html_escape(form.value.table.value) %>">
+ <input class="hidden" type="hidden" name="id" value="<%= html.html_escape(tableentry.id) %>">
+ <input class="submit" type="submit" value="Delete"></form>
+ <% end %>
+ </TD>
+ <% end %>
+ <% for i,f in ipairs(form.value.fields.value) do %>
+ <TD><%= html.html_escape(tableentry[f]) %></TD>
+ <% end %>
+ </TR>
+<% end %>
+</TABLE>
+<% if form.errtxt then %>
+<p class="error"><%= html.html_escape(form.errtxt) %></p>
+<% end %>
+<% if #form.value.entries.value == 0 then %>
+<p>No entries found</p>
+<% end %>
+</DL>
+
+<% if page_info.action == "viewtable" and viewlibrary and viewlibrary.dispatch_component and viewlibrary.check_permission("createtableentry") then
+ viewlibrary.dispatch_component("createtableentry", {table=form.value.table.value})
+end %>
diff --git a/kamailio.menu b/kamailio.menu
new file mode 100644
index 0000000..9d51be7
--- /dev/null
+++ b/kamailio.menu
@@ -0,0 +1,7 @@
+# Prefix and controller are already known at this point
+# Cat Group Tab Action
+Applications 86Kamailio Status status
+Applications 86Kamailio Users listusers
+Applications 86Kamailio Database listtables
+Applications 86Kamailio Search_Database searchdatabase
+Applications 86Kamailio Expert listfiles
diff --git a/kamailio.roles b/kamailio.roles
new file mode 100644
index 0000000..c5f2ba3
--- /dev/null
+++ b/kamailio.roles
@@ -0,0 +1,4 @@
+USER=kamailio:status,kamailio:startstop,kamailio:listusers,kamailio:reloadplan
+EDITOR=kamailio:createuser,kamailio:updateuser,kamailio:deleteuser,kamailio:listtables,kamailio:viewtable,kamailio:deletetableentry,kamailio:updatetableentry,kamailio:createtableentry,kamailio:searchdatabase,kamailio:reloadplan
+EXPERT=kamailio:listfiles,kamailio:edit,kamailio:createdatabase,kamailio:reloadplan
+ADMIN=kamailio:status,kamailio:startstop,kamailio:listusers,kamailio:createuser,kamailio:updateuser,kamailio:deleteuser,kamailio:listfiles,kamailio:edit,kamailio:listtables,kamailio:viewtable,kamailio:deletetableentry,kamailio:updatetableentry,kamailio:createtableentry,kamailio:createdatabase,kamailio:searchdatabase,kamailio:reloadplan