diff options
author | Mika Havela <mika.havela@gmail.com> | 2008-04-15 12:52:00 +0000 |
---|---|---|
committer | Mika Havela <mika.havela@gmail.com> | 2008-04-15 12:52:00 +0000 |
commit | 612b0e754176f1b7098c5c1d7973028a22ee902d (patch) | |
tree | 79e410f870556474f297aa960251d428204d75b5 | |
download | acf-gnats-612b0e754176f1b7098c5c1d7973028a22ee902d.tar.bz2 acf-gnats-612b0e754176f1b7098c5c1d7973028a22ee902d.tar.xz |
Creating a acf-gnats so that users can view/add bugreports to bugs@alpinelinux.org
git-svn-id: svn://svn.alpinelinux.org/acf/gnats/trunk@985 ab2d0c66-481e-0410-8bed-d214d4d58bed
-rw-r--r-- | Makefile | 54 | ||||
-rw-r--r-- | README | 0 | ||||
-rw-r--r-- | config.mk | 10 | ||||
-rw-r--r-- | gnats-controller.lua | 515 | ||||
-rw-r--r-- | gnats-edit-html.lsp | 58 | ||||
-rw-r--r-- | gnats-expert-html.lsp | 52 | ||||
-rw-r--r-- | gnats-model.lua | 498 | ||||
-rw-r--r-- | gnats-query-html.lsp | 68 | ||||
-rw-r--r-- | gnats-queryresult-html.lsp | 120 | ||||
-rw-r--r-- | gnats-report-html.lsp | 77 | ||||
-rw-r--r-- | gnats-reportsuccess-html.lsp | 2 | ||||
-rw-r--r-- | gnats-status-html.lsp | 29 | ||||
-rw-r--r-- | gnats-summary-html.lsp | 81 | ||||
-rw-r--r-- | gnats.menu | 8 |
14 files changed, 1572 insertions, 0 deletions
diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6a0959d --- /dev/null +++ b/Makefile @@ -0,0 +1,54 @@ +APP_NAME=gnats +PACKAGE=acf-$(APP_NAME) +VERSION=0.1.0 + +APP_DIST=\ + gnats-controller.lua \ + gnats-edit-html.lsp \ + gnats-expert-html.lsp \ + gnats-model.lua \ + gnats-query-html.lsp \ + gnats-queryresult-html.lsp \ + gnats-report-html.lsp \ + gnats-reportsuccess-html.lsp \ + gnats-status-html.lsp \ + gnats-summary-html.lsp \ + gnats.menu \ + + +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 $(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/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/gnats-controller.lua b/gnats-controller.lua new file mode 100644 index 0000000..4fa036c --- /dev/null +++ b/gnats-controller.lua @@ -0,0 +1,515 @@ +module(..., package.seeall) + +-- Load libraries +require("format") + +-- Set variables +local newrecordtxt = "[New]" +local anytxt = "Any" +local bugreportreceiver = "bugs@alpinelinux.org" + +local descr = { + labels = { + ['category'] = "Category", + ['severity'] = "Severity", + ['priority'] = "Priority", + ['class'] = "Class", + ['state'] = "State", + ['skipclosed'] = "Skip/Hide closed", + ['text'] = "Text in single-line fields", + ['multitext'] = "Text in multi-line fields", + ['responsible'] = "Responsible", + ['originator'] = "Originator", + ['release'] = "Alpine release", + ['environment']="Environment", + ['description']="Description", + ['howtorepeat']="How-to-repeat this bug", + ['fix']="Fix", + ['synopsis']="Short description (on line)", + ['from']="Your e-mail address", + ['reporter_name']="Your full name", + ['reporter_id']="Your ID (Submitter ID)", + ['reporter_org']="Your organization name", + + header = { + ['received'] = "Received", + ['to'] = "To", + ['from'] = "From", + ['date'] = "Date", + ['message_id'] = "Message ID", + ['subject'] = "Subject", + ['return_path'] = "Return path", + }, + sfields = { + ['number'] = "Number", + ['class'] = "Class", + ['responsible'] = "Responsible", + ['category'] = "Category", + ['priority'] = "Priority", + ['confidential'] = "Confidential", + ['originator'] = "Originator", + ['severity'] = "Severity", + ['keywords'] = "Keywords", + ['arrival_date'] = "Arrival date", + ['quarter'] = "Quarter", + ['synopsis'] = "Synopsis", + ['notify_list'] = "Notify list", + ['submitter_id'] = "Submitter id", + ['date_required'] = "Date required", + ['release'] = "Release", + ['last_modified'] = "Last modified", + ['closed_date'] = "Closed date", + ['state'] = "State", + }, + mfields = { + ['environment'] = "Environment", + ['release_note'] = "Release note", + ['audit_trail'] = "Audit trail", + ['howtorepeat'] = "How to repeat", + ['organization'] = "Organization", + ['fix'] = "Fix", + ['unformatted'] = "Unformatted", + }, + }, + audit_trail = { + ['Responsible-Changed-From-To'] = "Responsible Changed", + ['State-Changed-From-To'] = "State Changed", + ['From'] = "Reply via E-mail", + }, + maildescriptions = { + ['from'] = "From: ", + ['reporter_id'] = ">Submitter-Id: ", + ['xxx'] = ">Notify-List: ", + ['originator'] = ">Originator: ", + ['reporter_org'] = ">Organization: ", + ['synopsis'] = ">Synopsis: ", + ['xxx'] = ">Confidential: ", + ['severity'] = ">Severity: ", + ['priority'] = ">Priority: ", + ['category'] = ">Category: ", + ['class'] = ">Class: ", + ['release'] = ">Release: ", + ['environment'] = ">Environment: ", + ['description'] = ">Description: ", + ['howtorepeat'] = ">How-To-Repeat:", + ['fix'] = ">Fix: ", + }, + + } + +-- ################################################################################ +-- LOCAL FUNCTIONS + +local function list_redir(self) + self.conf.action = "status" + self.conf.type = "redir" + error (self.conf) +end + + +-- ################################################################################ +-- PUBLIC FUNCTIONS +mvc = {} +function mvc.on_load(self, parent) + if (self.worker[self.conf.action] == nil ) or ( self.conf.action == "init" ) then + self.worker[self.conf.action] = list_redir(self) + end +end + +function status(self) + return { status=self.model.getstatus() } +end + +function expert(self) + local modifications = self.clientdata.filecontent or "" + if ( self.clientdata.cmdsave ) then + modifications = self.model:update_filecontent(modifications) + end + local url = ENV["SCRIPT_NAME"] .. self.conf.prefix .. self.conf.controller + + local status=self.model.getstatus() + local filedetailsresult, file = self.model:get_filedetails() + + -- Add buttons + file.cmdsave = cfe ({ + name="cmdsave", + label="Apply settings", + value="Apply", + type="submit", + }) + if (self.clientdata.cmdsave) then + file.cmdsave.descr="* Changes has been saved!" + end + + return ( { + option={ script=ENV["SCRIPT_NAME"], + prefix=self.conf.prefix, + controller = self.conf.controller, + action = "update", + extra = "" }, + status = status, + file = file, + modifications = modifications, + url = url, } ) +end + +function edit(self) + + -- Save changes + local modifications = self.clientdata.filecontent or "" + if ( self.clientdata.cmdsave ) then + modifications = self.model:update_filecontent(modifications,self.clientdata.name) + end + + local status = self.model:getstatus(self) + local filedetailsresult, file = self.model:get_filedetails(self.clientdata.name) + + + if not (filedetailsresult) then + self.conf.action = "status" + self.conf.type = "redir" + error (self.conf) + end + + -- Add a cmd button to the view + file.cmdsave = cfe({ name="cmdsave", + label="Save/Apply above settings", + value="Save", + type="submit", + }) + if (modifications) then + file.cmdsave.descr="* Changes has been saved!" + end + + return { + option={ script=ENV["SCRIPT_NAME"], + prefix=self.conf.prefix, + controller = self.conf.controller, + action = "edit", + extra = "", + }, + modifications = modifications, + file = file, + status = status, + startstop = startstop, + } +end + +function query(self) + local query = {} + + query.pr = cfe({ + name="pr", + label="Query a specific PR number:", + }) + query.pr_cmd = cfe({ + name="pr_cmd", + label="Submit the query", + type="submit", + value="Submit", + }) + + local tags = { "category", "severity", "priority", "class", "state", } + for k,val in pairs(tags) do + query[val] = cfe({ + name=val, + label=(descr.labels[val] or val), + value=anytxt, + type="select", + option=self.model:get_select_opt(val), + }) + table.insert(query[val].option, 1, anytxt) + -- If there is only a limited amount records, then present as radio buttons. + if (#query[val].option < 6) then query[val].type="radio" end + end + + local val = "responsible" + query[val] = cfe({ + name=val, + label=(descr.labels[val] or val), + value=anytxt, + type="select", + option=self.model:list_responsible(), + }) + table.insert(query[val].option, 1, anytxt) + + local tags = { "skipclosed", "text", "multitext", } + for k,val in pairs(tags) do + query[val] = cfe({ + name=val, + label=(descr.labels[val] or val), + option=self.model:get_select_opt(val), + }) + end + + -- Show 'skipclosed' as a checkbox instead + query.skipclosed.type="checkbox" + query.skipclosed.checked="Yes" + + query.advanced_cmd = cfe({ + name="advanced_cmd", + label="Submit the advanced query", + type="submit", + value="Submit", + }) + + return { + option={ script=ENV["SCRIPT_NAME"], + prefix=self.conf.prefix, + controller = self.conf.controller, + action = "edit", + extra = "", + }, + query = query, + } +end + +function queryresult(self) + local query = {} + local pr_id = self.clientdata.pr + if (tonumber(pr_id)) then + query.header, query.sfields, query.mfields = self.model:read_pr(pr_id) + else + list_redir(self) + end + + local myform = query.header + for k,v in pairs(myform) do + myform[k] = cfe({ + name=k, + label=descr.labels.header[k] or k, + value=string.gsub(myform[k], "\"", ""), + }) + end + + local myform = query.sfields + for k,v in pairs(myform) do + myform[k] = cfe({ + name=k, + label=descr.labels.sfields[k] or k, + value=string.gsub(myform[k], "\"", ""), + }) + end + + local myform = query.mfields + for k,v in pairs(myform) do + myform[k] = cfe({ + name=k, + label=descr.labels.mfields[k] or k, + value=string.gsub(myform[k], "\"", ""), + }) + end + + local audit_trail_table = {} + local checkstring = "" + local checkstring2 = 0 + table.insert(audit_trail_table, cfe({class="", label=""})) + + for k,v in pairs(format.string_to_table(query.mfields.audit_trail.value, "\n")) do + if (v == "") then + checkstring2 = checkstring2 + 1 + end + if (checkstring2 == 99) then + table.insert(audit_trail_table, cfe({class="Unknown", label=""})) + checkstring = "" + checkstring2 = 0 + end + for k1, v1 in pairs(descr.audit_trail) do + if (string.match(v, "^".. string.gsub(k1, "-", "%%-"))) then + table.insert(audit_trail_table, cfe({class=k1, label=v1})) + checkstring = k1 + checkstring2 = 0 + break + end + end + if (#audit_trail_table > 0) then + table.insert(audit_trail_table[#audit_trail_table], string.match(v, "^.+")) --Strips empty lines + end + end + + query.mfields.audit_trail.option=audit_trail_table + + return { + option={ script=ENV["SCRIPT_NAME"], + prefix=self.conf.prefix, + controller = self.conf.controller, + action = "edit", + extra = "", + }, + query = query, + } + +end + +function summary(self) + -- parse the search criterias - select boxes + local action = "Query" + local s = {} + for i, f in ipairs({"class", "category", "severity", "priority", "state", "responsible"}) do + s[f] = self.model:validate_field(self.clientdata[f], f) + end + + local invalid = nil + -- parse the search criterias - input boxes + for i,f in ipairs({"text", "multitext", "originator"}) do + s[f] = self.model:validate_textfield(self.clientdata[f], f) + -- replace this with ajax stuff? + if action == "Query" and s[f] == nil then + self.model:set_invalid_field(f) + invalid = true + end + -- if field is blank, then ignore it + if action == "Query" and s[f] == "" then + s[f] = nil + end + end + + -- we have found atleast one invalid field. reset the action + if invalid then + action = nil + end + + if (self.clientdata.skipclosed) then + s["skip-closed"] = "" + else + s["skip-closed"] = nil + end + + -- Set the search criterias + self.model:set_search_criteria(s) + + return { + option={ script=ENV["SCRIPT_NAME"], + prefix=self.conf.prefix, + controller = self.conf.controller, + action = "edit", + extra = "", + }, + summary = self.model:summary(), + } +end + +function report(self) + local reportform = {} + local status = self.model:getstatus(self) + + local alltags = { "reporter_name", "from", "reporter_id", "reporter_org", + "synopsis","severity","priority","category","class","release","environment", + "description", "howtorepeat", "fix", } + + + for k,val in pairs(alltags) do + reportform[val] = cfe({ + name=val, + label=(descr.labels[val] or val), + value=(self.clientdata[val] or ""), + }) + end + + local tags = { "category", "priority","severity","class" } + for k,val in pairs(tags) do + reportform[val] = cfe({ + name=val, + label=(descr.labels[val] or val), + value=(self.clientdata[val] or ""), + type="select", + option=self.model:get_select_opt(val), + }) + -- If there is only a limited amount records, then present as radio buttons. + if (#reportform[val].option < 6) then + reportform[val].type="radio" + else + table.insert(reportform[val].option, 1, "") + end + end + + reportform.description.type="longtext" + reportform.howtorepeat.type="longtext" + reportform.fix.type="longtext" + reportform.environment.type="longtext" + reportform.release.value=status.alpinerelease.value or "" + + -- Add buttons + reportform.submit = cfe ({ + name="submit", + label="Submit this repport", + value="Submit", + type="submit", + }) + + if (self.clientdata.submit) then + + if not (self.clientdata.from) or (#self.clientdata.from == 0) then + reportform.from.errtxt = "You need to enter a valid e-mail address" + end + + if not (self.clientdata.synopsis) or (#self.clientdata.synopsis == 0) then + reportform.synopsis.errtxt = "You need to enter a valid online-description" + end + + if not (self.clientdata.severity) or (#self.clientdata.severity == 0) then + reportform.severity.errtxt = "You need to choose one of these options" + end + + if not (self.clientdata.priority) or (#self.clientdata.priority == 0) then + reportform.priority.errtxt = "You need to choose one of these options" + end + + if not (self.clientdata.category) or (#self.clientdata.category == 0) then + reportform.category.errtxt = "You need to choose one of these options" + end + + if not (self.clientdata.class) or (#self.clientdata.class == 0) then + reportform.class.errtxt = "You need to choose one of these options" + end + + local noerrorsexists + for k,v in pairs(alltags) do + noerrorsexists = false + if (#reportform[v]['errtxt'] > 0) then break end + noerrorsexists = true + end + + -- If we had no errors, then we proceed with creating/sending the bugreport + if (noerrorsexists) then + local bugreport = {} + table.insert(bugreport, "To: " .. (bugreportreceiver or "")) + table.insert(bugreport, "From: " .. (self.clientdata.from or "")) + table.insert(bugreport, "Reply-To: " .. (self.clientdata.from or "")) + table.insert(bugreport, "") + for k,v in pairs({"reporter_id", "notify_list", "originator", "synopsis", "confidential", "severity", + "priority", "category", "class", "release", }) do + if (v) and (descr.maildescriptions[v]) and (self.clientdata[v]) then + table.insert(bugreport, descr.maildescriptions[v] .. self.clientdata[v] ) + end + end + table.insert(bugreport, descr.maildescriptions.environment .. "\n" .. (self.clientdata.environment or "") .."\n" ) + table.insert(bugreport, descr.maildescriptions.howtorepeat .. "\n" .. (self.clientdata.howtorepeat or "") .."\n" ) + + table.insert(bugreport, descr.maildescriptions.fix .. "\n" .. (self.clientdata.fix or "") .."\n" ) + + local bugreportsuccess, bugreport = self.model:sendbug(bugreport) + + if (bugreportsuccess) then + self.conf.action = "reportsuccess" + self.conf.type = "redir" + error (self.conf) + end + reportform.submit.errtxt = "Something went wrong because the report could not be sent.\n" .. bugreport + end + end + + return { + option={ script=ENV["SCRIPT_NAME"], + prefix=self.conf.prefix, + controller = self.conf.controller, + action = "edit", + extra = "", + }, + reportform = reportform, + } +end + +function reportsuccess() + -- Just show that the report was successfully sent. +end diff --git a/gnats-edit-html.lsp b/gnats-edit-html.lsp new file mode 100644 index 0000000..08dee18 --- /dev/null +++ b/gnats-edit-html.lsp @@ -0,0 +1,58 @@ +<? local form = ... +require("viewfunctions") +?> +<? +--[[ DEBUG INFORMATION +io.write("<H1>DEBUGGING</H1><span style='color:red'><H2>DEBUG INFO: CFE</H2>") +io.write(html.cfe_unpack(form)) +io.write("</span>") +--]] +?> + +<H1>SYSTEM INFO</H1> +<DL> +<? +local myform = form.status +local tags = { "status", "version", "autostart", } +displayinfo(myform,tags,"viewonly") +?> +</DL> + +<form name="myform" action="" method="POST"> +<h1>CONFIGURATION</h1> +<H2>Expert config</H2> +<h3>File details</h3> +<DL> +<? +local myform = form.file +local tags = { "filename", "filesize", "mtime", "sumerrors", } +displayinfo(myform,tags,"viewonly") +?> +</DL> + +<H3>FILE CONTENT</H3> +<? +local myform = form.file +io.write('<input type="hidden" value="' .. myform.filename.value .. '" name="name">') +io.write(html.form[myform.filecontent.type](myform.filecontent)) +?> + +<H2>SAVE AND APPLY ABOVE SETTINGS</H2> +<DL> +<? +local tags = { "cmdsave", } +displayinfo(myform,tags) +?> +</DL> + +<? +-- Management buttons +local myform = form.management +local tags = { "start", "stop", "restart" } +if (myform) then + io.write("<H1>MANAGEMENT</H1>\n<DL>") + displaymanagement(myform,tags) + io.write("</DL>") +end +?> +</form> diff --git a/gnats-expert-html.lsp b/gnats-expert-html.lsp new file mode 100644 index 0000000..da4ac15 --- /dev/null +++ b/gnats-expert-html.lsp @@ -0,0 +1,52 @@ +<? local form = ... +require("viewfunctions") +?> +<? +--[[ DEBUG INFORMATION +io.write("<H1>DEBUGGING</H1><span style='color:red'><H2>DEBUG INFO: CFE</H2>") +io.write(html.cfe_unpack(form)) +io.write("</span>") +--]] +?> + + +<H1>SYSTEM INFO</H1> +<DL> +<? +local myform = form.status +local tags = { "status", "version", "autostart", } +displayinfo(myform,tags,"viewonly") +?> +</DL> + +<h1>CONFIGURATION</h1> +<h2>Expert config</h2> +<h3>List of files</h3> +<DL> +<TABLE> +<? +local myform = form.status.configfiles +for k,v in pairs(myform.option) do +?> + <TR> + <TD style="padding-right:20px;white-space:nowrap;"><?= html.link{value = "edit?name=" .. v , label=v } ?></TD> + </TR> +<? end ?> +</TABLE> +</DL> + + +<form action="<?= form.option.script .. "/" .. form.option.prefix .. + form.option.controller .. "/" .. form.option.action .. + (form.option.extra or "") ?>" method="POST"> +<? +-- Management buttons +local myform = form.management +local tags = { "start", "stop", "restart" } +if (myform) then + io.write("<H1>MANAGEMENT</H1>\n<DL>") + displaymanagement(myform,tags) + io.write("</DL>") +end +?> +</form> diff --git a/gnats-model.lua b/gnats-model.lua new file mode 100644 index 0000000..28b9d9e --- /dev/null +++ b/gnats-model.lua @@ -0,0 +1,498 @@ +module(..., package.seeall) + +-- Load libraries +require("fs") +require("procps") +require("date") +require("getopts") +require("format") +require("posix") +require("daemoncontrol") +require("validator") +require("socket") +require("processinfo") + +-- Set variables +local configfile = "" +local processname = "gnats" +local packagename = "gnats" +local baseurl = "/etc/gnats" --No trailing / +local gnatsopts = " -H dev.alpinelinux.org " +-- constants +local SECT_HEADER = 1 +local SECT_SFIELDS = 2 +local SECT_MFIELDS = 3 +local header = {} +local sfields = {} +local mfields = {} +local fields_single = { + ["number"] = "", ["category"] = "", ["synopsis"] = "", + ["confidential"] = "", ["severity"] = "", ["priority"] = "", + ["responsible"] = "", ["state"] = "", ["quarter"] = "", + ["keywords"] = "", ["date_required"] = "", ["class"] = "", + ["submitter_id"] = "", ["arrival_date"] = "", ["closed_date"] = "", + ["last_modified"] = "", ["originator"] = "", ["release"] = "", + ["notify_list"] = "" +} + +local fields_multiple = { + ["organization"]="", ["environment"]="", ["description"]="", + ["how_to_repeat"]="", ["fix"]="", ["release_note"]="", + ["audit_trail"]="", ["unformatted"]="" } + +local descr = { +} + +-- ################################################################################ +-- LOCAL FUNCTIONS + +-- Function to recursively inserts all filenames in a dir into an array +local function recursedir(path, filearray) + local k,v + for k,v in pairs(posix.dir(path) or {}) do + -- Ignore files that begins with a '.' + if not string.match(v, "^%.") then + local f = path .. "/" .. v + -- If subfolder exists, list files in this subfolder + if (posix.stat(f).type == "directory") then + recursedir(f, filearray) + else + table.insert(filearray, f) + end + end + end +end + +-- ################################################################################ +-- LOCAL GNATS SPECIFIC FUNCTIONS + +local function process_header(line) + local k, v + if string.find(line, "^%s*$") then + return 1 + end + + k, v = string.match(line, "^([%a%d_.-]+): (.*)$") + if k then + header[string.lower(k:gsub("-", "_"))] = v + end + return 0 +end + +local function process_sfields(line) + local k, v, i, j, key + local found = 0 + key, v = string.match(line, "^>([%a-]+):%s*(.*)$") + if key then + k = string.lower(key:gsub("-", "_")) + if fields_single[k] == nil then + return 1 + end + fields_single[k] = v + end + return 0 +end + +local current_mfield = 0 + +local function mfield_add_line(line) + if fields_multiple[current_mfield] == nil then + fields_multiple[current_mfield] = line or "" + else + fields_multiple[current_mfield] = fields_multiple[current_mfield].."\n"..(line or "") + end +end + +local function process_mfields(line) + local k, v = string.match(line, "^>([%a-]+):%s*(.*)$") + if k then + k = string.lower(k:gsub("-", "_")) + if fields_multiple[k] then + current_mfield = k + end + else + v = line + end + mfield_add_line(v) + return 0 +end + +local function get_array(name) + local a = {} + local f = assert(io.popen("/usr/bin/query-pr "..gnatsopts.." --valid-values " .. tostring(name))) + for line in f:lines() do + table.insert(a, line) + end + f:close() + return a +end + +local function get_various_info() + local f = assert(io.popen("/usr/bin/query-pr "..gnatsopts.." -x -q | wc -l")) + local count = f:read("*l") + f:close() + return count +end + +-- ################################################################################ +-- GNATS SPECIFIC FUNCTIONS + + +-- generate an array of select options +function get_select_opt(self, name) + return get_array(name) +end + +-- generate an array of select options +function list_responsible() + local a = {} + local f = assert(io.popen("/usr/bin/query-pr "..gnatsopts.." --list-responsible ")) + for line in f:lines() do + table.insert(a, string.match(line,"^(.-):.*$")) + end + f:close() + return a +end + +function summary() + -- TODO: this func should return an iterator instead of a table + local line + local pr = {} + local prs = {} + local i,k,v + local search_opts = "" + + -- create command line options for quer-pr. For example: + -- + -- --priority low --class sw-bug --category doc --skip-closed + -- + -- Note that --skip-closed has value = "" + -- + for k,v in pairs(search_criteria) do + if v then + search_opts = search_opts.." --"..k.." "..v + end + end + + local f = assert(io.popen("query-pr "..gnatsopts.. (search_opts or ""))) + + i = 0 + for line in f:lines() do + -- if line is empty, insert to array and reset record + if line == "" then + prs[i] = pr + pr = {} + end + -- extract the key/value: + -- >Number: 24 + -- >Category: pending + -- >Synopsis: bug report + -- >Confidential: no + -- >Severity: serious + -- >Priority: medium + -- >Responsible: gnats-admin + -- >State: open + -- >Class: sw-bug + -- >Submitter-Id: unknown + -- >Arrival-Date: Sat Apr 07 14:45:02 +0000 2007 + -- >Originator: Nathan Angelacos <nangel@nothome.org> + -- >Release: + -- + -- >Number: 25 + -- ... + k,v = line:match(">(.-):%s+(.*)") + if k then + -- convert to lowercase and replace '-' with '_' + -- + -- Submitter-Id -> submitter_id + local key = string.lower(k:gsub("-", "_")) + if key == "number" then + i = tonumber(v) + end + if key == "arrival_date" then + local datetable = date.string_to_table(v) + if (tonumber(datetable.month) < 10) then datetable.month = "0" .. tostring(datetable.month) end +-- if (tonumber(datetable.day) < 10) then datetable.day = "0" .. tostring(datetable.day) end + pr['submit_date'] = string.sub(datetable.year,3) .. "/" .. datetable.month .. "/" .. datetable.day + pr['submit_date_in_seconds'] = os.time(datetable) + end + + pr[ key ] = v + end + end + + local prs_sorted = {} + for k,v in pairs(prs) do + table.insert(prs_sorted, v) + end + table.sort(prs_sorted, function(a,b) return (a.submit_date_in_seconds > b.submit_date_in_seconds) end) + + return prs_sorted +end + +-- validate if "value" is a valid field +function validate_field(self, value, field) + -- check if for or field is nil + if field == nil or value == nil then + return nil + end + + -- loop through the array + for k,v in pairs(get_array(field)) do + if value == v then + return value + end + end + return nil +end + +-- validate for insecure chars in field +function validate_textfield(self,value) + if value == nil or value == "" then + return "" + end + return string.match(value, "^[-_%a%d@.]*$") +end + +-- just set the private variable +function set_search_criteria(self, criteria) + search_criteria = criteria +end +-- append an n valid input message +function set_invalid_field(self,field) + table.insert(invalid_input, field) +end + +--[==[ +-- return a table of invald input fields +function get_invalid_inputs() + return invalid_input +end +--]==] + +--[=[ +-- dump the pr +function write_pr(id) + local cmd = "query-pr -F "..gnatsopts.." "..tostring(id) + local f = assert(io.popen(cmd)) + io.write(f:read("*all")) + f:close() +end +--]=] +-- read pr to header, sfields and mfields +function read_pr(self, id) + local cmd = "query-pr -F "..gnatsopts.." "..tostring(id) + local f = assert(io.popen(cmd)) + local line + local section = SECT_HEADER + + for line in f:lines() do + -- read header + if section == SECT_HEADER then + section = section + process_header(line) + end + if section == SECT_SFIELDS then + section = section + process_sfields(line) + end + if section == SECT_MFIELDS then + section = section + process_mfields(line) + end + end + return header, fields_single, fields_multiple +end + +-- ################################################################################ +-- PUBLIC FUNCTIONS + +function startstop_service ( self, action ) + local cmd = action.value + local cmdresult,cmdmessage,cmderror,cmdaction = daemoncontrol.daemoncontrol(processname, cmd) + action.descr=cmdmessage + action.errtxt=cmderror + return cmdresult,action +end + +function valid_filename(self, path) + local files = {} + recursedir(baseurl,files) + for k,v in pairs(files) do + if (v == path) then + return true + end + end + return false, "Not a valid filename!" +end + +function getstatus() + local status = {} + local config = getconfig() + local value, errtxt = processinfo.package_version(packagename) + status.version = cfe({ name = "version", + label="Program version", + value=value, + errtxt=errtxt, + }) + + local files = {} + recursedir(baseurl,files) + status.configfiles = cfe({ name="configfiles", + label="Config files", + type="select", + descr=table.concat(files,"\n"), + option=files, + }) + status.configfiles.size = #status.configfiles.option + + status.numbugs = cfe({ name="numbugs", + label="Still not closed reports", + value=get_various_info(), + }) + + local alpinerelease = fs.read_file_as_array("/etc/alpine-release") or {""} + status.alpinerelease = cfe({ name="alpinerelease", + label="Alpine release", + value=alpinerelease[1], + }) + + status.debug = cfe({ name="debug", + label="DEBUG output", + value=get_various_info(), + }) + + + return status +end + +function get_logfile () + local file = {} + local cmdtxt = "cat /var/log/messages | grep " .. processname + local cmd, error = io.popen(cmdtxt ,r) + local cmdoutput = cmd:read("*a") + cmd:close() + + file["filename"] = cfe({ + name="filename", + label="File name", + value=cmdtxt, + }) + + file["filecontent"] = cfe({ + type="longtext", + name="filecontent", + label="File content", + value=cmdoutput, + }) + + return file +end + +function getconfig() + if (fs.is_file(configfile)) then + return getopts.getoptsfromfile(configfile) + end + return false +end + +function get_filedetails(self,path) + if not (valid_filename(self,path)) then + return false, {['errtxt'] = "Not a valid filename!"} + end + local file = {} + local filedetails = {} + local config = {} + local filenameerrtxt + if (path) and (fs.is_file(path)) then + filedetails = fs.stat(path) + config = getconfig(path) + else + config = {} + config.filename = {} + config["filename"]["errtxt"]="Config file '".. path .. "' is missing!" + end + + file["filename" .. (num or "")] = cfe({ + name="filename" .. (num or ""), + label="File name", + value=path, + errtxt=filenameerrtxt + }) + file["filesize" .. (num or "")] = cfe({ + name="filesize" .. (num or ""), + label="File size", + value=filedetails.size or 0, + }) + file["mtime" .. (num or "")] = cfe({ + name="mtime" .. (num or ""), + label="File date", + value=filedetails.mtime or "---", + }) + file["filecontent" .. (num or "")] = cfe({ + type="longtext", + name="filecontent" .. (num or ""), + label="File content", + value=fs.read_file(path), + }) + + -- Sum all errors into one cfe + local sumerrors = "" + for k,v in pairs(config or {}) do + if (config[k]) and (config[k]["errtxt"]) and (config[k]["errtxt"] ~= "") then + sumerrors = sumerrors .. config[k]["errtxt"] .. "\n" + end + end + if (sumerrors ~= "") then + file["sumerrors" .. (num or "")] = cfe ({ + name="sumerrors" .. (num or ""), + label = "Configuration errors", + errtxt = string.match(sumerrors, "(.-)\n$"), + }) + end + + return true, file +end +function update_filecontent (self, modifications,path) + if not (valid_filename(self,path)) then + return false, {['errtxt'] = "Not a valid filename!"} + end + local file_result,err = fs.write_file(path, format.dostounix(modifications)) + if (err) and (#err > 0) then + return false, err + end + return true, file_result +end + +function sendbug (self, message) + if not (message) then + return false, "No valid message to send" + end + + if not (string.match(string.lower(tostring(message[1])), "^to:")) or + not (string.match(string.lower(tostring(message[2])), "^from:")) then + return false, "Message is malformatted." + end + + local cmdtxt = "/usr/bin/which sendmail" + local cmd, error = io.popen(cmdtxt ,r) + local cmdoutput = cmd:read("*a") + cmd:close() + if not (cmdoutput) then + return false, "Sendmail is not installed!" + end + + local mailtxt = "outgoing_mail" + fs.write_file(mailtxt, table.concat(message , "\n")) + + local cmdtxt = "/usr/sbin/sendmail -oi -t < " .. mailtxt .. " 2>&1" + local cmd, error = io.popen(cmdtxt ,r) + local cmdoutput = cmd:read("*a") + cmd:close() + + local cmd, error = io.popen("/bin/rm -f " .. mailtxt ,r) + cmd:close() + + if (#cmdoutput > 0) then + return false, cmdoutput + end + return true +end + diff --git a/gnats-query-html.lsp b/gnats-query-html.lsp new file mode 100644 index 0000000..5aeace6 --- /dev/null +++ b/gnats-query-html.lsp @@ -0,0 +1,68 @@ +<? local form = ... ?> +<? +--[[ DEBUG INFORMATION +io.write("<H1>DEBUGGING</H1><span style='color:red'><H2>DEBUG INFO: CFE</H2>") +io.write(html.cfe_unpack(form)) +io.write("</span>") +--]] +?> + +<? +function displayinfo(myform,tags,viewtype) + for k,v in pairs(tags) do + if (myform[v]) and (myform[v]["value"]) then + local val = myform[v] + io.write("\n\t<DT") + if (#val.errtxt > 0) then + val.class = "error" + io.write(" class='error'") + end + io.write(">" .. val.label .. "</DT>") + io.write("\n\t\t<DD>") + if (viewtype == "viewonly") then + if (val.value == "") then val.value = " " end + io.write(val.value) + elseif (val.type == "radio") and (type(val.option) == "table") and (#val.option > 0) then + io.write("<span style='display:inline' class='" .. ( val.class or "") .. "'>") + for k1,v1 in pairs(val.option) do + io.write("\n\t\t\t"..tostring(v1) .. ":") + io.write("<input style='margin-right:20px;margin-left:5px;' type='radio' class='" .. ( val.class or "") .. "' name='" .. val.name .. "'") + if (tostring(val.value) == tostring(v1)) then io.write(" checked='yes'") end + io.write(" value='" .. v1 .. "'>") + end + io.write("\n\t\t\t</input></span>") + else + io.write(html.form[val.type](val)) + end + if (val.descr) and (#val.descr > 0) then io.write("\n\t\t<P CLASS='descr'>" .. string.gsub(val.descr, "\n", "<BR>") .. "</P>") end + if (#val.errtxt > 0) then io.write("\n\t\t<P CLASS='error'>" .. string.gsub(val.errtxt, "\n", "<BR>") .. "</P>") end + io.write("\n\t\t</DD>\n") + end + end +end +?> + +<H1>Query Alpine problem reports</H1> + +<form name="cmd" action="queryresult" method="POST"> +<H2>Basic query</H2> +<DL> +<? +local myform = form.query +local tags = { "pr","pr_cmd", } +displayinfo(myform,tags) +?> +</DL> +</form> + +<form name="cmd" action="summary" method="POST"> +<H2>Advanced query</H2> +<DL> +<? +local myform = form.query +local tags = { "category", "severity", "priority", "class", "state", "skipclosed", "text", "multitext", "responsible", "originator", "release", "advanced_cmd", } +displayinfo(myform,tags) +?> +</DL> +</form> + diff --git a/gnats-queryresult-html.lsp b/gnats-queryresult-html.lsp new file mode 100644 index 0000000..c47b919 --- /dev/null +++ b/gnats-queryresult-html.lsp @@ -0,0 +1,120 @@ +<? local form = ... ?> +<? +--[[ DEBUG INFORMATION +io.write("<H1>DEBUGGING</H1><span style='color:red'><H2>DEBUG INFO: CFE</H2>") +io.write(html.cfe_unpack(form.query)) +io.write("</span>") +--]] +?> + +<? +function displayinfo(myform,tags,viewtype) + for k,v in pairs(tags) do + if (myform[v]) and (myform[v]["value"]) then + local val = myform[v] + io.write("\n\t<DT") + if (#val.errtxt > 0) then + val.class = "error" + io.write(" class='error'") + end + io.write(">" .. val.label .. "</DT>") + io.write("\n\t\t<DD>") + if (viewtype == "viewonly") then + if not (val.value) or (val.value == "") then val.value = " " end + io.write(val.value) + elseif (val.type == "radio") and (type(val.option) == "table") and (#val.option > 0) then + io.write("<span style='display:inline' class='" .. ( val.class or "") .. "'>") + for k1,v1 in pairs(val.option) do + io.write("\n\t\t\t"..tostring(v1) .. ":") + io.write("<input style='margin-right:20px;margin-left:5px;' type='radio' class='" .. ( val.class or "") .. "' name='" .. val.name .. "'") + if (tostring(val.value) == tostring(v1)) then io.write(" checked='yes'") end + io.write(" value='" .. v1 .. "'>") + end + io.write("\n\t\t\t</input></span>") + else + io.write(html.form[val.type](val)) + end + if (val.descr) and (#val.descr > 0) then io.write("\n\t\t<P CLASS='descr'>" .. string.gsub(val.descr, "\n", "<BR>") .. "</P>") end + if (#val.errtxt > 0) then io.write("\n\t\t<P CLASS='error'>" .. string.gsub(val.errtxt, "\n", "<BR>") .. "</P>") end + io.write("\n\t\t</DD>\n") + end + end +end +?> + +<H1><? io.write(form["query"]["sfields"]["category"]["value"] .. "/") +io.write(form["query"]["sfields"]["number"]["value"] .. ": " ) +io.write(form["query"]["sfields"]["synopsis"]["value"] or form["query"]["header"]["subject"]["value"] or "Qurery object")?></H1> + +<H2>Header</H2> +<DL> +<? +local myform = form.query.header +local tags = { "from","date", "subject", } +displayinfo(myform,tags,"viewonly") +?> +</DL> + +<H2>Details</H2> +<DL> +<? +local myform = form.query.sfields +local tags = { "class", "release", "state", "priority", "severity", "responsible", "arrival_date", "closed_date", "last_modified", "originator", } +displayinfo(myform,tags,"viewonly") +?> +</DL> + +<? +local myform = form.query.mfields +local tags = {"organization", "environment", "description", "how_to_repeat", "fix", "release_note", } +for k,v in pairs(tags) do + local myform=myform[v] +?> +<H2><? io.write(myform.label) ?></H2> +<DL> +<PRE><? io.write(myform.value) ?></PRE> +</DL> +<? end ?> + + +<style> +table {margin:10px;margin-top:5px;border:1px solid #aaa;background:#eee;} +.Responsible-Changed-From-To {background: #fcc;} +.Responsible-Changed-From-To {background: #fcc;} +.State-Changed-From-To {background: #cfc;} +.From {background: #ccf;} +.Header {padding:3px;font-weight:bold;border-bottom:1px solid #665;} +</style> + +<? +local myform = form.query["mfields"]["audit_trail"] +?> +<H2><? io.write(myform.label) ?></H2> +<DL> +<? +for k,v in pairs(myform.option) do + io.write("\n<TABLE STYLE='width:98%;'>") + if (#v.label > 0) then + io.write("<TR CLASS='" .. (v.class or "") .. "'><TD COLSPAN=2 CLASS='Header'>" .. (v.label or "Uknown") .. "</TD></TR>") + end + for i=1,#v do + io.write("<TR><TD WIDTH='100%'><PRE>" .. (v[i] or "Uknown") .. "</PRE></TD></TR>") + end + io.write("\n</TABLE>") +end +?> +</DL> + +<? +local myform = form.query.mfields +local tags = {"unformatted", } +for k,v in pairs(tags) do + local myform=myform[v] +?> +<H2><? io.write(myform.label) ?></H2> +<DL> +<PRE><? io.write(myform.value) ?></PRE> +</DL> +<? end ?> + + diff --git a/gnats-report-html.lsp b/gnats-report-html.lsp new file mode 100644 index 0000000..b438fa7 --- /dev/null +++ b/gnats-report-html.lsp @@ -0,0 +1,77 @@ +<? local form = ... +?> +<? +--[[ DEBUG INFORMATION +io.write("<H1>DEBUGGING</H1><span style='color:red'><H2>DEBUG INFO: CFE</H2>") +io.write(html.cfe_unpack(form)) +io.write("</span>") +--]] +?> + +<? +function displayinfo(myform,tags,viewtype) + for k,v in pairs(tags) do + if (myform[v]) and (myform[v]["value"]) then + local val = myform[v] + io.write("\n\t<DT") + if (#val.errtxt > 0) then + val.class = "error" + io.write(" class='error'") + end + io.write(">" .. val.label .. "</DT>") + io.write("\n\t\t<DD>") + if (viewtype == "viewonly") then + if (val.value == "") then val.value = " " end + io.write(val.value) + elseif (val.type == "radio") and (type(val.option) == "table") and (#val.option > 0) then + io.write("<span style='display:inline' class='" .. ( val.class or "") .. "'>") + for k1,v1 in pairs(val.option) do + io.write("\n\t\t\t"..tostring(v1) .. ":") + io.write("<input style='margin-right:20px;margin-left:5px;' type='radio' class='" .. ( val.class or "") .. "' name='" .. val.name .. "'") + if (tostring(val.value) == tostring(v1)) then io.write(" checked='yes'") end + io.write(" value='" .. v1 .. "'>") + end + io.write("\n\t\t\t</input></span>") + else + io.write(html.form[val.type](val)) + end + if (val.descr) and (#val.descr > 0) then io.write("\n\t\t<P CLASS='descr'>" .. string.gsub(val.descr, "\n", "<BR>") .. "</P>") end + if (#val.errtxt > 0) then io.write("\n\t\t<P CLASS='error'>" .. string.gsub(val.errtxt, "\n", "<BR>") .. "</P>") end + io.write("\n\t\t</DD>\n") + end + end +end +?> + +<H1>REPORT A BUG</H1> + +<form name="cmd" action="" method="POST"> + +<H2>Info about you</H2> +<DL> +<? +local myform = form.reportform +local tags = { "reporter_name", "from", "reporter_id", "reporter_org" } +displayinfo(myform,tags) +?> +</DL> + +<H2>Report description</H2> +<DL> +<? +local myform = form.reportform +local tags = { "synopsis","severity","priority","category","class","release","environment", "description", "howtorepeat", "fix", } +displayinfo(myform,tags) +?> +</DL> + +<H2>Submit repport</H2> +<DL> +<? +local myform = form.reportform +local tags = { "submit", } +displayinfo(myform,tags) +?> +</DL> +</form> + diff --git a/gnats-reportsuccess-html.lsp b/gnats-reportsuccess-html.lsp new file mode 100644 index 0000000..9f6ba13 --- /dev/null +++ b/gnats-reportsuccess-html.lsp @@ -0,0 +1,2 @@ +<H1>Thank you!</H1> +<p><BR>Your report was successfully sent!<BR><BR></p> diff --git a/gnats-status-html.lsp b/gnats-status-html.lsp new file mode 100644 index 0000000..1cf2963 --- /dev/null +++ b/gnats-status-html.lsp @@ -0,0 +1,29 @@ +<? local form = ... +require("viewfunctions") +?> +<? +--[[ DEBUG INFORMATION +io.write("<H1>DEBUGGING</H1><span style='color:red'><H2>DEBUG INFO: CFE</H2>") +io.write(html.cfe_unpack(form)) +io.write("</span>") +--]] +?> + +<H1>SYSTEM INFO</H1> +<DL> +<? +local myform = form.status +local tags = { "status", "version", } +displayinfo(myform,tags,"viewonly") +?> +</DL> + +<H2>PROGRAM SPECIFIC OPTIONS/INFORMATION</H2> +<DL> +<? +local myform = form.status +local tags = { "numbugs", } +displayinfo(myform,tags,"viewonly") +?> +</DL> + diff --git a/gnats-summary-html.lsp b/gnats-summary-html.lsp new file mode 100644 index 0000000..2c3f84a --- /dev/null +++ b/gnats-summary-html.lsp @@ -0,0 +1,81 @@ +<? local form = ... +require("viewfunctions") +?> +<? +--[[ DEBUG INFORMATION +io.write("<H1>DEBUGGING</H1><span style='color:red'><H2>DEBUG INFO: CFE</H2>") +io.write(html.cfe_unpack(form)) +io.write("</span>") +--]] +?> + +<style> +table { margin:10px; border:2px solid #555; } +table th { border-bottom:2px solid #555; font-weight: bold; } +table td {border:1px solid #aaa; border-right:none; border-bottom:none; } +.o { background-color: #fff; padding-bottom: 0px; } +.a { background-color: #cffafd; padding-bottom: 0px; } +.f { background-color: #ffc; padding-bottom: 0px; } +.p { background-color: #d1fbd6; padding-bottom: 0px; } +.r { background-color: #d6cfc4; padding-bottom: 0px; } +.s { background-color: #fcccd9; padding-bottom: 0px; } +.c { background-color: #c1d5db; padding-bottom: 0px; } +.header {padding:3px;font-weight:bold;border-bottom:1px solid #665;} +DT {border:1px solid #aaa; width: 40px;} +DL { padding-top: 5px; } +</style> + +<h1>Current Alpine problem reports</h1> +<p>The following is a listing of current problems submitted by Alpine users. These represent problem reports covering all versions including experimental development code and obsolete releases. </p> +<BR> + +<h3>Bugs can be in one of several states (S)</h3> + +<DL> +<DT class='o' WIDTH='300px'>o - open</DT><DD>A problem report has been submitted, no sanity checking performed.</DD> +</DL> +<DL> +<DT class='a'>a - analyzed</DT><DD>The problem is understood and a solution is being sought.</DD> +</DL> +<DL> +<DT class='f'>f - feedback</DT><DD>Further work requires additional information from the originator or the community - possibly confirmation of the effectiveness of a proposed solution.</DD> +</DL> +<DL> +<DT class='p'>p - patched</DT><DD>A patch has been committed, but some issues (MFC and / or confirmation from originator) are still open.</DD> +</DL> +<DL> +<DT class='r'>r - repocopy</DT><DD>The resolution of the problem report is dependent on a repocopy operation within the CVS repository which is awaiting completion.</DD> +</DL> +<DL> +<DT class='s'>s - suspended</DT><DD>The problem is not being worked on, due to lack of information or resources. This is a prime candidate for somebody who is looking for a project to do. If the problem cannot be solved at all, it will be closed, rather than suspended.</DD> +</DL> +<DL> +<DT class='c'>c - closed</DT><DD>A problem report is closed when any changes have been integrated, documented, and tested -- or when fixing the problem is abandoned.</DD> +</DL> + + +<H1>SUMMARY</H1> +<table style='width:98%;'> + <tr class='header'> + <th>ID</th> + <th>S</th> + <th>Submitted</th> + <th>Severity</th> + <th>Category</th> + <th>Description</th> + </tr> + +<? for k,v in pairs(form.summary) do ?> + + <tr class='<? io.write(string.sub(v.state,1,1)) ?>'> + <td width='30px'><A HREF='queryresult?pr=<? io.write(v.number) ?>' STYLE='font-weight:bold;'><? io.write(v.number or "") ?></A></td> + <td width='15px'><? io.write(string.sub(v.state,1,1) or "") ?></td> + <td width='80px'><? io.write(v.submit_date or "") ?></td> + <td width='80px'><? io.write(v.severity or "")?></td> + <td width='80px'><? io.write(v.category or "") ?></td> + <td style='white-space:normal;word-wrap:break-word'><? io.write(v.synopsis) ?></td> + </tr> +<? end ?> + +</table> + diff --git a/gnats.menu b/gnats.menu new file mode 100644 index 0000000..b9c5d6a --- /dev/null +++ b/gnats.menu @@ -0,0 +1,8 @@ +#CAT GROUP/DESC TAB ACTION +Applications 98GNATS_bugreport Status status +Applications 98GNATS_bugreport Query query +Applications 98GNATS_bugreport Report_a_BUG report +#Applications 98GNATS_bugreport Statuslist summary +#Applications 98GNATS_bugreport QueryResult queryresult +Applications 98GNATS_bugreport Configuration expert + |