1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
|
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
|