module(..., package.seeall) -- Load libraries require("modelfunctions") require("posix") require("fs") require("format") require("date") require("validator") -- Set variables local configfile = "/etc/rrdtool/acf-rrdtool.conf" local graphpath = "/etc/rrdtool" local databases = "/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 startstop_service(action) return modelfunctions.startstop_service(processname, action) end function getstatus() return modelfunctions.getstatus(processname, packagename, header .. " status") end function getconfigfile() return modelfunctions.getfiledetails(configfile) end function setconfigfile(filedetails) return modelfunctions.setfiledetails(filedetails, {configfile}) end function getrrdlist() return list_files(databases) end function 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 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 rrd_info(self, path, userid) local success, errtxt if (validfilename(path)) then local f = io.popen( "/usr/bin/rrdtool info ".. tostring(path) .. " 2>&1" ) success = f:read("*a") or "" f:close() end return cfe({ value=success, label="rrdtool info ".. tostring(path) , errtxt=errtxt }) end function 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 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 f = io.popen( "/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")) .. " 2>&1") success = f:read("*a") or "" f:close() configfile.errtxt = tostring(success) end return configfile end function list_graphs() local graphs = {} local files = fs.find_files_as_array(".*graph", graphpath) for k,v in pairs(files) do local filecontent = {} for kk,vv in pairs(fs.read_file_as_array(v)) do vv = string.gsub(vv,"\"", "") local delim = string.find(vv, "\=") if delim then filecontent[string.sub(vv,1,(delim-1))]=string.sub(vv,(delim+1),#vv) 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 view_graph(self, graph_grp, graph_id) local general_settings = { filenameextention=".png", graph_width="800", } local graphs = 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) .. " " cmd = cmd .. tostring(settings.value) 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 getgraphlist() return list_files(graphpath,".*graph") end function 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 = [[label=Example name for this graph group=General descr=Some describing text that helps the user to know what this graph is all about value=--start now-3600s --end now --width 680 --height 400 --step 300 DEF... ]] 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 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(filedetails, 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 setgraphfile(filedetails,file) return modelfunctions.setfiledetails(filedetails, {file}) end