local mymodule = {} -- Load libraries modelfunctions = require("modelfunctions") posix = require("posix") fs = require("acf.fs") format = require("acf.format") date = require("acf.date") validator = require("acf.validator") processinfo = require("acf.processinfo") -- Set variables local configfile = "/etc/rrdtool/acf-rrdtool.conf" local graphpath = "/etc/rrdtool" local databases = "/etc/rrdtool/databases" local db_scripts = "/etc/rrdtool/databases" local processname = "rrdtool" local packagename = "rrdtool" local header = "rrdtool" local path="PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/sbin:/usr/sbin:/sbin " -- ################################################################################ -- LOCAL FUNCTIONS local function file_info ( path ) local st = fs.stat(path) local size = st.size or "0" local lastmod = st.mtime or "---" return lastmod,size end local function list_files (path,search) local files = fs.find_files_as_array(search, path) local listed_files = {} -- Loop through each file and present its info for k,v in pairs(files) do -- Get info on this specific file and put it in a table local lastmod,size = file_info(v) local filename = cfe({ value=v, label="File name" }) local filesize = cfe({ value=size, label="File size" }) local mtime = cfe({ value=lastmod, label="File date" }) table.insert ( listed_files , cfe({ type="group", value={filename=filename, filesize=filesize, mtime=mtime, label="File details"} }) ) end table.sort(listed_files, function (a,b) return (a.value.filename.value < b.value.filename.value) end ) return cfe({ type="list", value=listed_files, label="Log files" }) end local function validfilename(path) local files = fs.find_files_as_array(nil, databases) for k,v in pairs(fs.find_files_as_array(".*graph",graphpath)) do table.insert(files,v) end for k,v in pairs(files) do if (v == path) then return true, tostring(path) end end return false, "Not a valid filename!" end -- ################################################################################ -- PUBLIC FUNCTIONS function mymodule.getstatus() return modelfunctions.getstatus(nil, packagename, header .. " status") end function mymodule.getconfigfile() return modelfunctions.getfiledetails(configfile) end function mymodule.setconfigfile(self, filedetails) return modelfunctions.setfiledetails(self, filedetails, {configfile}) end function mymodule.getrrdlist() return list_files(databases,".*rrd") end function mymodule.createnewrrd() local newdb = {} newdb.filename = cfe({label="Name",value="mydb.rrd",descr="The name of the RRD you want to create. RRD files should end with the extension .rrd. However, RRDtool will accept any filename."}) newdb.start = cfe({label="Start",value="",descr="Specifies the time in seconds since 1970-01-01 UTC when the first value should be added to the RRD. RRDtool will not accept any data timed before or at the time specified.\ (Your system saids that your current time is '" .. tostring(os.time(os.date("*t"))) .. "' in the format you should specify above. The startvalue should be smaller than your current time.)\ (Default: now - 10s)"}) newdb.step = cfe({label="Step",value="",descr="Specifies the base interval in seconds with which data will be fed into the RRD.\ (Default: 300)"}) newdb.ds = cfe({label="Data store(s)",value="",descr="DS:ds-name:GAUGE | COUNTER | DERIVE | ABSOLUTE:heartbeat:min:max \ DS:ds-name:COMPUTE:rpn-expression",type="longtext"}) newdb.rra = cfe({label="Round Robin Archive(s)",value="",descr="RRA:AVERAGE | MIN | MAX | LAST:xff:steps:rows",type="longtext"}) return {value=newdb} end function mymodule.remove_file(self, path, userid) local success = "Failed to delete file" local errtxt if not (fs.is_file(path)) then errtxt = "File doesn't exist!" elseif (validfilename(path)) then os.remove(path) success = "File Deleted" else errtxt = "Not a valid filename!" end return cfe({ value=success, label="Delete config file result", errtxt=errtxt }) end function mymodule.rrd_info(self, path, userid) local success, errtxt if (validfilename(path)) then success, errtxt = modelfunctions.run_executable({"rrdtool", "info", path}, true) else errtxt = "Invalid filename" end return cfe({ value=success, label="rrdtool info ".. tostring(path) , errtxt=errtxt }) end function mymodule.savenewrrd(self, configfile, userid) path=tostring(databases).."/"..configfile.value.filename.value configfile.errtxt = "Failed to create file" local path = configfile.value.filename.value if not fs.is_dir(databases) then fs.create_directory(databases) end if not string.find(path, "/") then path = databases .. "/" .. path elseif not validator.is_valid_filename(path,databases) then configfile.value.filename.errtxt = "Not a valid path!\ If you specify path, it should be " .. tostring(databases) .."/" return configfile end if (posix.stat(path)) then configfile.value.filename.errtxt = "File already exists" elseif (#configfile.value.step.value > 0) and not tonumber(configfile.value.step.value) then configfile.value.step.errtxt = "Only numeric values!" return configfile elseif (#configfile.value.ds.value == 0) then configfile.value.ds.errtxt = "You need to specify at least one DS!" return configfile elseif (#configfile.value.rra.value == 0) then configfile.value.rra.errtxt = "You need to specify at least one RRA!" return configfile else local start,step if (#configfile.value.start.value > 0) then start = "--start " .. tostring(configfile.value.start.value) .. " " else start = "" end if (#configfile.value.step.value > 0) then step = "--step " .. tostring(configfile.value.step.value) .. " " else step = "" end local cmd = "/usr/bin/rrdtool create ".. format.escapespecialcharacters(path) .. " " .. format.escapespecialcharacters(start) .. format.escapespecialcharacters(step) .. tostring(string.gsub(format.dostounix(format.escapespecialcharacters(configfile.value.ds.value)),"\n", " \\\n")) .. " " .. tostring(string.gsub(format.dostounix(format.escapespecialcharacters(configfile.value.rra.value)),"\n", " \\\n")) local createdate = os.date() local cmd_descr = "#!/bin/sh\n# " .. tostring(createdate) .. "\n# This file can be used to recreate '" .. tostring(path) .. "'\n\n" .. string.gsub(string.gsub(cmd," RRA", " \\\nRRA")," DS", " \\\nDS") local filedetails={} filedetails.value={} filedetails.value.filename={value=tostring(db_scripts) .. "/" .. string.match(path,".*/(.*)") .. ".cmd"} filedetails.value.filecontent={value=string.gsub(cmd_descr,"\\/","/")} if not fs.is_dir(tostring(db_scripts)) then fs.create_directory(tostring(db_scripts)) end filedetails = modelfunctions.setfiledetails(self, filedetails, mymodule.getgraphlist) filedetails = nil local f = io.popen( cmd .. " 2>&1") success = f:read("*a") or "" f:close() configfile.errtxt = tostring(success) end return configfile end function mymodule.list_graphs() local graphs = {} local files = fs.find_files_as_array(".*graph", graphpath) for k,v in pairs(files) do local filecontent = {} local cont=false for kk,vv in pairs(fs.read_file_as_array(v)) do vv = string.gsub(vv,"\n", "") local delim = string.find(vv, "\=") if cont==true then filecontent['value']=tostring(filecontent['value']) .. " " .. tostring(vv) filecontent['plutt']="test" else if delim then filecontent[string.sub(vv,1,(delim-1))]=string.sub(vv,(delim+1),#vv) --TODO: Remove trailing slash \ for each line end end if delim and string.sub(vv,1,(delim-1))=="value" then cont=true end end if not (filecontent.group) then filecontent.group = "General" end graphs[filecontent.group] = graphs[filecontent.group] or {} table.insert(graphs[(filecontent.group)],filecontent) end return graphs end function mymodule.view_graph(self, graph_grp, graph_id) local general_settings = { filenameextention=".png", graph_width="800", } local graphs = mymodule.list_graphs() local settings = graphs[tostring(graph_grp)][tonumber(self.clientdata.id)] local filename = "/".. string.lower(tostring(graph_grp)).."_"..tostring(graph_id) .. (settings.filenameextention or ".png") settings.output = tostring(filename) --TODO: Loop the defaultsettings and put it in the settings table if there is missing some information local cmd = "/usr/bin/rrdtool graph /usr/share/acf/www" .. tostring(filename) .. " " local imagedate = os.date() cmd = cmd .. tostring(settings.value) .. " --title '" .. tostring(settings.label) .. " - " .. tostring(settings.descr) .. "' -W '" .. tostring(imagedate) .. "'" settings.cmd_query = cmd local f = io.popen( tostring(cmd) .. " 2>&1" ) settings.cmd_result = f:read("*a") or "" f:close() -- Display the image in it's original size (if possible) settings.graph = {} settings.graph.width,settings.graph.height = string.match(settings.cmd_result, "^(%d+)x(%d+)") return settings end function mymodule.getgraphlist() return list_files(graphpath,".*graph") end function mymodule.getgraphfile(path) if (path) and not (validfilename(path)) then path = nil end local filedetails = modelfunctions.getfiledetails(path) -- If we create a new file, we want to help the user to know what variables he should use if not (path) then filedetails.value.filecontent.value = [[group=General label=Example graph descr=Testgraph to show network activity value=--start now-3600s --end now --width 680 --height 500 --step 60 --vertical-label 'bit' DEF:eth0rx=]] .. tostring(databases) .. [[/mydb.rrd:eth0rx:AVERAGE DEF:eth0tx=]] .. tostring(databases) .. [[/mydb.rrd:eth0tx:AVERAGE LINE2:eth0rx#006600: AREA:eth0tx#99000099: ]] end local output = {value={filename=filedetails.value.filename, filecontent=filedetails.value.filecontent,}} -- output.value.filecontent.rows="30" -- FIXME: For some reason I can't control the size of a textarea if not (path) then output.value.filename.errtxt=nil end return output end function mymodule.set_filedetails (self, filedetails, userid) if filedetails.value.filename.value and (#filedetails.value.filename.value > 0) and not (string.match(filedetails.value.filename.value, ".*/$")) then if not (string.match(filedetails.value.filename.value, "^" .. graphpath)) then filedetails.value.filename.value = graphpath .. "/" .. string.gsub(filedetails.value.filename.value,"^.*/", "") end if not (string.match(filedetails.value.filename.value, "\.graph$")) then filedetails.value.filename.value = filedetails.value.filename.value .. ".graph" end filedetails = modelfunctions.setfiledetails(self, filedetails, mymodule.getgraphlist) else filedetails.errtxt = "Failed to set file" filedetails.value.filename.errtxt = "You need to specify a valid filename!" end filedetails.value.filesize = nil filedetails.value.mtime = nil return filedetails end function mymodule.setgraphfile(self, filedetails, file) return modelfunctions.setfiledetails(self, filedetails, {file}) end return mymodule